0001-WIP-Support-libnss-for-as-TLS-backend.patch
application/octet-stream
Filename: 0001-WIP-Support-libnss-for-as-TLS-backend.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch 0001
Subject: WIP: Support libnss for as TLS backend
| File | + | − |
|---|---|---|
| configure | 229 | 6 |
| configure.in | 30 | 0 |
| src/backend/libpq/auth.c | 7 | 0 |
| src/backend/libpq/be-secure.c | 3 | 0 |
| src/backend/libpq/be-secure-nss.c | 1024 | 0 |
| src/backend/libpq/Makefile | 4 | 0 |
| src/backend/utils/misc/guc.c | 19 | 1 |
| src/include/common/pg_nss.h | 141 | 0 |
| src/include/libpq/libpq-be.h | 7 | 2 |
| src/include/libpq/libpq.h | 3 | 0 |
| src/include/pg_config.h.in | 3 | 0 |
| src/include/pg_config_manual.h | 2 | 3 |
| src/interfaces/libpq/fe-connect.c | 6 | 0 |
| src/interfaces/libpq/fe-secure-nss.c | 960 | 0 |
| src/interfaces/libpq/libpq-fe.h | 11 | 0 |
| src/interfaces/libpq/libpq-int.h | 5 | 0 |
| src/interfaces/libpq/Makefile | 4 | 0 |
| src/Makefile.global.in | 1 | 0 |
| src/test/ssl/Makefile | 157 | 0 |
| src/test/ssl/t/001_ssltests.pl | 98 | 101 |
| src/test/ssl/t/002_scram.pl | 1 | 1 |
| src/test/ssl/t/SSL/Backend/NSS.pm | 72 | 0 |
| src/test/ssl/t/SSL/Backend/OpenSSL.pm | 98 | 0 |
| src/test/ssl/t/SSL/Server.pm | 49 | 11 |
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)