v18-0001-NSS-Frontend-Backend-and-build-infrastructure.patch
application/octet-stream
Filename: v18-0001-NSS-Frontend-Backend-and-build-infrastructure.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch v18-0001
Subject: NSS Frontend, Backend and build infrastructure
| File | + | − |
|---|---|---|
| configure | 360 | 41 |
| configure.ac | 48 | 12 |
| contrib/postgres_fdw/expected/postgres_fdw.out | 1 | 1 |
| src/backend/libpq/auth.c | 7 | 0 |
| src/backend/libpq/be-secure.c | 3 | 0 |
| src/backend/libpq/be-secure-nss.c | 1193 | 0 |
| src/backend/libpq/hba.c | 1 | 1 |
| src/backend/libpq/Makefile | 5 | 1 |
| src/backend/utils/misc/guc.c | 19 | 1 |
| src/common/Makefile | 1 | 1 |
| src/include/common/pg_nss.h | 175 | 0 |
| src/include/libpq/libpq-be.h | 12 | 3 |
| 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 | 4 | 0 |
| src/interfaces/libpq/fe-secure.c | 4 | 1 |
| src/interfaces/libpq/fe-secure-nss.c | 1081 | 0 |
| src/interfaces/libpq/libpq-fe.h | 11 | 0 |
| src/interfaces/libpq/libpq-int.h | 17 | 1 |
| src/interfaces/libpq/Makefile | 11 | 2 |
| src/Makefile.global.in | 1 | 1 |
| src/tools/msvc/config_default.pl | 1 | 0 |
| src/tools/msvc/Mkvcbuild.pm | 18 | 4 |
| src/tools/msvc/Solution.pm | 20 | 0 |
From 0d9f8f0427880c489890bbef813bd012b9c438c5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 28 Oct 2020 11:18:42 +0100
Subject: [PATCH v18 1/5] NSS Frontend, Backend and build infrastructure
TODO:
* SCRAM channel binding is implemented but doesn't work
* Frontend CRL configuration handling
* Settle on a required version in autoconf
* Move pg_nss.h to a .c file for both frontend and backend
---
configure | 401 +++++-
configure.ac | 60 +-
.../postgres_fdw/expected/postgres_fdw.out | 2 +-
src/Makefile.global.in | 2 +-
src/backend/libpq/Makefile | 6 +-
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1193 +++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 20 +-
src/common/Makefile | 2 +-
src/include/common/pg_nss.h | 175 +++
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 13 +-
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 1081 +++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 18 +-
src/tools/msvc/Mkvcbuild.pm | 22 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
25 files changed, 3001 insertions(+), 73 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
diff --git a/configure b/configure
index ace4ed5dec..d99ec29c6a 100755
--- a/configure
+++ b/configure
@@ -654,6 +654,9 @@ LIBOBJS
UUID_LIBS
LDAP_LIBS_BE
LDAP_LIBS_FE
+with_ssl
+NSPR_CONFIG
+NSS_CONFIG
PTHREAD_CFLAGS
PTHREAD_LIBS
PTHREAD_CC
@@ -711,7 +714,6 @@ with_uuid
with_readline
with_systemd
with_selinux
-with_openssl
with_ldap
with_krb_srvnam
krb_srvtab
@@ -856,7 +858,6 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
-with_openssl
with_selinux
with_systemd
with_readline
@@ -868,6 +869,8 @@ with_libxslt
with_system_tzdata
with_zlib
with_gnu_ld
+with_ssl
+with_openssl
enable_largefile
'
ac_precious_vars='build_alias
@@ -1558,7 +1561,6 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1572,6 +1574,8 @@ Optional Packages:
use system time zone data in DIR
--without-zlib do not use Zlib
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss)
+ --with-openssl obsolete spelling of --with-ssl=openssl
Some influential environment variables:
CC C compiler command
@@ -8071,41 +8075,6 @@ fi
$as_echo "$with_bonjour" >&6; }
-#
-# OpenSSL
-#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
-
-
-
-# Check whether --with-openssl was given.
-if test "${with_openssl+set}" = set; then :
- withval=$with_openssl;
- case $withval in
- yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
- ;;
- no)
- :
- ;;
- *)
- as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
- ;;
- esac
-
-else
- with_openssl=no
-
-fi
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
-
-
#
# SELinux
#
@@ -12179,7 +12148,64 @@ fi
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-openssl option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_openssl=no
+
+fi
+
+
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12440,8 +12466,280 @@ _ACEOF
fi
done
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ if test -z "$NSS_CONFIG"; then
+ for ac_prog in nss-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSS_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSS_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSS_CONFIG=$ac_cv_path_NSS_CONFIG
+if test -n "$NSS_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
fi
+
+ test -n "$NSS_CONFIG" && break
+done
+
+else
+ # Report the value of NSS_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5
+$as_echo_n "checking for NSS_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5
+$as_echo "$NSS_CONFIG" >&6; }
+fi
+
+ if test -z "$NSPR_CONFIG"; then
+ for ac_prog in nspr-config
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_NSPR_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $NSPR_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG
+if test -n "$NSPR_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$NSPR_CONFIG" && break
+done
+
+else
+ # Report the value of NSPR_CONFIG in configure's output in all cases.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5
+$as_echo_n "checking for NSPR_CONFIG... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5
+$as_echo "$NSPR_CONFIG" >&6; }
+fi
+
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5
+$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char NSS_InitContext ();
+int
+main ()
+{
+return NSS_InitContext ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_NSS_InitContext=yes
+else
+ ac_cv_lib_nss3_NSS_InitContext=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5
+$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; }
+if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5
+$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5
+$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; }
+if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5
+$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5
+$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; }
+if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5
+fi
+
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13327,7 +13625,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -13344,6 +13642,25 @@ else
fi
+fi
+
+if test "$with_ssl" = nss ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss/nss.h" "ac_cv_header_nss_nss_h" "$ac_includes_default"
+if test "x$ac_cv_header_nss_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss/nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -18061,11 +18378,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/urandom" >&5
$as_echo_n "checking for /dev/urandom... " >&6; }
diff --git a/configure.ac b/configure.ac
index 5b91c83fd0..7b9f4b10a3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -852,15 +852,6 @@ PGAC_ARG_BOOL(with, bonjour, no,
AC_MSG_RESULT([$with_bonjour])
-#
-# OpenSSL
-#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
-
#
# SELinux
#
@@ -1209,7 +1200,21 @@ if test "$with_gssapi" = yes ; then
fi
fi
+#
+# SSL Library
+#
+# There are currently two supported SSL/TLS libraries, OpenSSL and NSS.
+#
+PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
if test "$with_openssl" = yes ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1233,7 +1238,31 @@ if test "$with_openssl" = yes ; then
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
# function was removed.
AC_CHECK_FUNCS([CRYPTO_lock])
+ AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.])
+elif test "$with_ssl" = nss ; then
+ # TODO: fallback in case nss-config/nspr-config aren't found.
+ PGAC_PATH_PROGS(NSS_CONFIG, nss-config)
+ PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config)
+ if test -n "$NSS_CONFIG"; then
+ NSS_LIBS=`$NSS_CONFIG --libs`
+ NSS_CFLAGS=`$NSS_CONFIG --cflags`
+ fi
+ if test -n "$NSPR_CONFIG"; then
+ NSPR_LIBS=`$NSPR_CONFIG --libs`
+ NSPR_CFLAGS=`$NSPR_CONFIG --cflags`
+ fi
+
+ LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS"
+ CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS"
+
+ AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])])
+ AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])])
+ AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])])
+ AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or nss])
fi
+AC_SUBST(with_ssl)
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
@@ -1405,11 +1434,16 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_ssl" = nss ; then
+ AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file <nss/ssl.h> is required for NSS])])
+ AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file <nss/nss.h> is required for NSS])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
@@ -2158,11 +2192,13 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
- if test x"$with_openssl" = x"yes" ; then
+if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then
+ if test x"$with_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
+ elif test x"$with_ssl" = x"nss" ; then
+ USE_NSS_RANDOM=1
else
AC_CHECK_FILE([/dev/urandom], [], [])
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2d88d06358..984efc0d7e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8911,7 +8911,7 @@ DO $d$
END;
$d$;
ERROR: invalid option "password"
-HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
+HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size
CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')"
PL/pgSQL function inline_code_block line 3 at EXECUTE
-- If we add a password for our user mapping instead, we should get a different
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 7ca1e9aac5..bdab2a6825 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..045d2c2f6b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -28,8 +28,12 @@ OBJS = \
pqmq.o \
pqsignal.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_ssl),nss)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d132c5cb48..d476101583 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..86ac400782
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1193 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prio.h>
+#include <nspr/prmem.h>
+#include <nspr/prtypes.h>
+
+#include <nss/nss.h>
+#include <nss/base64.h>
+#include <nss/cert.h>
+#include <nss/certdb.h>
+#include <nss/hasht.h>
+#include <nss/keyhi.h>
+#include <nss/pk11pub.h>
+#include <nss/secder.h>
+#include <nss/secerr.h>
+#include <nss/secitem.h>
+#include <nss/secoidt.h>
+#include <nss/secport.h>
+#include <nss/ssl.h>
+#include <nss/sslerr.h>
+#include <nss/sslproto.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reason, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+/* default init hook can be overridden by a shared library */
+static void default_nss_tls_init(bool isServerStart);
+nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init;
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg);
+static bool dummy_ssl_passwd_cb_called = false;
+static bool ssl_is_server_start;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ return -1;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ return -1;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ return -1;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ return -1;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ return -1;
+ }
+ }
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ */
+ ssl_is_server_start = isServerStart;
+ (*nss_tls_init_hook) (isServerStart);
+
+ return 0;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998. In current versions of NSPR all parameters
+ * are ignored.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database);
+
+ if (!nss_context)
+ ereport(FATAL,
+ (errmsg("unable to read certificate database \"%s\": %s",
+ ssl_database, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ *
+ * We use a model filedescriptor here which is a construct in NSPR/NSS in
+ * order to create a configuration template for sockets which can then be
+ * applied to new sockets created. This makes more sense in a server which
+ * accepts multiple connections and want to perform the boilerplate just
+ * once, but it does provide a nice abstraction here aswell in that we can
+ * error out early without having performed any operation on the real
+ * socket.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed ciphers. If there are no user preferred suites,
+ * set the domestic policy.
+ *
+ * Historically there were different cipher policies based on export (and
+ * import) restrictions: Domestic, Export and France. These are since long
+ * removed with all ciphers being enabled by default. Due to backwards
+ * compatibility, the old API is still used even though all three policies
+ * now do the same thing.
+ *
+ * If SSLCipherSuites define a policy of the user, we set that rather than
+ * enabling all ciphers via NSS_SetDomesticPolicy.
+ *
+ * TODO: while this code works, the set of ciphers which can be set and
+ * still end up with a working socket is woefully underdocumented for
+ * anything more recent than SSLv3 (the code for TLS actually calls ssl3
+ * functions under the hood for SSL_CipherPrefSet), so it's unclear if this
+ * is helpful or not. Using the policies works, but may be too coarsely
+ * grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, enable them all.
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port);
+ if (!server_cert)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The certificate requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port);
+ if (!private_key)
+ {
+ if (dummy_ssl_passwd_cb_called)
+ ereport(ERROR,
+ (errmsg("unable to load private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError())),
+ errhint("The private key requires a password.")));
+ else
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+ }
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key. The NULL parameter is an SSLExtraServerCertData
+ * pointer with the final parameter being the size of the extra server
+ * cert data structure pointed to. This is typically only used for
+ * credential delegation.
+ */
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+ PRIntn flags = 0;
+
+ /*
+ * The flags parameter to PR_Send is no longer used and is, according to
+ * the documentation, required to be zero.
+ */
+ n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ {
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ }
+ else
+ errno = ECONNRESET;
+ }
+
+ return n_write;
+}
+
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/*
+ * be_tls_get_version
+ *
+ * Returns the protocol version used for the current connection, or NULL in
+ * case of errors.
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ CERTCertificate *certificate;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ SECStatus status;
+ PLArenaPool *arena = NULL;
+ SECItem digest;
+ char *ret;
+
+ *len = 0;
+ certificate = SSL_LocalCertificate(port->pr_fd);
+ if (!certificate)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&certificate->signature);
+ /* Set sha-256 as a default */
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+
+ while (NSS_SCRAMDigestAlgorithm->signature)
+ {
+ if (signature_alg == NSS_SCRAMDigestAlgorithm->signature)
+ {
+ digest_alg = NSS_SCRAMDigestAlgorithm->hash;
+ digest_len = NSS_SCRAMDigestAlgorithm->len;
+ break;
+ }
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data,
+ certificate->derCert.data,
+ certificate->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = palloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * default_nss_tls_init
+ *
+ * The default TLS init hook function which users can override for installing
+ * their own passphrase callbacks and similar actions. In case no callback has
+ * been configured, or the callback isn't reload capable during a server
+ * reload, the dummy callback will be installed.
+ *
+ * The private data for the callback is set differently depending on how it's
+ * invoked. For calls which may invoke the callback deeper in the callstack
+ * the private data is set with SSL_SetPKCS11PinArg. When the call is directly
+ * invoking the callback, like PK11_FindCertFromNickname, then the private
+ * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is
+ * thus not required but good practice.
+ *
+ * NSS doesn't provide a default callback like OpenSSL does, but a callback is
+ * required to be set. The password callback can be installed at any time, but
+ * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc.
+ */
+static void
+default_nss_tls_init(bool isServerStart)
+{
+ /*
+ * No user-defined callback has been configured, install the dummy call-
+ * back since we must set something.
+ */
+ if (!ssl_passphrase_command[0])
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ else
+ {
+ /*
+ * There is a user-defined callback, set it unless we are in a restart
+ * and cannot handle restarts due to an interactive callback.
+ */
+ if (isServerStart)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ {
+ if (ssl_passphrase_command_supports_reload)
+ PK11_SetPasswordFunc(external_ssl_passphrase_cb);
+ else
+ PK11_SetPasswordFunc(dummy_ssl_passphrase_cb);
+ }
+ }
+}
+
+/*
+ * external_ssl_passphrase_cb
+ *
+ * Runs the callback configured by ssl_passphrase_command and returns the
+ * captured password back to NSS.
+ */
+static char *
+external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ /*
+ * NSS use a hardcoded 256 byte buffer for reading the password so set the
+ * same limit for our callback buffer.
+ */
+ char buf[256];
+ int len;
+ char *password = NULL;
+ char *prompt;
+
+ /*
+ * Since there is no password callback in NSS when the server starts up,
+ * it makes little sense to create an interactive callback. Thus, if this
+ * is a retry attempt then give up immediately.
+ */
+ if (retry)
+ return NULL;
+
+ /*
+ * Construct the same prompt that NSS uses internally even though it is
+ * unlikely to serve much purpose, but we must set a prompt so we might
+ * as well do it right.
+ */
+ prompt = psprintf("Enter Password or Pin for \"%s\":",
+ PK11_GetTokenName(slot));
+
+ len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+ pfree(prompt);
+
+ if (!len)
+ return NULL;
+
+ /*
+ * At least one byte with password content was returned, and NSS requires
+ * that we return it allocated in NSS controlled memory. If we fail to
+ * allocate then abort without passing back NULL and bubble up the error
+ * on the PG side.
+ */
+ password = (char *) PR_Malloc(len + 1);
+ if (!password)
+ {
+ explicit_bzero(buf, sizeof(buf));
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ }
+ strlcpy(password, buf, sizeof(password));
+ explicit_bzero(buf, sizeof(buf));
+
+ return password;
+}
+
+/*
+ * dummy_ssl_passphrase_cb
+ *
+ * Return unsuccessful if we are asked to provide the passphrase for a cert or
+ * key, without having a passphrase callback installed.
+ */
+static char *
+dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ dummy_ssl_passwd_cb_called = true;
+ return NULL;
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE);
+ if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ /*
+ * Skip over the key= portion of the key=value containing the the
+ * peer CN.
+ */
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+static PRInt32
+pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL Server");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(ERROR,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(ERROR,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ return psprintf("%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 3a78d2043e..eb1b028464 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1040,7 +1040,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-openssl or --with-nss to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a62d64eaa4..050f673c68 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."),
@@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] =
GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 25c55bd642..614f93af09 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -79,7 +79,7 @@ OBJS_COMMON = \
wait_error.o \
wchar.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
OBJS_COMMON += \
protocol_openssl.o \
sha2_openssl.o
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..008fee85f1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,175 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+#define NO_NSPR_10_SUPPORT
+#include <nss/hasht.h>
+#include <nss/secoidt.h>
+#include <nss/sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ SECOidTag signature;
+ SECOidTag hash;
+ int len;
+} NSSSignatureAlgorithms;
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = {
+
+ {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH},
+ {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH},
+ {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH},
+
+ {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH},
+ {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH},
+
+ {0, 0}
+};
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0a23281ad5..74f88b628e 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
@@ -282,7 +287,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
@@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart);
extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook;
#endif
+#ifdef USE_NSS
+typedef void (*nss_tls_init_hook_type) (bool isServerStart);
+extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook;
+#endif
#endif /* USE_SSL */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 4ac5f4b340..e902f38c8e 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -45,12 +45,21 @@ OBJS = \
pqexpbuffer.o \
fe-auth.o
-ifeq ($(with_openssl),yes)
+ifneq ($(with_ssl),no)
+OBJS += \
+ fe-secure-common.o
+endif
+
+ifeq ($(with_ssl),openssl)
OBJS += \
- fe-secure-common.o \
fe-secure-openssl.o
endif
+ifeq ($(with_ssl), nss)
+OBJS += \
+ fe-secure-nss.o
+endif
+
ifeq ($(with_gssapi),yes)
OBJS += \
fe-gssapi-common.o \
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e7781d010f..8188401659 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c
new file mode 100644
index 0000000000..3090f37f5e
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,1081 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+#include "common/pg_nss.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * Remove backwards compatibility with ancient NSPR versions to avoid this.
+ */
+#define NO_NSPR_10_SUPPORT
+#include <nspr/nspr.h>
+#include <nspr/prerror.h>
+#include <nspr/prinit.h>
+#include <nspr/prio.h>
+#include <nss/hasht.h>
+#include <nss/nss.h>
+#include <nss/ssl.h>
+#include <nss/sslproto.h>
+#include <nss/pk11func.h>
+#include <nss/secerr.h>
+#include <nss/secoidt.h>
+#include <nss/secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, const char *name);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static const char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer);
+static int ssl_protocol_version_to_nss(const char *protocol);
+static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * This logic exist in NSS as well, but it's only available for when there is
+ * a database to open, and not only using the system trust store. Thus, we
+ * need to keep our own copy.
+ */
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init
+ * being the closest match there is. PR_Init is however already documented to
+ * not be required so simply making this a noop seems like the best option.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* noop */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+ conn->has_password = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. See be-secure-nss.c for further discussion
+ * on PR_Init.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * NSS initialization and the use of contexts is further discussed in
+ * be-secure-nss.c
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to open certificate database \"%s\": %s"),
+ conn->cert_database,
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ else
+ {
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+ if (!nss_context)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to create certificate database: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"),
+ ca_trust_name,
+ pg_SSLerrmessage(PR_GetError()));
+
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"),
+ conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"),
+ conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now we closed as we've applied the settings of the model
+ * onto the real socket. From hereon we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. We
+ * don't want to sit blocked here, so the timeout is turned off by using
+ * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned
+ * for closed connections, while -1 indicates an error within the ongoing
+ * connection.
+ */
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EWOULDBLOCK;
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ read_errno = ECONNRESET;
+ break;
+
+ default:
+ break;
+ }
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * This is part of the PostgreSQL TLS backend API.
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ unsigned char c;
+ int n;
+
+ /*
+ * PR_Recv peeks into the stream with the timeount turned off, to see if
+ * there is another byte to read off the wire. There is an NSS function
+ * SSL_DataPending() which might seem like a better fit, but it will only
+ * check already encrypted data in the SSL buffer, not still unencrypted
+ * data, thus it doesn't guarantee that a subsequent call to
+ * PR_Read/PR_Recv wont block.
+ */
+ n = PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ return (n > 0);
+}
+
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ CERTCertificate *server_cert;
+ SECOidTag signature_alg;
+ SECOidTag digest_alg;
+ int digest_len;
+ PLArenaPool *arena;
+ SECItem digest;
+ char *ret;
+ SECStatus status;
+
+ *len = 0;
+
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ if (!server_cert)
+ return NULL;
+
+ signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature);
+ digest_alg = SEC_OID_SHA256;
+ digest_len = SHA256_LENGTH;
+
+ while (NSS_SCRAMDigestAlgorithm->signature)
+ {
+ if (signature_alg == NSS_SCRAMDigestAlgorithm->signature)
+ {
+ digest_alg = NSS_SCRAMDigestAlgorithm->hash;
+ digest_len = NSS_SCRAMDigestAlgorithm->len;
+ break;
+ }
+ }
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len);
+ digest.len = digest_len;
+
+ status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data,
+ server_cert->derCert.len);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to generate peer certificate digest: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ PORT_FreeArena(arena, PR_TRUE);
+ return NULL;
+ }
+
+ ret = pg_malloc(digest.len);
+ memcpy(ret, digest.data, digest.len);
+ *len = digest_len;
+ PORT_FreeArena(arena, PR_TRUE);
+
+ return ret;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert = NULL;
+ SECStatus status = SECSuccess;
+ SECItem altname_item;
+ PLArenaPool *arena = NULL;
+ CERTGeneralName *san_list;
+ CERTGeneralName *cn;
+
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME,
+ &altname_item);
+ if (status == SECSuccess)
+ {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena)
+ {
+ status = SECFailure;
+ goto done;
+ }
+ san_list = CERT_DecodeAltNameExtension(arena, &altname_item);
+ if (!san_list)
+ {
+ status = SECFailure;
+ goto done;
+ }
+
+ for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn))
+ {
+ char *alt_name;
+ int rv;
+ char tmp[512];
+
+ status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp),
+ (char *) cn->name.other.data,
+ cn->name.other.len);
+
+ if (status != SECSuccess)
+ goto done;
+
+ rv = pq_verify_peer_name_matches_certificate_name(conn, tmp,
+ strlen(tmp),
+ &alt_name);
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+
+ if (rv == 1)
+ status = SECSuccess;
+ else
+ {
+ status = SECFailure;
+ break;
+ }
+ }
+ }
+ else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND)
+ status = SECSuccess;
+ else
+ status = SECSuccess;
+
+done:
+ /* san_list will be freed by freeing the arena it was allocated in */
+ if (arena)
+ PORT_FreeArena(arena, PR_TRUE);
+ PR_Free(server_hostname);
+
+ if (status == SECSuccess)
+ return 1;
+
+ return 0;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+static const char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return error;
+
+ return "unknown TLS error";
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(),
+ server_cert,
+ checksig,
+ certificateUsageSSLServer,
+ pin,
+ NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status == SECSuccess)
+ {
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ status = SECFailure;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ }
+
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * via nickname from the cert database and see if we find one which apply with
+ * NSS_CmpCertChainWCANames() and PK11_FindKeyByAnyCert() we could support
+ * just running with a ssl database specified.
+ *
+ * For now, we use the default client certificate validation which requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn))
+ return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ return SECSuccess;
+ break;
+ default:
+ return SECFailure;
+ break;
+ }
+
+ /* Unreachable */
+ return SECSuccess;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * PQgetssl
+ *
+ * Return NULL as this is legacy and defined to always be equal to calling
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning
+ * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color
+ * of said bikeshed hasn't yet been determined.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (conn->has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn));
+ else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ conn->has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 97c3805303..dd6ee15c5a 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void);
extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook);
extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg);
+extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..5d9326490a 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database; /* NSS certificate/key database */
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,21 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd; /* NSPR file descriptor for the connection */
+ /*
+ * Track whether the NSS database has a password set or not. There is no
+ * API function for retrieving password status of a database, but we need
+ * to know since some NSS API calls require the password passed in (they
+ * don't call the callback themselves). To track, we simply flip this to
+ * true in case NSS invoked the password callback - as that will only
+ * happen in case there is a password. The reason for tracking this is that
+ * there are calls which require a password parameter, but doesn't use the
+ * callbacks provided, so we must call the callback on behalf of these.
+ */
+ bool has_password;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
@@ -755,7 +771,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
*/
-#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
+#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID))
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
#endif
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 90594bd41b..29879b51af 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.21.1 (Apple Git-122.3)