From 121286d34fd65c6a3cdf6cc9c78ce4fbe87bf90d Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Wed, 28 Oct 2020 11:18:42 +0100 Subject: [PATCH v24 1/6] NSS Frontend, Backend and build infrastructure TODO: * SCRAM channel binding is implemented but doesn't work * Frontend CRL configuration handling * Settle on a required version in autoconf * Move pg_nss.h to a .c file for both frontend and backend Daniel Gustafsson, Andrew Dunstan, Jacob Champion --- configure | 294 +++- configure.ac | 31 +- .../postgres_fdw/expected/postgres_fdw.out | 2 +- src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 7 + src/backend/libpq/be-secure-nss.c | 1402 +++++++++++++++++ src/backend/libpq/be-secure.c | 3 + src/backend/libpq/hba.c | 2 +- src/backend/utils/misc/guc.c | 20 +- src/common/Makefile | 5 + src/include/common/pg_nss.h | 175 ++ src/include/libpq/libpq-be.h | 15 +- src/include/libpq/libpq.h | 3 + src/include/pg_config.h.in | 3 + src/include/pg_config_manual.h | 5 +- src/interfaces/libpq/Makefile | 5 + src/interfaces/libpq/fe-connect.c | 4 + src/interfaces/libpq/fe-secure-nss.c | 1112 +++++++++++++ src/interfaces/libpq/fe-secure.c | 5 +- src/interfaces/libpq/libpq-fe.h | 13 +- src/interfaces/libpq/libpq-int.h | 20 +- src/tools/msvc/Mkvcbuild.pm | 22 +- src/tools/msvc/Solution.pm | 20 + 23 files changed, 3150 insertions(+), 22 deletions(-) create mode 100644 src/backend/libpq/be-secure-nss.c create mode 100644 src/include/common/pg_nss.h create mode 100644 src/interfaces/libpq/fe-secure-nss.c diff --git a/configure b/configure index ce9ea36999..143726c79c 100755 --- a/configure +++ b/configure @@ -654,6 +654,8 @@ UUID_LIBS LDAP_LIBS_BE LDAP_LIBS_FE with_ssl +NSPR_CONFIG +NSS_CONFIG PTHREAD_CFLAGS PTHREAD_LIBS PTHREAD_CC @@ -1570,7 +1572,7 @@ Optional Packages: use system time zone data in DIR --without-zlib do not use Zlib --with-gnu-ld assume the C compiler uses GNU ld [default=no] - --with-ssl=LIB use LIB for SSL/TLS support (openssl) + --with-ssl=LIB use LIB for SSL/TLS support (openssl, nss) --with-openssl obsolete spelling of --with-ssl=openssl Some influential environment variables: @@ -12462,8 +12464,274 @@ done $as_echo "#define USE_OPENSSL 1" >>confdefs.h +elif test "$with_ssl" = nss ; then + # TODO: fallback in case nss-config/nspr-config aren't found. + if test -z "$NSS_CONFIG"; then + for ac_prog in nss-config +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_NSS_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $NSS_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +NSS_CONFIG=$ac_cv_path_NSS_CONFIG +if test -n "$NSS_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5 +$as_echo "$NSS_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$NSS_CONFIG" && break +done + +else + # Report the value of NSS_CONFIG in configure's output in all cases. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5 +$as_echo_n "checking for NSS_CONFIG... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5 +$as_echo "$NSS_CONFIG" >&6; } +fi + + if test -z "$NSPR_CONFIG"; then + for ac_prog in nspr-config +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_NSPR_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $NSPR_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG +if test -n "$NSPR_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5 +$as_echo "$NSPR_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$NSPR_CONFIG" && break +done + +else + # Report the value of NSPR_CONFIG in configure's output in all cases. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5 +$as_echo_n "checking for NSPR_CONFIG... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5 +$as_echo "$NSPR_CONFIG" >&6; } +fi + + if test -n "$NSS_CONFIG"; then + NSS_LIBS=`$NSS_CONFIG --libs` + NSS_CFLAGS=`$NSS_CONFIG --cflags` + fi + if test -n "$NSPR_CONFIG"; then + NSPR_LIBS=`$NSPR_CONFIG --libs` + NSPR_CFLAGS=`$NSPR_CONFIG --cflags` + fi + + LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS" + CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_InitContext in -lnss3" >&5 +$as_echo_n "checking for NSS_InitContext in -lnss3... " >&6; } +if ${ac_cv_lib_nss3_NSS_InitContext+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lnss3 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char NSS_InitContext (); +int +main () +{ +return NSS_InitContext (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_nss3_NSS_InitContext=yes +else + ac_cv_lib_nss3_NSS_InitContext=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_NSS_InitContext" >&5 +$as_echo "$ac_cv_lib_nss3_NSS_InitContext" >&6; } +if test "x$ac_cv_lib_nss3_NSS_InitContext" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBNSS3 1 +_ACEOF + + LIBS="-lnss3 $LIBS" + +else + as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5 +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5 +$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; } +if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lnspr4 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char PR_GetDefaultIOMethods (); +int +main () +{ +return PR_GetDefaultIOMethods (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes +else + ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5 +$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; } +if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBNSPR4 1 +_ACEOF + + LIBS="-lnspr4 $LIBS" + +else + as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5 +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5 +$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; } +if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lssl3 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char SSL_GetImplementedCiphers (); +int +main () +{ +return SSL_GetImplementedCiphers (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes +else + ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5 +$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; } +if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBSSL3 1 +_ACEOF + + LIBS="-lssl3 $LIBS" + +else + as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5 +fi + + +$as_echo "#define USE_NSS 1" >>confdefs.h + elif test "$with_ssl" != no ; then - as_fn_error $? "--with-ssl must specify openssl" "$LINENO" 5 + as_fn_error $? "--with-ssl must specify one of openssl or nss" "$LINENO" 5 fi @@ -13369,6 +13637,23 @@ else fi +elif test "$with_ssl" = nss ; then + ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default" +if test "x$ac_cv_header_nss_ssl_h" = xyes; then : + +else + as_fn_error $? "header file 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 is required for NSS" "$LINENO" 5 +fi + + fi if test "$with_pam" = yes ; then @@ -18131,6 +18416,9 @@ $as_echo_n "checking which random number source to use... " >&6; } if test x"$with_ssl" = x"openssl" ; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5 $as_echo "OpenSSL" >&6; } +elif test x"$with_ssl" = x"nss" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5 +$as_echo "NSS" >&6; } elif test x"$PORTNAME" = x"win32" ; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5 $as_echo "Windows native" >&6; } @@ -18160,7 +18448,7 @@ fi if test x"$ac_cv_file__dev_urandom" = x"no" ; then as_fn_error $? " no source of strong random numbers was found -PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5 +PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers." "$LINENO" 5 fi fi diff --git a/configure.ac b/configure.ac index 07da84d401..59657a6010 100644 --- a/configure.ac +++ b/configure.ac @@ -1201,7 +1201,7 @@ fi # # There is currently only one supported SSL/TLS library: OpenSSL. # -PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl)]) +PGAC_ARG_REQ(with, ssl, [LIB], [use LIB for SSL/TLS support (openssl, nss)]) if test x"$with_ssl" = x"" ; then with_ssl=no fi @@ -1235,8 +1235,28 @@ if test "$with_ssl" = openssl ; then # function was removed. AC_CHECK_FUNCS([CRYPTO_lock]) AC_DEFINE([USE_OPENSSL], 1, [Define to 1 if you have OpenSSL support.]) +elif test "$with_ssl" = nss ; then + # TODO: fallback in case nss-config/nspr-config aren't found. + PGAC_PATH_PROGS(NSS_CONFIG, nss-config) + PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config) + if test -n "$NSS_CONFIG"; then + NSS_LIBS=`$NSS_CONFIG --libs` + NSS_CFLAGS=`$NSS_CONFIG --cflags` + fi + if test -n "$NSPR_CONFIG"; then + NSPR_LIBS=`$NSPR_CONFIG --libs` + NSPR_CFLAGS=`$NSPR_CONFIG --cflags` + fi + + LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS" + CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS" + + AC_CHECK_LIB(nss3, NSS_InitContext, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])]) + AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])]) + AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])]) + AC_DEFINE([USE_NSS], 1, [Define to 1 if you have NSS support,]) elif test "$with_ssl" != no ; then - AC_MSG_ERROR([--with-ssl must specify openssl]) + AC_MSG_ERROR([--with-ssl must specify one of openssl or nss]) fi AC_SUBST(with_ssl) @@ -1414,6 +1434,9 @@ fi if test "$with_ssl" = openssl ; then AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file is required for OpenSSL])]) AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file is required for OpenSSL])]) +elif test "$with_ssl" = nss ; then + AC_CHECK_HEADER(nss/ssl.h, [], [AC_MSG_ERROR([header file is required for NSS])]) + AC_CHECK_HEADER(nss/nss.h, [], [AC_MSG_ERROR([header file is required for NSS])]) fi if test "$with_pam" = yes ; then @@ -2170,6 +2193,8 @@ fi AC_MSG_CHECKING([which random number source to use]) if test x"$with_ssl" = x"openssl" ; then AC_MSG_RESULT([OpenSSL]) +elif test x"$with_ssl" = x"nss" ; then + AC_MSG_RESULT([NSS]) elif test x"$PORTNAME" = x"win32" ; then AC_MSG_RESULT([Windows native]) else @@ -2179,7 +2204,7 @@ else if test x"$ac_cv_file__dev_urandom" = x"no" ; then AC_MSG_ERROR([ no source of strong random numbers was found -PostgreSQL can use OpenSSL, native Windows API or /dev/urandom as a source of random numbers.]) +PostgreSQL can use OpenSSL, NSS, native Windows API or /dev/urandom as a source of random numbers.]) fi fi diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index b09dce63f5..fbbe778217 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -8928,7 +8928,7 @@ DO $d$ END; $d$; ERROR: invalid option "password" -HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size +HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size, batch_size CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')" PL/pgSQL function inline_code_block line 3 at EXECUTE -- If we add a password for our user mapping instead, we should get a different diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 8d1d16b0fc..045d2c2f6b 100644 --- a/src/backend/libpq/Makefile +++ b/src/backend/libpq/Makefile @@ -30,6 +30,10 @@ OBJS = \ ifeq ($(with_ssl),openssl) OBJS += be-secure-openssl.o +else +ifeq ($(with_ssl),nss) +OBJS += be-secure-nss.o +endif endif ifeq ($(with_gssapi),yes) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 545635f41a..09ad2ad063 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2849,7 +2849,14 @@ CheckCertAuth(Port *port) { int status_check_usermap = STATUS_ERROR; +#if defined(USE_OPENSSL) Assert(port->ssl); +#elif defined(USE_NSS) + /* TODO: should we rename pr_fd to ssl, to keep consistency? */ + Assert(port->pr_fd); +#else + Assert(false); +#endif /* Make sure we have received a username in the certificate */ if (port->peer_cn == NULL || diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c new file mode 100644 index 0000000000..876e54be51 --- /dev/null +++ b/src/backend/libpq/be-secure-nss.c @@ -0,0 +1,1402 @@ +/*------------------------------------------------------------------------- + * + * be-secure-nss.c + * functions for supporting NSS as a TLS backend + * + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/libpq/be-secure-nss.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include + +/* + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Ensure that the colliding definitions match, else throw an error. In case + * NSPR has removed the definition for some reason, make sure to put ours + * back again. + */ +#if defined(BITS_PER_BYTE) +#if BITS_PER_BYTE != pg_BITS_PER_BYTE +#error "incompatible byte widths between NSPR and postgres" +#endif +#else +#define BITS_PER_BYTE pg_BITS_PER_BYTE +#endif +#undef pg_BITS_PER_BYTE + +#include "common/pg_nss.h" +#include "lib/stringinfo.h" +#include "libpq/libpq.h" +#include "nodes/pg_list.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/guc.h" +#include "utils/memutils.h" + + +/* default init hook can be overridden by a shared library */ +static void default_nss_tls_init(bool isServerStart); +nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init; + +static PRDescIdentity pr_id; + +static PRIOMethods pr_iomethods; +static NSSInitContext *nss_context = NULL; +static SSLVersionRange desired_sslver; + +static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg); +static SECStatus pg_SSLShutdownFunc(void *private_data, void *nss_data); +static bool dummy_ssl_passwd_cb_called = false; +static bool ssl_is_server_start; + +/* + * PR_ImportTCPSocket() is a private API, but very widely used, as it's the + * only way to make NSS use an already set up POSIX file descriptor rather + * than opening one itself. To quote the NSS documentation: + * + * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's + * implementation changes. In practice, this is unlikely to happen because + * NSPR's implementation has been stable for years and because of NSPR's + * strong commitment to backward compatibility." + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket + * + * The function is declared in , but as it is a header marked + * private we declare it here rather than including it. + */ +NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int); + +/* NSS IO layer callback overrides */ +static PRInt32 pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout); +static PRInt32 pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout); +static PRStatus pg_ssl_close(PRFileDesc *fd); +/* Utility functions */ +static PRFileDesc *init_iolayer(Port *port); +static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name); + +static char *pg_SSLerrmessage(PRErrorCode errcode); +static char *ssl_protocol_version_to_string(int v); +static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc *fd, + PRBool checksig, PRBool isServer); +static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc *fd); +static char *dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg); + +/* ------------------------------------------------------------ */ +/* Public interface */ +/* ------------------------------------------------------------ */ + +/* + * be_tls_init + * Initialize the nss TLS library in the postmaster + * + * The majority of the setup needs to happen in be_tls_open_server since the + * NSPR initialization must happen after the forking of the backend. We could + * potentially move some parts in under !isServerStart, but so far this is the + * separation chosen. + */ +int +be_tls_init(bool isServerStart) +{ + SECStatus status; + SSLVersionRange supported_sslver; + + status = SSL_ConfigServerSessionIDCacheWithOpt(0, 0, NULL, 1, 0, 0, PR_FALSE); + if (status != SECSuccess) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("unable to connect to TLS connection cache: %s", + pg_SSLerrmessage(PR_GetError())))); + return -1; + } + + if (!ssl_database || strlen(ssl_database) == 0) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("no certificate database specified"))); + return -1; + } + + /* + * We check for the desired TLS version range here, even though we cannot + * set it until be_open_server such that we can be compatible with how the + * OpenSSL backend reports errors for incompatible range configurations. + * Set either the default supported TLS version range, or the configured + * range from ssl_min_protocol_version and ssl_max_protocol version. In + * case the user hasn't defined the maximum allowed version we fall back + * to the highest version TLS that the library supports. + */ + if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("unable to get default protocol support from NSS"))); + return -1; + } + + /* + * Set the fallback versions for the TLS protocol version range to a + * combination of our minimal requirement and the library maximum. Error + * messages should be kept identical to those in be-secure-openssl.c to + * make translations easier. + */ + desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0; + desired_sslver.max = supported_sslver.max; + + if (ssl_min_protocol_version) + { + int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version, + "ssl_min_protocol_version"); + + if (ver == -1) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("\"%s\" setting \"%s\" not supported by this build", + "ssl_min_protocol_version", + GetConfigOption("ssl_min_protocol_version", + false, false)))); + return -1; + } + + if (ver > 0) + desired_sslver.min = ver; + } + + if (ssl_max_protocol_version) + { + int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version, + "ssl_max_protocol_version"); + + if (ver == -1) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("\"%s\" setting \"%s\" not supported by this build", + "ssl_max_protocol_version", + GetConfigOption("ssl_max_protocol_version", + false, false)))); + return -1; + } + if (ver > 0) + desired_sslver.max = ver; + + if (ver < desired_sslver.min) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("could not set SSL protocol version range"), + errdetail("\"%s\" cannot be higher than \"%s\"", + "ssl_min_protocol_version", + "ssl_max_protocol_version"))); + return -1; + } + } + + /* + * Set the passphrase callback which will be used both to obtain the + * passphrase from the user, as well as by NSS to obtain the phrase + * repeatedly. + */ + ssl_is_server_start = isServerStart; + (*nss_tls_init_hook) (isServerStart); + + return 0; +} + +int +be_tls_open_server(Port *port) +{ + SECStatus status; + PRFileDesc *model; + PRFileDesc *pr_fd; + PRFileDesc *layer; + CERTCertificate *server_cert; + SECKEYPrivateKey *private_key; + CERTSignedCrl *crl; + SECItem crlname; + char *cert_database; + NSSInitParameters params; + + /* + * The NSPR documentation states that runtime initialization via PR_Init + * is no longer required, as the first caller into NSPR will perform the + * initialization implicitly. The documentation doesn't however clarify + * from which version this is holds true, so let's perform the potentially + * superfluous initialization anyways to avoid crashing on older versions + * of NSPR, as there is no difference in overhead. The NSS documentation + * still states that PR_Init must be called in some way (implicitly or + * explicitly). + * + * The below parameters are what the implicit initialization would've done + * for us, and should work even for older versions where it might not be + * done automatically. The last parameter, maxPTDs, is set to various + * values in other codebases, but has been unused since NSPR 2.1 which was + * released sometime in 1998. In current versions of NSPR all parameters + * are ignored. + */ + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ ); + + /* + * The certificate path (configdir) must contain a valid NSS database. If + * the certificate path isn't a valid directory, NSS will fall back on the + * system certificate database. If the certificate path is a directory but + * is empty then the initialization will fail. On the client side this can + * be allowed for any sslmode but the verify-xxx ones. + * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side + * we won't allow this to fail however, as we require the certificate and + * key to exist. + * + * The original design of NSS was for a single application to use a single + * copy of it, initialized with NSS_Initialize() which isn't returning any + * handle with which to refer to NSS. NSS initialization and shutdown are + * global for the application, so a shutdown in another NSS enabled + * library would cause NSS to be stopped for libpq as well. The fix has + * been to introduce NSS_InitContext which returns a context handle to + * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS + * 3.12, but the use of it is not very well documented. + * https://bugzilla.redhat.com/show_bug.cgi?id=738456 + * + * The InitParameters struct passed can be used to override internal + * values in NSS, but the usage is not documented at all. When using + * NSS_Init initializations, the values are instead set via PK11_Configure + * calls so the PK11_Configure documentation can be used to glean some + * details on these. + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs + */ + memset(¶ms, '\0', sizeof(params)); + params.length = sizeof(params); + + if (!ssl_database || strlen(ssl_database) == 0) + ereport(FATAL, + (errmsg("no certificate database specified"))); + + cert_database = psprintf("sql:%s", ssl_database); + nss_context = NSS_InitContext(cert_database, "", "", "", + ¶ms, + NSS_INIT_READONLY | NSS_INIT_PK11RELOAD); + pfree(cert_database); + + if (!nss_context) + ereport(FATAL, + (errmsg("unable to read certificate database \"%s\": %s", + ssl_database, pg_SSLerrmessage(PR_GetError())))); + + /* + * Import the already opened socket as we don't want to use NSPR functions + * for opening the network socket due to how the PostgreSQL protocol works + * with TLS connections. This function is not part of the NSPR public API, + * see the comment at the top of the file for the rationale of still using + * it. + */ + pr_fd = PR_ImportTCPSocket(port->sock); + if (!pr_fd) + { + ereport(COMMERROR, + (errmsg("unable to connect to socket"))); + return -1; + } + + /* + * Most of the documentation available, and implementations of, NSS/NSPR + * use the PR_NewTCPSocket() function here, which has the drawback that it + * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which + * copes with IPv6 as well. + * + * We use a model filedescriptor here which is a construct in NSPR/NSS in + * order to create a configuration template for sockets which can then be + * applied to new sockets created. This makes more sense in a server which + * accepts multiple connections and want to perform the boilerplate just + * once, but it does provide a nice abstraction here as well in that we + * can error out early without having performed any operation on the real + * socket. + */ + model = PR_OpenTCPSocket(port->laddr.addr.ss_family); + if (!model) + { + ereport(COMMERROR, + (errmsg("unable to open socket"))); + return -1; + } + + /* + * Convert the NSPR socket to an SSL socket. Ensuring the success of this + * operation is critical as NSS SSL_* functions may return SECSuccess on + * the socket even though SSL hasn't been enabled, which introduce a risk + * of silent downgrades. + */ + model = SSL_ImportFD(NULL, model); + if (!model) + { + ereport(COMMERROR, + (errmsg("unable to enable TLS on socket"))); + return -1; + } + + /* + * Configure basic settings for the connection over the SSL socket in + * order to set it up as a server. + */ + if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess) + { + ereport(COMMERROR, + (errmsg("unable to configure TLS connection"))); + return -1; + } + + if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess || + SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess) + { + ereport(COMMERROR, + (errmsg("unable to configure TLS connection as server"))); + return -1; + } + + /* + * SSLv2 is disabled by default, and SSLv3 will be excluded from the range + * of allowed protocols further down. Since we really don't want these to + * ever be enabled, let's use belts and suspenders and explicitly turn + * them off as well. + */ + SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE); + SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE); + +#ifdef SSL_CBC_RANDOM_IV + + /* + * Enable protection against the BEAST attack in case the NSS server has + * support for that. While SSLv3 is disabled, we may still allow TLSv1 + * which is affected. The option isn't documented as an SSL option, but as + * an NSS environment variable. + */ + SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE); +#endif + + /* + * Configure the allowed ciphers. If there are no user preferred suites, + * set the domestic policy. + * + * Historically there were different cipher policies based on export (and + * import) restrictions: Domestic, Export and France. These are since long + * removed with all ciphers being enabled by default. Due to backwards + * compatibility, the old API is still used even though all three policies + * now do the same thing. + * + * If SSLCipherSuites define a policy of the user, we set that rather than + * enabling all ciphers via NSS_SetDomesticPolicy. + * + * TODO: while this code works, the set of ciphers which can be set and + * still end up with a working socket is woefully underdocumented for + * anything more recent than SSLv3 (the code for TLS actually calls ssl3 + * functions under the hood for SSL_CipherPrefSet), so it's unclear if + * this is helpful or not. Using the policies works, but may be too + * coarsely grained. + * + * Another TODO: The SSL_ImplementedCiphers table returned with calling + * SSL_GetImplementedCiphers is sorted in server preference order. Sorting + * SSLCipherSuites according to the order of the ciphers therein could be + * a way to implement ssl_prefer_server_ciphers - if we at all want to use + * cipher selection for NSS like how we do it for OpenSSL that is. + */ + + /* + * If no ciphers are specified, enable them all. + */ + if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0) + { + status = NSS_SetDomesticPolicy(); + if (status != SECSuccess) + { + ereport(COMMERROR, + (errmsg("unable to set cipher policy: %s", + pg_SSLerrmessage(PR_GetError())))); + return -1; + } + } + else + { + char *ciphers, + *c; + + char *sep = ":;, "; + PRUint16 ciphercode; + const PRUint16 *nss_ciphers; + + /* + * If the user has specified a set of preferred cipher suites we start + * by turning off all the existing suites to avoid the risk of down- + * grades to a weaker cipher than expected. + */ + nss_ciphers = SSL_GetImplementedCiphers(); + for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++) + SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE); + + ciphers = pstrdup(SSLCipherSuites); + + for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep)) + { + ciphercode = pg_find_cipher(c); + if (ciphercode != INVALID_CIPHER) + { + status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE); + if (status != SECSuccess) + { + ereport(COMMERROR, + (errmsg("invalid cipher-suite specified: %s", c))); + return -1; + } + } + } + + pfree(ciphers); + } + + if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess) + { + ereport(COMMERROR, + (errmsg("unable to set requested SSL protocol version range"))); + return -1; + } + + /* + * Set up the custom IO layer. + */ + layer = init_iolayer(port); + if (!layer) + return -1; + + if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS) + { + PR_Close(layer); + ereport(COMMERROR, + (errmsg("unable to push IO layer"))); + return -1; + } + + server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port); + if (!server_cert) + { + if (dummy_ssl_passwd_cb_called) + { + ereport(COMMERROR, + (errmsg("unable to load certificate for \"%s\": %s", + ssl_cert_file, pg_SSLerrmessage(PR_GetError())), + errhint("The certificate requires a password."))); + return -1; + } + else + { + ereport(COMMERROR, + (errmsg("unable to find certificate for \"%s\": %s", + ssl_cert_file, pg_SSLerrmessage(PR_GetError())))); + return -1; + } + } + + private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port); + if (!private_key) + { + if (dummy_ssl_passwd_cb_called) + { + ereport(COMMERROR, + (errmsg("unable to load private key for \"%s\": %s", + ssl_cert_file, pg_SSLerrmessage(PR_GetError())), + errhint("The private key requires a password."))); + return -1; + } + else + { + ereport(COMMERROR, + (errmsg("unable to find private key for \"%s\": %s", + ssl_cert_file, pg_SSLerrmessage(PR_GetError())))); + return -1; + } + } + + /* + * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to + * contain the CRL nickname for the current server certificate in the NSS + * certificate database. The main difference from the OpenSSL backend is + * that NSS will use the CRL regardless, but being able to make sure the + * CRL is loaded seems like a good feature. + */ + if (ssl_crl_file[0]) + { + SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject); + crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE); + if (!crl) + { + ereport(COMMERROR, + (errmsg("specified CRL not found in database"))); + return -1; + } + SEC_DestroyCrl(crl); + } + + /* + * Finally we must configure the socket for being a server by setting the + * certificate and key. The NULL parameter is an SSLExtraServerCertData + * pointer with the final parameter being the size of the extra server + * cert data structure pointed to. This is typically only used for + * credential delegation. + */ + status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0); + if (status != SECSuccess) + { + ereport(COMMERROR, + (errmsg("unable to configure server for TLS server connections: %s", + pg_SSLerrmessage(PR_GetError())))); + return -1; + } + + ssl_loaded_verify_locations = true; + + /* + * At this point, we no longer have use for the certificate and private + * key as they have been copied into the context by NSS. Destroy our + * copies explicitly to clean out the memory as best we can. + */ + CERT_DestroyCertificate(server_cert); + SECKEY_DestroyPrivateKey(private_key); + + /* Set up certificate authentication callback */ + status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port); + if (status != SECSuccess) + { + ereport(COMMERROR, + (errmsg("unable to install authcert hook: %s", + pg_SSLerrmessage(PR_GetError())))); + return -1; + } + SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port); + SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE); + SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE); + + port->pr_fd = SSL_ImportFD(model, pr_fd); + if (!port->pr_fd) + { + ereport(COMMERROR, + (errmsg("unable to initialize"))); + return -1; + } + + PR_Close(model); + + /* + * Force a handshake on the next I/O request, the second parameter means + * that we are a server, PR_FALSE would indicate being a client. NSPR + * requires us to call SSL_ResetHandshake since we imported an already + * established socket. + */ + status = SSL_ResetHandshake(port->pr_fd, PR_TRUE); + if (status != SECSuccess) + { + ereport(COMMERROR, + (errmsg("unable to initiate handshake: %s", + pg_SSLerrmessage(PR_GetError())))); + return -1; + } + status = SSL_ForceHandshake(port->pr_fd); + if (status != SECSuccess) + { + ereport(COMMERROR, + (errmsg("unable to handshake: %s", + pg_SSLerrmessage(PR_GetError())))); + return -1; + } + + port->ssl_in_use = true; + + /* Register out shutdown callback */ + NSS_RegisterShutdown(pg_SSLShutdownFunc, port); + + return 0; +} + +ssize_t +be_tls_read(Port *port, void *ptr, size_t len, int *waitfor) +{ + ssize_t n_read; + PRErrorCode err; + + n_read = PR_Read(port->pr_fd, ptr, len); + + if (n_read < 0) + { + err = PR_GetError(); + + if (err == PR_WOULD_BLOCK_ERROR) + { + *waitfor = WL_SOCKET_READABLE; + errno = EWOULDBLOCK; + } + else + errno = ECONNRESET; + } + + return n_read; +} + +ssize_t +be_tls_write(Port *port, void *ptr, size_t len, int *waitfor) +{ + ssize_t n_write; + PRErrorCode err; + PRIntn flags = 0; + + /* + * The flags parameter to PR_Send is no longer used and is, according to + * the documentation, required to be zero. + */ + n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT); + + if (n_write < 0) + { + err = PR_GetError(); + + if (err == PR_WOULD_BLOCK_ERROR) + { + *waitfor = WL_SOCKET_WRITEABLE; + errno = EWOULDBLOCK; + } + else + errno = ECONNRESET; + } + + return n_write; +} + +void +be_tls_close(Port *port) +{ + if (!port) + return; + + if (port->peer_cn) + { + SSL_InvalidateSession(port->pr_fd); + pfree(port->peer_cn); + port->peer_cn = NULL; + } + + PR_Close(port->pr_fd); + port->pr_fd = NULL; + port->ssl_in_use = false; + + if (nss_context) + { + NSS_ShutdownContext(nss_context); + nss_context = NULL; + } +} + +void +be_tls_destroy(void) +{ + /* + * It reads a bit odd to clear a session cache when we are destroying the + * context altogether, but if the session cache isn't cleared before + * shutting down the context it will fail with SEC_ERROR_BUSY. + */ + SSL_ClearSessionCache(); +} + +int +be_tls_get_cipher_bits(Port *port) +{ + SECStatus status; + SSLChannelInfo channel; + SSLCipherSuiteInfo suite; + + status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel)); + if (status != SECSuccess) + goto error; + + status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite)); + if (status != SECSuccess) + goto error; + + return suite.effectiveKeyBits; + +error: + ereport(WARNING, + (errmsg("unable to extract TLS session information: %s", + pg_SSLerrmessage(PR_GetError())))); + return 0; +} + +/* + * be_tls_get_compression + * + * NSS disabled support for TLS compression in version 3.33 and removed the + * code in a subsequent release. The API for retrieving information about + * compression as well as enabling it is kept for backwards compatibility, but + * we don't need to consult it since it was only available for SSLv3 which we + * don't support. + * + * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587 + */ +bool +be_tls_get_compression(Port *port) +{ + return false; +} + +/* + * be_tls_get_version + * + * Returns the protocol version used for the current connection, or NULL in + * case of errors. + */ +const char * +be_tls_get_version(Port *port) +{ + SECStatus status; + SSLChannelInfo channel; + + status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel)); + if (status != SECSuccess) + { + ereport(WARNING, + (errmsg("unable to extract TLS session information: %s", + pg_SSLerrmessage(PR_GetError())))); + return NULL; + } + + return ssl_protocol_version_to_string(channel.protocolVersion); +} + +const char * +be_tls_get_cipher(Port *port) +{ + SECStatus status; + SSLChannelInfo channel; + SSLCipherSuiteInfo suite; + + status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel)); + if (status != SECSuccess) + goto error; + + status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite)); + if (status != SECSuccess) + goto error; + + return suite.cipherSuiteName; + +error: + ereport(WARNING, + (errmsg("unable to extract TLS session information: %s", + pg_SSLerrmessage(PR_GetError())))); + return NULL; +} + +void +be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len) +{ + CERTCertificate *certificate; + + certificate = SSL_PeerCertificate(port->pr_fd); + if (certificate) + strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len); + else + ptr[0] = '\0'; +} + +void +be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len) +{ + CERTCertificate *certificate; + + certificate = SSL_PeerCertificate(port->pr_fd); + if (certificate) + strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len); + else + ptr[0] = '\0'; +} + +void +be_tls_get_peer_serial(Port *port, char *ptr, size_t len) +{ + CERTCertificate *certificate; + + certificate = SSL_PeerCertificate(port->pr_fd); + if (certificate) + snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber))); + else + ptr[0] = '\0'; +} + +char * +be_tls_get_certificate_hash(Port *port, size_t *len) +{ + CERTCertificate *certificate; + SECOidTag signature_alg; + SECOidTag digest_alg; + int digest_len; + const NSSSignatureAlgorithms *candidate; + SECStatus status; + PLArenaPool *arena = NULL; + SECItem digest; + char *ret; + + *len = 0; + certificate = SSL_LocalCertificate(port->pr_fd); + if (!certificate) + return NULL; + + signature_alg = SECOID_GetAlgorithmTag(&certificate->signature); + + candidate = NSS_SCRAMDigestAlgorithm; + while (candidate->signature) + { + if (signature_alg == candidate->signature) + { + digest_alg = candidate->hash; + digest_len = candidate->len; + break; + } + + candidate++; + } + + if (!candidate->signature) + elog(ERROR, "could not find digest for OID '%s'", + SECOID_FindOIDTagDescription(signature_alg)); + + arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len); + digest.len = digest_len; + + status = PK11_HashBuf(digest_alg, digest.data, + certificate->derCert.data, + certificate->derCert.len); + + if (status != SECSuccess) + { + PORT_FreeArena(arena, PR_TRUE); + return NULL; + } + + ret = palloc(digest.len); + memcpy(ret, digest.data, digest.len); + *len = digest_len; + PORT_FreeArena(arena, PR_TRUE); + + return ret; +} + +/* ------------------------------------------------------------ */ +/* Internal functions */ +/* ------------------------------------------------------------ */ + +/* + * default_nss_tls_init + * + * The default TLS init hook function which users can override for installing + * their own passphrase callbacks and similar actions. In case no callback has + * been configured, or the callback isn't reload capable during a server + * reload, the dummy callback will be installed. + * + * The private data for the callback is set differently depending on how it's + * invoked. For calls which may invoke the callback deeper in the callstack + * the private data is set with SSL_SetPKCS11PinArg. When the call is directly + * invoking the callback, like PK11_FindCertFromNickname, then the private + * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is + * thus not required but good practice. + * + * NSS doesn't provide a default callback like OpenSSL does, but a callback is + * required to be set. The password callback can be installed at any time, but + * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc. + */ +static void +default_nss_tls_init(bool isServerStart) +{ + /* + * No user-defined callback has been configured, install the dummy call- + * back since we must set something. + */ + if (!ssl_passphrase_command[0]) + PK11_SetPasswordFunc(dummy_ssl_passphrase_cb); + else + { + /* + * There is a user-defined callback, set it unless we are in a restart + * and cannot handle restarts due to an interactive callback. + */ + if (isServerStart) + PK11_SetPasswordFunc(external_ssl_passphrase_cb); + else + { + if (ssl_passphrase_command_supports_reload) + PK11_SetPasswordFunc(external_ssl_passphrase_cb); + else + PK11_SetPasswordFunc(dummy_ssl_passphrase_cb); + } + } +} + +/* + * external_ssl_passphrase_cb + * + * Runs the callback configured by ssl_passphrase_command and returns the + * captured password back to NSS. + */ +static char * +external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + /* + * NSS use a hardcoded 256 byte buffer for reading the password so set the + * same limit for our callback buffer. + */ + char buf[256]; + int len; + char *password = NULL; + char *prompt; + + /* + * Since there is no password callback in NSS when the server starts up, + * it makes little sense to create an interactive callback. Thus, if this + * is a retry attempt then give up immediately. + */ + if (retry) + return NULL; + + /* + * Construct the same prompt that NSS uses internally even though it is + * unlikely to serve much purpose, but we must set a prompt so we might as + * well do it right. + */ + prompt = psprintf("Enter Password or Pin for \"%s\":", + PK11_GetTokenName(slot)); + + len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf)); + pfree(prompt); + + if (!len) + return NULL; + + /* + * At least one byte with password content was returned, and NSS requires + * that we return it allocated in NSS controlled memory. If we fail to + * allocate then abort without passing back NULL and bubble up the error + * on the PG side. + */ + password = (char *) PR_Malloc(len + 1); + if (!password) + { + explicit_bzero(buf, sizeof(buf)); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + } + strlcpy(password, buf, sizeof(password)); + explicit_bzero(buf, sizeof(buf)); + + return password; +} + +/* + * dummy_ssl_passphrase_cb + * + * Return unsuccessful if we are asked to provide the passphrase for a cert or + * key, without having a passphrase callback installed. + */ +static char * +dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + dummy_ssl_passwd_cb_called = true; + return NULL; +} + +static SECStatus +pg_bad_cert_handler(void *arg, PRFileDesc *fd) +{ + Port *port = (Port *) arg; + + port->peer_cert_valid = false; + return SECFailure; +} + +/* + * raw_subject_common_name + * + * Returns the Subject Common Name for the given certificate as a raw char + * buffer (that is, without any form of escaping for unprintable characters or + * embedded nulls), with the length of the buffer returned in the len param. + * The buffer is allocated in the TopMemoryContext and is given a NULL + * terminator so that callers are safe to call strlen() on it. + * + * This is used instead of CERT_GetCommonName(), which always performs quoting + * and/or escaping. NSS doesn't appear to give us a way to easily unescape the + * result, and we need to store the raw CN into port->peer_cn for compatibility + * with the OpenSSL implementation. + */ +static char * +raw_subject_common_name(CERTCertificate *cert, unsigned int *len) +{ + CERTName subject = cert->subject; + CERTRDN **rdn; + + for (rdn = subject.rdns; *rdn; rdn++) + { + CERTAVA **ava; + + for (ava = (*rdn)->avas; *ava; ava++) + { + SECItem *buf; + char *cn; + + if (CERT_GetAVATag(*ava) != SEC_OID_AVA_COMMON_NAME) + continue; + + /* Found a CN, ecode and copy it into a newly allocated buffer */ + buf = CERT_DecodeAVAValue(&(*ava)->value); + if (!buf) + { + /* + * This failure case is difficult to test. (Since this code + * runs after certificate authentication has otherwise + * succeeded, you'd need to convince a CA implementation to + * sign a corrupted certificate in order to get here.) + * + * Follow the behavior of CERT_GetCommonName() in this case and + * simply return NULL, as if a Common Name had not been found. + */ + goto fail; + } + + cn = MemoryContextAlloc(TopMemoryContext, buf->len + 1); + memcpy(cn, buf->data, buf->len); + cn[buf->len] = '\0'; + + *len = buf->len; + + SECITEM_FreeItem(buf, PR_TRUE); + return cn; + } + } + +fail: + /* Not found. */ + *len = 0; + return NULL; +} + +/* + * pg_cert_auth_handler + * + * Callback for validation of incoming certificates. Returning SECFailure will + * cause NSS to terminate the connection immediately. + */ +static SECStatus +pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer) +{ + SECStatus status; + Port *port = (Port *) arg; + CERTCertificate *cert; + char *peer_cn; + unsigned int len; + + status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE); + if (status != SECSuccess) + return status; + + port->peer_cn = NULL; + port->peer_cert_valid = false; + + cert = SSL_PeerCertificate(port->pr_fd); + if (!cert) + { + /* Shouldn't be possible; why did we get a client cert callback? */ + Assert(cert != NULL); + + PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0); + return SECFailure; + } + + peer_cn = raw_subject_common_name(cert, &len); + if (!peer_cn) + { + /* No Common Name, but the certificate otherwise checks out. */ + port->peer_cert_valid = true; + + status = SECSuccess; + goto cleanup; + } + + /* + * Reject embedded NULLs in certificate common name to prevent attacks like + * CVE-2009-4034. + */ + if (len != strlen(peer_cn)) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL certificate's common name contains embedded null"))); + + pfree(peer_cn); + PR_SetError(SEC_ERROR_CERT_NOT_VALID, 0); + + status = SECFailure; + goto cleanup; + } + + port->peer_cn = peer_cn; + port->peer_cert_valid = true; + + status = SECSuccess; + +cleanup: + CERT_DestroyCertificate(cert); + return status; +} + +static PRInt32 +pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRRecvFN read_fn; + PRInt32 n_read; + + read_fn = fd->lower->methods->recv; + n_read = read_fn(fd->lower, buf, amount, flags, timeout); + + return n_read; +} + +static PRInt32 +pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRSendFN send_fn; + PRInt32 n_write; + + send_fn = fd->lower->methods->send; + n_write = send_fn(fd->lower, buf, amount, flags, timeout); + + return n_write; +} + +static PRStatus +pg_ssl_close(PRFileDesc *fd) +{ + /* + * Disconnect our private Port from the fd before closing out the stack. + * (Debug builds of NSPR will assert if we do not.) + */ + fd->secret = NULL; + return PR_GetDefaultIOMethods()->close(fd); +} + +static PRFileDesc * +init_iolayer(Port *port) +{ + const PRIOMethods *default_methods; + PRFileDesc *layer; + + /* + * Start by initializing our layer with all the default methods so that we + * can selectively override the ones we want while still ensuring that we + * have a complete layer specification. + */ + default_methods = PR_GetDefaultIOMethods(); + memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods)); + + pr_iomethods.recv = pg_ssl_read; + pr_iomethods.send = pg_ssl_write; + pr_iomethods.close = pg_ssl_close; + + /* + * Each IO layer must be identified by a unique name, where uniqueness is + * per connection. Each connection in a postgres cluster can generate the + * identity from the same string as they will create their IO layers on + * different sockets. Only one layer per socket can have the same name. + */ + pr_id = PR_GetUniqueIdentity("PostgreSQL Server"); + if (pr_id == PR_INVALID_IO_LAYER) + { + ereport(ERROR, + (errmsg("out of memory when setting up TLS connection"))); + return NULL; + } + + /* + * Create the actual IO layer as a stub such that it can be pushed onto + * the layer stack. The step via a stub is required as we define custom + * callbacks. + */ + layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods); + if (!layer) + { + ereport(ERROR, + (errmsg("unable to create NSS I/O layer"))); + return NULL; + } + + /* Store the Port as private data available in callbacks */ + layer->secret = (void *) port; + + return layer; +} + +static char * +ssl_protocol_version_to_string(int v) +{ + switch (v) + { + /* SSL v2 and v3 are not supported */ + case SSL_LIBRARY_VERSION_2: + case SSL_LIBRARY_VERSION_3_0: + Assert(false); + break; + + case SSL_LIBRARY_VERSION_TLS_1_0: + return pstrdup("TLSv1.0"); + case SSL_LIBRARY_VERSION_TLS_1_1: + return pstrdup("TLSv1.1"); + case SSL_LIBRARY_VERSION_TLS_1_2: + return pstrdup("TLSv1.2"); + case SSL_LIBRARY_VERSION_TLS_1_3: + return pstrdup("TLSv1.3"); + } + + return pstrdup("unknown"); +} + + +/* + * ssl_protocol_version_to_nss + * Translate PostgreSQL TLS version to NSS version + * + * Returns zero in case the requested TLS version is undefined (PG_ANY) and + * should be set by the caller, or -1 on failure. + */ +static uint16 +ssl_protocol_version_to_nss(int v, const char *guc_name) +{ + switch (v) + { + /* + * There is no SSL_LIBRARY_ macro defined in NSS with the value + * zero, so we use this to signal the caller that the highest + * useful version should be set on the connection. + */ + case PG_TLS_ANY: + return 0; + + /* + * No guard is required here as there are no versions of NSS + * without support for TLS1. + */ + case PG_TLS1_VERSION: + return SSL_LIBRARY_VERSION_TLS_1_0; + case PG_TLS1_1_VERSION: +#ifdef SSL_LIBRARY_VERSION_TLS_1_1 + return SSL_LIBRARY_VERSION_TLS_1_1; +#else + break; +#endif + case PG_TLS1_2_VERSION: +#ifdef SSL_LIBRARY_VERSION_TLS_1_2 + return SSL_LIBRARY_VERSION_TLS_1_2; +#else + break; +#endif + case PG_TLS1_3_VERSION: +#ifdef SSL_LIBRARY_VERSION_TLS_1_3 + return SSL_LIBRARY_VERSION_TLS_1_3; +#else + break; +#endif + default: + break; + } + + return -1; +} + +/* + * pg_SSLerrmessage + * Create and return a human readable error message given + * the specified error code + * + * PR_ErrorToName only converts the enum identifier of the error to string, + * but that can be quite useful for debugging (and in case PR_ErrorToString is + * unable to render a message then we at least have something). + */ +static char * +pg_SSLerrmessage(PRErrorCode errcode) +{ + return psprintf("%s (%s)", + PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT), + PR_ErrorToName(errcode)); +} + +/* + * pg_SSLShutdownFunc + * Callback for NSS shutdown + * + * If NSS is terminated from the outside when the connection is still in use + * we must treat this as potentially hostile and immediately close to avoid + * leaking the connection in any way. Once this is called, NSS will shutdown + * regardless so we may as well clean up the best we can. Returning SECFailure + * will cause the NSS shutdown to return with an error, but it will shutdown + * nevertheless. nss_data is reserved for future use and is always NULL. + */ +static SECStatus +pg_SSLShutdownFunc(void *private_data, void *nss_data) +{ + Port *port = (Port *) private_data; + + if (!port || !port->ssl_in_use) + return SECSuccess; + + /* + * There is a connection still open, close it and signal to whatever that + * called the shutdown that it was erroneous. + */ + be_tls_close(port); + be_tls_destroy(); + + return SECFailure; +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 4cf139a223..5e0c27c134 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload; #ifdef USE_SSL bool ssl_loaded_verify_locations = false; #endif +#ifdef USE_NSS +char *ssl_database; +#endif /* GUC variable controlling SSL cipher list */ char *SSLCipherSuites = NULL; diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 20bf1461ce..9a04c093d5 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1041,7 +1041,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) ereport(elevel, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("hostssl record cannot match because SSL is not supported by this build"), - errhint("Compile with --with-ssl=openssl to use SSL connections."), + errhint("Compile with --with-ssl to use SSL connections."), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); *err_msg = "hostssl record cannot match because SSL is not supported by this build"; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index eafdb1118e..b023b31c46 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -4308,7 +4308,11 @@ static struct config_string ConfigureNamesString[] = }, &ssl_library, #ifdef USE_SSL +#if defined(USE_OPENSSL) "OpenSSL", +#elif defined(USE_NSS) + "NSS", +#endif #else "", #endif @@ -4366,6 +4370,18 @@ static struct config_string ConfigureNamesString[] = check_canonical_path, assign_pgstat_temp_directory, NULL }, +#ifdef USE_NSS + { + {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the NSS certificate database."), + NULL + }, + &ssl_database, + "", + NULL, NULL, NULL + }, +#endif + { {"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY, gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."), @@ -4394,8 +4410,10 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &SSLCipherSuites, -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) "HIGH:MEDIUM:+3DES:!aNULL", +#elif defined (USE_NSS) + "", #else "none", #endif diff --git a/src/common/Makefile b/src/common/Makefile index 5422579a6a..be8b2583b2 100644 --- a/src/common/Makefile +++ b/src/common/Makefile @@ -85,12 +85,17 @@ OBJS_COMMON += \ protocol_openssl.o \ cryptohash_openssl.o else +ifeq ($(with_ssl),nss) +OBJS_COMMON += \ + cryptohash_nss.o +else OBJS_COMMON += \ cryptohash.o \ md5.o \ sha1.o \ sha2.o endif +endif # A few files are currently only built for frontend, not server # (Mkvcbuild.pm has a copy of this list, too). logging.c is excluded diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h new file mode 100644 index 0000000000..008fee85f1 --- /dev/null +++ b/src/include/common/pg_nss.h @@ -0,0 +1,175 @@ +/*------------------------------------------------------------------------- + * + * pg_nss.h + * Support for NSS as a TLS backend + * + * These definitions are used by both frontend and backend code. + * + * Copyright (c) 2020, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/include/common/pg_nss.h + * + *------------------------------------------------------------------------- + */ +#ifndef PG_NSS_H +#define PG_NSS_H + +#ifdef USE_NSS +#define NO_NSPR_10_SUPPORT +#include +#include +#include + +PRUint16 pg_find_cipher(char *name); + +typedef struct +{ + SECOidTag signature; + SECOidTag hash; + int len; +} NSSSignatureAlgorithms; + +typedef struct +{ + const char *name; + PRUint16 number; +} NSSCiphers; + +#define INVALID_CIPHER 0xFFFF + +static const NSSSignatureAlgorithms NSS_SCRAMDigestAlgorithm[] = { + + {SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH}, + {SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH}, + {SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH}, + {SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH}, + {SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, SEC_OID_SHA256, SHA256_LENGTH}, + + {SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, SEC_OID_SHA224, SHA224_LENGTH}, + {SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, SEC_OID_SHA224, SHA224_LENGTH}, + {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, SEC_OID_SHA224, SHA224_LENGTH}, + + {SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, SEC_OID_SHA256, SHA256_LENGTH}, + {SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, SEC_OID_SHA256, SHA256_LENGTH}, + {SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, SEC_OID_SHA256, SHA256_LENGTH}, + + {SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, SEC_OID_SHA384, SHA384_LENGTH}, + {SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, SEC_OID_SHA384, SHA384_LENGTH}, + + {SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, SEC_OID_SHA512, SHA512_LENGTH}, + {SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, SEC_OID_SHA512, SHA512_LENGTH}, + + {0, 0} +}; + +/* + * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h + * in order to provide a human readable version of the ciphers. It would be + * nice to not have to have this, but NSS doesn't provide any API addressing + * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less? + */ +static const NSSCiphers NSS_CipherList[] = { + + {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL}, + + {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5}, + {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA}, + {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5}, + {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA}, + {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA}, + {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA}, + {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA}, + + {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA}, + {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA}, + {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA}, + {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA}, + + {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA}, + {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA}, + {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA}, + {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA}, + + {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5}, + {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA}, + {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA}, + + {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA}, + {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA}, + {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA}, + {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA}, + {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA}, + {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA}, + + {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA}, + {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA}, + {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA}, + {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA}, + {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA}, + {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA}, + {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256}, + {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256}, + {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256}, + + {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256}, + {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA}, + {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA}, + {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA}, + {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA}, + {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA}, + {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA}, + + {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA}, + {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256}, + {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256}, + {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256}, + + {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA}, + {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA}, + {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA}, + {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA}, + {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA}, + {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA}, + + {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA}, + + {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256}, + {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384}, + {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256}, + {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384}, + {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256}, + {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384}, + + {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256}, + {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384}, + {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256}, + {NULL, 0} +}; + +/* + * pg_find_cipher + * Translate an NSS ciphername to the cipher code + * + * Searches the configured ciphers for the corresponding cipher code to the + * name. Search is performed case insensitive. + */ +PRUint16 +pg_find_cipher(char *name) +{ + const NSSCiphers *cipher_list = NSS_CipherList; + + while (cipher_list->name) + { + if (pg_strcasecmp(cipher_list->name, name) == 0) + return cipher_list->number; + + cipher_list++; + } + + return 0xFFFF; +} + +#endif /* USE_NSS */ + +#endif /* PG_NSS_H */ diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 66a8673d93..ea373a0ae0 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -193,13 +193,18 @@ typedef struct Port bool peer_cert_valid; /* - * OpenSSL structures. (Keep these last so that the locations of other - * fields are the same whether or not you build with OpenSSL.) + * SSL backend specific structures. (Keep these last so that the locations + * of other fields are the same whether or not you build with SSL + * enabled.) */ #ifdef USE_OPENSSL SSL *ssl; X509 *peer; #endif + +#ifdef USE_NSS + void *pr_fd; +#endif } Port; #ifdef USE_SSL @@ -283,7 +288,7 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len); * This is not supported with old versions of OpenSSL that don't have * the X509_get_signature_nid() function. */ -#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID) +#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)) #define HAVE_BE_TLS_GET_CERTIFICATE_HASH extern char *be_tls_get_certificate_hash(Port *port, size_t *len); #endif @@ -293,6 +298,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len); typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart); extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook; #endif +#ifdef USE_NSS +typedef void (*nss_tls_init_hook_type) (bool isServerStart); +extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook; +#endif #endif /* USE_SSL */ diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index a55898c85a..d2860b852f 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload; #ifdef USE_SSL extern bool ssl_loaded_verify_locations; #endif +#ifdef USE_NSS +extern char *ssl_database; +#endif extern int secure_initialize(bool isServerStart); extern bool secure_loaded_verify_locations(void); diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 55cab4d2bf..8856a33590 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -905,6 +905,9 @@ /* Define to 1 to build with PAM support. (--with-pam) */ #undef USE_PAM +/* Define to build with NSS support (--with-ssl=nss) */ +#undef USE_NSS + /* Define to 1 to use software CRC-32C implementation (slicing-by-8). */ #undef USE_SLICING_BY_8_CRC32C diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h index d27c8601fa..7861b94290 100644 --- a/src/include/pg_config_manual.h +++ b/src/include/pg_config_manual.h @@ -176,10 +176,9 @@ /* * USE_SSL code should be compiled only when compiling with an SSL - * implementation. (Currently, only OpenSSL is supported, but we might add - * more implementations in the future.) + * implementation. */ -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) || defined(USE_NSS) #define USE_SSL #endif diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index f74677eaf9..e10d8bef42 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -56,6 +56,11 @@ OBJS += \ fe-secure-openssl.o endif +ifeq ($(with_ssl), nss) +OBJS += \ + fe-secure-nss.o +endif + ifeq ($(with_gssapi),yes) OBJS += \ fe-gssapi-common.o \ diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 8ca0583aa9..1eeeff54a8 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */ offsetof(struct pg_conn, target_session_attrs)}, + {"cert_database", NULL, NULL, NULL, + "CertificateDatabase", "", 64, + offsetof(struct pg_conn, cert_database)}, + /* Terminating entry --- MUST BE LAST */ {NULL, NULL, NULL, NULL, NULL, NULL, 0} diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c new file mode 100644 index 0000000000..dca0572e67 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-nss.c @@ -0,0 +1,1112 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-nss.c + * functions for supporting NSS as a TLS backend for frontend libpq + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-secure-nss.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "libpq-fe.h" +#include "fe-auth.h" +#include "fe-secure-common.h" +#include "libpq-int.h" +#include "common/pg_nss.h" + +/* + * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef + * our version to avoid compiler warnings on redefinition. + */ +#define pg_BITS_PER_BYTE BITS_PER_BYTE +#undef BITS_PER_BYTE + +/* + * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with + * colliding definitions from ours, causing a much expected compiler error. + * Remove backwards compatibility with ancient NSPR versions to avoid this. + */ +#define NO_NSPR_10_SUPPORT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 , but as it is a header marked + * private we declare it here rather than including it. + */ +NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int); + +static SECMODModule * ca_trust = NULL; +static NSSInitContext * nss_context = NULL; + +/* + * This logic exist in NSS as well, but it's only available for when there is + * a database to open, and not only using the system trust store. Thus, we + * need to keep our own copy. + */ +#if defined(WIN32) +static const char *ca_trust_name = "nssckbi.dll"; +#elif defined(__darwin__) +static const char *ca_trust_name = "libnssckbi.dylib"; +#else +static const char *ca_trust_name = "libnssckbi.so"; +#endif + +static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL; + +/* ------------------------------------------------------------ */ +/* Procedures common to all secure sessions */ +/* ------------------------------------------------------------ */ + +/* + * pgtls_init_library + * + * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init + * being the closest match there is. PR_Init is however already documented to + * not be required so simply making this a noop seems like the best option. + */ +void +pgtls_init_library(bool do_ssl, int do_crypto) +{ + /* noop */ +} + +int +pgtls_init(PGconn *conn) +{ + conn->ssl_in_use = false; + conn->has_password = false; + + return 0; +} + +void +pgtls_close(PGconn *conn) +{ + if (nss_context) + { + NSS_ShutdownContext(nss_context); + nss_context = NULL; + } +} + +PostgresPollingStatusType +pgtls_open_client(PGconn *conn) +{ + SECStatus status; + PRFileDesc *pr_fd; + PRFileDesc *model; + NSSInitParameters params; + SSLVersionRange desired_range; + + /* + * The NSPR documentation states that runtime initialization via PR_Init + * is no longer required, as the first caller into NSPR will perform the + * initialization implicitly. See be-secure-nss.c for further discussion + * on PR_Init. + */ + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + + /* + * NSS initialization and the use of contexts is further discussed in + * be-secure-nss.c + */ + memset(¶ms, 0, sizeof(params)); + params.length = sizeof(params); + + if (conn->cert_database && strlen(conn->cert_database) > 0) + { + char *cert_database_path = psprintf("sql:%s", conn->cert_database); + + nss_context = NSS_InitContext(cert_database_path, "", "", "", + ¶ms, + NSS_INIT_READONLY | NSS_INIT_PK11RELOAD); + pfree(cert_database_path); + + if (!nss_context) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to open certificate database \"%s\": %s"), + conn->cert_database, + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + } + else + { + nss_context = NSS_InitContext("", "", "", "", ¶ms, + NSS_INIT_READONLY | NSS_INIT_NOCERTDB | + NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN | + NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD); + if (!nss_context) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to create certificate database: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + } + + /* + * Configure cipher policy. + */ + status = NSS_SetDomesticPolicy(); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to configure cipher policy: %s"), + pg_SSLerrmessage(PR_GetError())); + + return PGRES_POLLING_FAILED; + } + + /* + * If we don't have a certificate database, the system trust store is the + * fallback we can use. If we fail to initialize that as well, we can + * still attempt a connection as long as the sslmode isn't verify*. + */ + if (!conn->cert_database && conn->sslmode[0] == 'v') + { + status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\""); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), + ca_trust_name, + pg_SSLerrmessage(PR_GetError())); + + return PGRES_POLLING_FAILED; + } + } + + + PK11_SetPasswordFunc(PQssl_passwd_cb); + + /* + * Import the already opened socket as we don't want to use NSPR functions + * for opening the network socket due to how the PostgreSQL protocol works + * with TLS connections. This function is not part of the NSPR public API, + * see the comment at the top of the file for the rationale of still using + * it. + */ + pr_fd = PR_ImportTCPSocket(conn->sock); + if (!pr_fd) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to attach to socket: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + /* + * Most of the documentation available, and implementations of, NSS/NSPR + * use the PR_NewTCPSocket() function here, which has the drawback that it + * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which + * copes with IPv6 as well. + */ + model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family)); + if (!model) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to enable TLS: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + /* Disable old protocol versions (SSLv2 and SSLv3) */ + SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE); + SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE); + SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE); + +#ifdef SSL_CBC_RANDOM_IV + + /* + * Enable protection against the BEAST attack in case the NSS library has + * support for that. While SSLv3 is disabled, we may still allow TLSv1 + * which is affected. The option isn't documented as an SSL option, but as + * an NSS environment variable. + */ + SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE); +#endif + + /* Set us up as a TLS client for the handshake */ + SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE); + + /* + * When setting the available protocols, we either use the user defined + * configuration values, and if missing we accept whatever is the highest + * version supported by the library as the max and only limit the range in + * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum + * for Stream protocols, rather than datagram. + */ + SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range); + desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0; + + if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0) + { + int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version); + + if (ssl_min_ver == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"), + conn->ssl_min_protocol_version); + return -1; + } + + desired_range.min = ssl_min_ver; + } + + if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0) + { + int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version); + + if (ssl_max_ver == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"), + conn->ssl_max_protocol_version); + return -1; + } + + desired_range.max = ssl_max_ver; + } + + if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to set allowed SSL protocol version range: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + /* + * Set up callback for verifying server certificates, as well as for how + * to handle failed verifications. + */ + SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn); + SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn); + + /* + * Convert the NSPR socket to an SSL socket. Ensuring the success of this + * operation is critical as NSS SSL_* functions may return SECSuccess on + * the socket even though SSL hasn't been enabled, which introduce a risk + * of silent downgrades. + */ + conn->pr_fd = SSL_ImportFD(model, pr_fd); + if (!conn->pr_fd) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to configure client for TLS: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + /* + * The model can now we closed as we've applied the settings of the model + * onto the real socket. From hereon we should only use conn->pr_fd. + */ + PR_Close(model); + + /* Set the private data to be passed to the password callback */ + SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn); + + /* + * If a CRL file has been specified, verify if it exists in the database + * but don't fail in case it doesn't. + */ + if (conn->sslcrl && strlen(conn->sslcrl) > 0) + { + /* XXX: Implement me.. */ + } + + status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to initiate handshake: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + /* + * Set callback for client authentication when requested by the server. + */ + SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn); + + /* + * Specify which hostname we are expecting to talk to. This is required, + * albeit mostly applies to when opening a connection to a traditional + * http server it seems. + */ + SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host); + + do + { + status = SSL_ForceHandshake(conn->pr_fd); + } + while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR); + + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + conn->ssl_in_use = true; + return PGRES_POLLING_OK; +} + +ssize_t +pgtls_read(PGconn *conn, void *ptr, size_t len) +{ + PRInt32 nread; + PRErrorCode status; + int read_errno = 0; + + /* + * PR_Recv blocks until there is data to read or the timeout expires. We + * don't want to sit blocked here, so the timeout is turned off by using + * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned + * for closed connections, while -1 indicates an error within the ongoing + * connection. + */ + nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT); + + if (nread == 0) + { + read_errno = ECONNRESET; + nread = -1; + } + else if (nread == -1) + { + status = PR_GetError(); + + switch (status) + { + case PR_IO_TIMEOUT_ERROR: + /* No data available yet. */ + nread = 0; + break; + + case PR_WOULD_BLOCK_ERROR: + read_errno = EWOULDBLOCK; + break; + + /* + * The error cases for PR_Recv are not documented, but can be + * reverse engineered from _MD_unix_map_default_error() in the + * NSPR code, defined in pr/src/md/unix/unix_errors.c. + */ + case PR_NETWORK_UNREACHABLE_ERROR: + case PR_CONNECT_RESET_ERROR: + read_errno = ECONNRESET; + break; + + default: + break; + } + + if (nread == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("TLS read error: %s"), + pg_SSLerrmessage(status)); + } + } + + SOCK_ERRNO_SET(read_errno); + return (ssize_t) nread; +} + +/* + * pgtls_read_pending + * Check for the existence of data to be read. + * + * This is part of the PostgreSQL TLS backend API. + */ +bool +pgtls_read_pending(PGconn *conn) +{ + unsigned char c; + int n; + + /* + * PR_Recv peeks into the stream with the timeount turned off, to see if + * there is another byte to read off the wire. There is an NSS function + * SSL_DataPending() which might seem like a better fit, but it will only + * check already encrypted data in the SSL buffer, not still unencrypted + * data, thus it doesn't guarantee that a subsequent call to + * PR_Read/PR_Recv won't block. + */ + n = PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT); + return (n > 0); +} + +ssize_t +pgtls_write(PGconn *conn, const void *ptr, size_t len) +{ + PRInt32 n; + PRErrorCode status; + int write_errno = 0; + + n = PR_Write(conn->pr_fd, ptr, len); + + if (n < 0) + { + status = PR_GetError(); + + switch (status) + { + case PR_WOULD_BLOCK_ERROR: +#ifdef EAGAIN + write_errno = EAGAIN; +#else + write_errno = EINTR; +#endif + break; + + default: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("TLS write error: %s"), + pg_SSLerrmessage(status)); + write_errno = ECONNRESET; + break; + } + } + + SOCK_ERRNO_SET(write_errno); + return (ssize_t) n; +} + +char * +pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len) +{ + CERTCertificate *server_cert; + SECOidTag signature_alg; + SECOidTag digest_alg; + int digest_len; + const NSSSignatureAlgorithms *candidate; + PLArenaPool *arena; + SECItem digest; + char *ret; + SECStatus status; + + *len = 0; + + server_cert = SSL_PeerCertificate(conn->pr_fd); + if (!server_cert) + return NULL; + + signature_alg = SECOID_GetAlgorithmTag(&server_cert->signature); + + candidate = NSS_SCRAMDigestAlgorithm; + while (candidate->signature) + { + if (signature_alg == candidate->signature) + { + digest_alg = candidate->hash; + digest_len = candidate->len; + break; + } + + candidate++; + } + + if (!candidate->signature) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not find digest for OID '%s'\n"), + SECOID_FindOIDTagDescription(signature_alg)); + return NULL; + } + + arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + digest.data = PORT_ArenaZAlloc(arena, sizeof(unsigned char) * digest_len); + digest.len = digest_len; + + status = PK11_HashBuf(digest_alg, digest.data, server_cert->derCert.data, + server_cert->derCert.len); + + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to generate peer certificate digest: %s"), + pg_SSLerrmessage(PR_GetError())); + PORT_FreeArena(arena, PR_TRUE); + return NULL; + } + + ret = pg_malloc(digest.len); + memcpy(ret, digest.data, digest.len); + *len = digest_len; + PORT_FreeArena(arena, PR_TRUE); + + return ret; +} + +/* + * Verify that the server certificate matches the hostname we connected to. + * + * The certificate's Common Name and Subject Alternative Names are considered. + */ +int +pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, + int *names_examined, + char **first_name) +{ + char *server_hostname = NULL; + CERTCertificate *server_cert = NULL; + SECStatus status = SECSuccess; + SECItem altname_item; + PLArenaPool *arena = NULL; + CERTGeneralName *san_list; + CERTGeneralName *cn; + + server_hostname = SSL_RevealURL(conn->pr_fd); + if (!server_hostname || server_hostname[0] == '\0') + goto done; + + /* + * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName + * verification. + */ + server_cert = SSL_PeerCertificate(conn->pr_fd); + status = CERT_VerifyCertName(server_cert, server_hostname); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to verify server hostname: %s"), + pg_SSLerrmessage(PR_GetError())); + goto done; + } + + status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME, + &altname_item); + if (status == SECSuccess) + { + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) + { + status = SECFailure; + goto done; + } + san_list = CERT_DecodeAltNameExtension(arena, &altname_item); + if (!san_list) + { + status = SECFailure; + goto done; + } + + for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn)) + { + char *alt_name; + int rv; + char tmp[512]; + + status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp), + (char *) cn->name.other.data, + cn->name.other.len); + + if (status != SECSuccess) + goto done; + + rv = pq_verify_peer_name_matches_certificate_name(conn, tmp, + strlen(tmp), + &alt_name); + if (alt_name) + { + if (!*first_name) + *first_name = alt_name; + else + free(alt_name); + } + + if (rv == 1) + status = SECSuccess; + else + { + status = SECFailure; + break; + } + } + } + else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND) + status = SECSuccess; + else + status = SECSuccess; + +done: + /* san_list will be freed by freeing the arena it was allocated in */ + if (arena) + PORT_FreeArena(arena, PR_TRUE); + PR_Free(server_hostname); + + if (status == SECSuccess) + return 1; + + return 0; +} + +/* ------------------------------------------------------------ */ +/* PostgreSQL specific TLS support functions */ +/* ------------------------------------------------------------ */ + +static const char * +pg_SSLerrmessage(PRErrorCode errcode) +{ + const char *error; + + /* + * Try to get the user friendly error description, and if that fails try + * to fall back on the name of the PRErrorCode. + */ + error = PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT); + if (!error) + error = PR_ErrorToName(errcode); + if (error) + return error; + + return "unknown TLS error"; +} + +static SECStatus +pg_load_nss_module(SECMODModule * *module, const char *library, const char *name) +{ + SECMODModule *mod; + char *modulespec; + + modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name); + + /* + * Attempt to load the specified module. The second parameter is "parent" + * which should always be NULL for application code. The third parameter + * defines if loading should recurse which is only applicable when loading + * a module from within another module. This hierarchy would have to be + * defined in the modulespec, and since we don't support anything but + * directly addressed modules we should pass PR_FALSE. + */ + mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE); + pfree(modulespec); + + if (mod && mod->loaded) + { + *module = mod; + return SECSuccess; + } + + SECMOD_DestroyModule(mod); + return SECFailure; +} + +/* ------------------------------------------------------------ */ +/* NSS Callbacks */ +/* ------------------------------------------------------------ */ + +/* + * pg_cert_auth_handler + * Callback for authenticating server certificate + * + * This is pretty much the same procedure as the SSL_AuthCertificate function + * provided by NSS, with the difference being server hostname validation. With + * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full + * flavor of our sslmodes, so we need our own implementation. + */ +static SECStatus +pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer) +{ + SECStatus status; + PGconn *conn = (PGconn *) arg; + CERTCertificate *server_cert; + void *pin; + + Assert(!isServer); + + pin = SSL_RevealPinArg(conn->pr_fd); + server_cert = SSL_PeerCertificate(conn->pr_fd); + + status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), + server_cert, + checksig, + certificateUsageSSLServer, + pin, + NULL); + + /* + * If we've already failed validation then there is no point in also + * performing the hostname check for verify-full. + */ + if (status == SECSuccess) + { + if (!pq_verify_peer_name_matches_certificate(conn)) + status = SECFailure; + } + else + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to verify certificate: %s"), + pg_SSLerrmessage(PR_GetError())); + } + + return status; +} + +/* + * pg_client_auth_handler + * Callback for client certificate validation + * + * The client auth callback is not on by default in NSS, so we need to invoke + * it ourselves to ensure we can do cert authentication. A TODO is to support + * running without a specified sslcert parameter. By retrieving all the certs + * via nickname from the cert database and see if we find one which apply with + * NSS_CmpCertChainWCANames() and PK11_FindKeyByAnyCert() we could support + * just running with a ssl database specified. + * + * For now, we use the default client certificate validation which requires a + * defined nickname to identify the cert in the database. + */ +static SECStatus +pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames, + CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey) +{ + PGconn *conn = (PGconn *) arg; + + return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey); +} + +/* + * pg_bad_cert_handler + * Callback for failed certificate validation + * + * The TLS handshake will call this function iff the server certificate failed + * validation. Depending on the sslmode, we allow the connection anyways. + */ +static SECStatus +pg_bad_cert_handler(void *arg, PRFileDesc * fd) +{ + PGconn *conn = (PGconn *) arg; + PRErrorCode err; + + /* + * This really shouldn't happen, as we've set the PGconn object as our + * callback data, and at the callsite we know it will be populated. That + * being said, the NSS code itself performs this check even when it should + * not be required so let's use the same belts with our suspenders. + */ + if (!arg) + return SECFailure; + + /* + * For sslmodes other than verify-full and verify-ca we don't perform peer + * validation, so return immediately. sslmode require with a database + * specified which contains a CA certificate will work like verify-ca to + * be compatible with the OpenSSL implementation. + */ + if (strcmp(conn->sslmode, "require") == 0) + { + if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn)) + return SECFailure; + } + if (conn->sslmode[0] == 'v') + return SECFailure; + + err = PORT_GetError(); + + /* + * TODO: these are relevant error codes that can occur in certificate + * validation, figure out which we don't want for require/prefer etc. + */ + switch (err) + { + case SEC_ERROR_INVALID_AVA: + case SEC_ERROR_INVALID_TIME: + case SEC_ERROR_BAD_SIGNATURE: + case SEC_ERROR_EXPIRED_CERTIFICATE: + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_UNTRUSTED_ISSUER: + case SEC_ERROR_UNTRUSTED_CERT: + case SEC_ERROR_CERT_VALID: + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + case SEC_ERROR_CRL_EXPIRED: + case SEC_ERROR_CRL_BAD_SIGNATURE: + case SEC_ERROR_EXTENSION_VALUE_INVALID: + case SEC_ERROR_CA_CERT_INVALID: + case SEC_ERROR_CERT_USAGES_INVALID: + case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: + return SECSuccess; + break; + default: + return SECFailure; + break; + } + + /* Unreachable */ + return SECSuccess; +} + +/* ------------------------------------------------------------ */ +/* SSL information functions */ +/* ------------------------------------------------------------ */ + +/* + * PQgetssl + * + * Return NULL as this is legacy and defined to always be equal to calling + * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning + * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color + * of said bikeshed hasn't yet been determined. + */ +void * +PQgetssl(PGconn *conn) +{ + return NULL; +} + +void * +PQsslStruct(PGconn *conn, const char *struct_name) +{ + if (!conn) + return NULL; + + /* + * Return the underlying PRFileDesc which can be used to access + * information on the connection details. There is no SSL context per se. + */ + if (strcmp(struct_name, "NSS") == 0) + return conn->pr_fd; + return NULL; +} + +const char *const * +PQsslAttributeNames(PGconn *conn) +{ + static const char *const result[] = { + "library", + "cipher", + "protocol", + "key_bits", + "compression", + NULL + }; + + return result; +} + +const char * +PQsslAttribute(PGconn *conn, const char *attribute_name) +{ + SECStatus status; + SSLChannelInfo channel; + SSLCipherSuiteInfo suite; + + if (!conn || !conn->pr_fd) + return NULL; + + if (strcmp(attribute_name, "library") == 0) + return "NSS"; + + status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel)); + if (status != SECSuccess) + return NULL; + + status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite)); + if (status != SECSuccess) + return NULL; + + if (strcmp(attribute_name, "cipher") == 0) + return suite.cipherSuiteName; + + if (strcmp(attribute_name, "key_bits") == 0) + { + static char key_bits_str[8]; + + snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits); + return key_bits_str; + } + + if (strcmp(attribute_name, "protocol") == 0) + { + switch (channel.protocolVersion) + { +#ifdef SSL_LIBRARY_VERSION_TLS_1_3 + case SSL_LIBRARY_VERSION_TLS_1_3: + return "TLSv1.3"; +#endif +#ifdef SSL_LIBRARY_VERSION_TLS_1_2 + case SSL_LIBRARY_VERSION_TLS_1_2: + return "TLSv1.2"; +#endif +#ifdef SSL_LIBRARY_VERSION_TLS_1_1 + case SSL_LIBRARY_VERSION_TLS_1_1: + return "TLSv1.1"; +#endif + case SSL_LIBRARY_VERSION_TLS_1_0: + return "TLSv1.0"; + default: + return "unknown"; + } + } + + /* + * NSS disabled support for compression in version 3.33, and it was only + * available for SSLv3 at that point anyways, so we can safely return off + * here without checking. + */ + if (strcmp(attribute_name, "compression") == 0) + return "off"; + + return NULL; +} + +static int +ssl_protocol_version_to_nss(const char *protocol) +{ + if (pg_strcasecmp("TLSv1", protocol) == 0) + return SSL_LIBRARY_VERSION_TLS_1_0; + +#ifdef SSL_LIBRARY_VERSION_TLS_1_1 + if (pg_strcasecmp("TLSv1.1", protocol) == 0) + return SSL_LIBRARY_VERSION_TLS_1_1; +#endif + +#ifdef SSL_LIBRARY_VERSION_TLS_1_2 + if (pg_strcasecmp("TLSv1.2", protocol) == 0) + return SSL_LIBRARY_VERSION_TLS_1_2; +#endif + +#ifdef SSL_LIBRARY_VERSION_TLS_1_3 + if (pg_strcasecmp("TLSv1.3", protocol) == 0) + return SSL_LIBRARY_VERSION_TLS_1_3; +#endif + + return -1; +} + +/* + * cert_database_has_CA + * + * Returns true in case there is a CA certificate in the database connected + * to in the conn object, else false. This function only checks for the + * presence of a CA certificate, not it's validity and/or usefulness. + */ +static bool +cert_database_has_CA(PGconn *conn) +{ + CERTCertList *certificates; + bool hasCA; + + /* + * If the certificate database has a password we must provide it, since + * this API doesn't invoke the standard password callback. + */ + if (conn->has_password) + certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn)); + else + certificates = PK11_ListCerts(PK11CertListCA, NULL); + hasCA = !CERT_LIST_EMPTY(certificates); + CERT_DestroyCertList(certificates); + + return hasCA; +} + +PQsslKeyPassHook_nss_type +PQgetSSLKeyPassHook_nss(void) +{ + return PQsslKeyPassHook; +} + +void +PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook) +{ + PQsslKeyPassHook = hook; +} + +/* + * Supply a password to decrypt a client certificate. + * + * This must match NSS type PK11PasswordFunc. + */ +static char * +PQssl_passwd_cb(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + PGconn *conn = (PGconn *) arg; + + conn->has_password = true; + + if (PQsslKeyPassHook) + return PQsslKeyPassHook(slot, (PRBool) retry, arg); + else + return PQdefaultSSLKeyPassHook_nss(slot, retry, arg); +} + +/* + * The default password handler callback. + */ +char * +PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg) +{ + PGconn *conn = (PGconn *) arg; + + /* + * If the password didn't work the first time there is no point in + * retrying as it hasn't changed. + */ + if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0) + return PORT_Strdup(conn->sslpassword); + + return NULL; +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index 00b87bdc96..44385ee391 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -421,6 +421,9 @@ PQsslAttributeNames(PGconn *conn) return result; } +#endif /* USE_SSL */ + +#ifndef USE_OPENSSL PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void) @@ -439,7 +442,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn) { return 0; } -#endif /* USE_SSL */ +#endif /* USE_OPENSSL */ /* Dummy version of GSSAPI information functions, when built without GSS support */ #ifndef ENABLE_GSS diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index c266ad5b13..aba32e6635 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -619,12 +619,23 @@ extern int pg_valid_server_encoding_id(int encoding); /* === in fe-secure-openssl.c === */ -/* Support for overriding sslpassword handling with a callback. */ +/* Support for overriding sslpassword handling with a callback */ typedef int (*PQsslKeyPassHook_OpenSSL_type) (char *buf, int size, PGconn *conn); extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void); extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook); extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn); +/* == in fe-secure-nss.c === */ +typedef struct PK11SlotInfoStr PK11SlotInfo; +typedef int PRIntn; +typedef PRIntn PRBool; + +/* Support for overriding sslpassword handling with a callback */ +typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo *slot, PRBool retry, void *arg); +extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void); +extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook); +extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo *slot, PRBool retry, void *arg); + #ifdef __cplusplus } #endif diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 4db498369c..a95450fbba 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -362,6 +362,7 @@ struct pg_conn char *sslpassword; /* client key file password */ char *sslrootcert; /* root certificate filename */ char *sslcrl; /* certificate revocation list filename */ + char *cert_database; /* NSS certificate/key database */ char *requirepeer; /* required peer credentials for local sockets */ char *gssencmode; /* GSS mode (require,prefer,disable) */ char *krbsrvname; /* Kerberos service name */ @@ -485,6 +486,23 @@ struct pg_conn * OpenSSL version changes */ #endif #endif /* USE_OPENSSL */ + +#ifdef USE_NSS + void *pr_fd; /* NSPR file descriptor for the connection */ + + /* + * Track whether the NSS database has a password set or not. There is no + * API function for retrieving password status of a database, but we need + * to know since some NSS API calls require the password passed in (they + * don't call the callback themselves). To track, we simply flip this to + * true in case NSS invoked the password callback - as that will only + * happen in case there is a password. The reason for tracking this is + * that there are calls which require a password parameter, but doesn't + * use the callbacks provided, so we must call the callback on behalf of + * these. + */ + bool has_password; +#endif /* USE_NSS */ #endif /* USE_SSL */ #ifdef ENABLE_GSS @@ -759,7 +777,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len); * This is not supported with old versions of OpenSSL that don't have * the X509_get_signature_nid() function. */ -#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID) +#if defined(USE_NSS) || (defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)) #define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len); #endif diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 90328db04e..ff01acf3f6 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -195,12 +195,19 @@ sub mkvcbuild $postgres->FullExportDLL('postgres.lib'); # The OBJS scraper doesn't know about ifdefs, so remove appropriate files - # if building without OpenSSL. - if (!$solution->{options}->{openssl}) + # if building without various options. + if (!$solution->{options}->{openssl} && !$solution->{options}->{nss}) { $postgres->RemoveFile('src/backend/libpq/be-secure-common.c'); + } + if (!$solution->{options}->{openssl}) + { $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{nss}) + { + $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c'); + } if (!$solution->{options}->{gss}) { $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); @@ -258,12 +265,19 @@ sub mkvcbuild $libpq->AddReference($libpgcommon, $libpgport); # The OBJS scraper doesn't know about ifdefs, so remove appropriate files - # if building without OpenSSL. - if (!$solution->{options}->{openssl}) + # if building without various options + if (!$solution->{options}->{openssl} && !$solution->{options}->{nss}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c'); + } + if (!$solution->{options}->{openssl}) + { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); } + if (!$solution->{options}->{nss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c'); + } if (!$solution->{options}->{gss}) { $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm index 1c0c92fcd2..5148708969 100644 --- a/src/tools/msvc/Solution.pm +++ b/src/tools/msvc/Solution.pm @@ -488,6 +488,7 @@ sub GenerateFiles USE_LLVM => undef, USE_NAMED_POSIX_SEMAPHORES => undef, USE_OPENSSL => undef, + USE_NSS => undef, USE_PAM => undef, USE_SLICING_BY_8_CRC32C => undef, USE_SSE42_CRC32C => undef, @@ -540,6 +541,10 @@ sub GenerateFiles $define{HAVE_OPENSSL_INIT_SSL} = 1; } } + if ($self->{options}->{nss}) + { + $define{USE_NSS} = 1; + } $self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1); $self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0); @@ -1005,6 +1010,21 @@ sub AddProject } } } + if ($self->{options}->{nss}) + { + $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss'); + $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr'); + foreach my $lib (qw(plds4 plc4 nspr4)) + { + $proj->AddLibrary($self->{options}->{nss} . + '\lib\lib' . "$lib.lib", 0); + } + foreach my $lib (qw(ssl3 smime3 nss3)) + { + $proj->AddLibrary($self->{options}->{nss} . + '\lib' . "\\$lib.dll.lib", 0); + } + } if ($self->{options}->{nls}) { $proj->AddIncludeDir($self->{options}->{nls} . '\include'); -- 2.21.1 (Apple Git-122.3)