From ed0da4793cb211cced0cd8812c648fb63e066bb6 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Tue, 27 Oct 2020 21:03:41 +0100 Subject: [PATCH] Support for NSS as a TLS backend v13 Daniel Gustafsson, Andrew Dunstan --- configure | 342 ++++- configure.ac | 50 +- contrib/Makefile | 2 +- contrib/pgcrypto/Makefile | 5 + contrib/pgcrypto/nss.c | 773 +++++++++++ contrib/pgcrypto/openssl.c | 2 +- contrib/pgcrypto/px.c | 1 + contrib/pgcrypto/px.h | 1 + .../postgres_fdw/expected/postgres_fdw.out | 2 +- contrib/sslinfo/sslinfo.c | 164 ++- doc/src/sgml/acronyms.sgml | 43 + doc/src/sgml/config.sgml | 28 +- doc/src/sgml/installation.sgml | 30 +- doc/src/sgml/libpq.sgml | 27 +- doc/src/sgml/pgcrypto.sgml | 38 +- doc/src/sgml/runtime.sgml | 78 +- doc/src/sgml/sslinfo.sgml | 14 +- src/Makefile.global.in | 10 + src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 7 + src/backend/libpq/be-secure-nss.c | 1175 +++++++++++++++++ src/backend/libpq/be-secure-openssl.c | 16 +- src/backend/libpq/be-secure.c | 3 + src/backend/libpq/hba.c | 2 +- src/backend/utils/misc/guc.c | 20 +- src/include/common/pg_nss.h | 141 ++ src/include/libpq/libpq-be.h | 13 +- src/include/libpq/libpq.h | 3 + src/include/pg_config.h.in | 6 + src/include/pg_config_manual.h | 5 +- src/interfaces/libpq/Makefile | 11 +- src/interfaces/libpq/fe-connect.c | 4 + src/interfaces/libpq/fe-secure-nss.c | 1018 ++++++++++++++ src/interfaces/libpq/fe-secure.c | 5 +- src/interfaces/libpq/libpq-fe.h | 11 + src/interfaces/libpq/libpq-int.h | 5 + src/port/pg_strong_random.c | 37 + src/test/Makefile | 2 +- src/test/ssl/Makefile | 176 +++ src/test/ssl/t/001_ssltests.pl | 326 +++-- src/test/ssl/t/002_scram.pl | 4 +- src/test/ssl/t/SSL/Backend/NSS.pm | 64 + src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 ++ .../ssl/t/{SSLServer.pm => SSL/Server.pm} | 80 +- src/tools/msvc/Install.pm | 3 +- src/tools/msvc/Mkvcbuild.pm | 29 +- src/tools/msvc/Solution.pm | 20 + src/tools/msvc/config_default.pl | 1 + 48 files changed, 4618 insertions(+), 286 deletions(-) create mode 100644 contrib/pgcrypto/nss.c create mode 100644 src/backend/libpq/be-secure-nss.c create mode 100644 src/include/common/pg_nss.h create mode 100644 src/interfaces/libpq/fe-secure-nss.c create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm rename src/test/ssl/t/{SSLServer.pm => SSL/Server.pm} (78%) diff --git a/configure b/configure index ace4ed5dec..412f40b58a 100755 --- a/configure +++ b/configure @@ -654,6 +654,8 @@ LIBOBJS UUID_LIBS LDAP_LIBS_BE LDAP_LIBS_FE +NSPR_CONFIG +NSS_CONFIG PTHREAD_CFLAGS PTHREAD_LIBS PTHREAD_CC @@ -711,6 +713,7 @@ with_uuid with_readline with_systemd with_selinux +with_nss with_openssl with_ldap with_krb_srvnam @@ -857,6 +860,7 @@ with_bsd_auth with_ldap with_bonjour with_openssl +with_nss with_selinux with_systemd with_readline @@ -1559,6 +1563,7 @@ Optional Packages: --with-ldap build with LDAP support --with-bonjour build with Bonjour support --with-openssl build with OpenSSL support + --with-nss build with NSS support --with-selinux build with SELinux support --with-systemd build with systemd support --without-readline do not use GNU Readline nor BSD Libedit for editing @@ -8106,6 +8111,41 @@ fi $as_echo "$with_openssl" >&6; } +# +# LibNSS +# +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" >&5 +$as_echo_n "checking whether to build with NSS support... " >&6; } + + + +# Check whether --with-nss was given. +if test "${with_nss+set}" = set; then : + withval=$with_nss; + case $withval in + yes) + +$as_echo "#define USE_NSS 1" >>confdefs.h + + ;; + no) + : + ;; + *) + as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5 + ;; + esac + +else + with_nss=no + +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5 +$as_echo "$with_nss" >&6; } + + # # SELinux # @@ -12180,6 +12220,9 @@ fi fi if test "$with_openssl" = yes ; then + if test x"$with_nss" = x"yes" ; then + as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5 + fi # Minimum required OpenSSL version is 1.0.1 $as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h @@ -12442,6 +12485,274 @@ done fi +if test "$with_nss" = yes ; then + if test x"$with_openssl" = x"yes" ; then + as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" "$LINENO" 5 + fi + # TODO: fallback in case nss-config/nspr-config aren't found. + if test -z "$NSS_CONFIG"; then + for ac_prog in nss-config +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_NSS_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $NSS_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_NSS_CONFIG="$NSS_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_NSS_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +NSS_CONFIG=$ac_cv_path_NSS_CONFIG +if test -n "$NSS_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5 +$as_echo "$NSS_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$NSS_CONFIG" && break +done + +else + # Report the value of NSS_CONFIG in configure's output in all cases. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSS_CONFIG" >&5 +$as_echo_n "checking for NSS_CONFIG... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSS_CONFIG" >&5 +$as_echo "$NSS_CONFIG" >&6; } +fi + + if test -z "$NSPR_CONFIG"; then + for ac_prog in nspr-config +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_NSPR_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $NSPR_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_NSPR_CONFIG="$NSPR_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_NSPR_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +NSPR_CONFIG=$ac_cv_path_NSPR_CONFIG +if test -n "$NSPR_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5 +$as_echo "$NSPR_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$NSPR_CONFIG" && break +done + +else + # Report the value of NSPR_CONFIG in configure's output in all cases. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NSPR_CONFIG" >&5 +$as_echo_n "checking for NSPR_CONFIG... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NSPR_CONFIG" >&5 +$as_echo "$NSPR_CONFIG" >&6; } +fi + + if test -n "$NSS_CONFIG"; then + NSS_LIBS=`$NSS_CONFIG --libs` + NSS_CFLAGS=`$NSS_CONFIG --cflags` + fi + if test -n "$NSPR_CONFIG"; then + NSPR_LIBS=`$NSPR_CONFIG --libs` + NSPR_CFLAGS=`$NSPR_CONFIG --cflags` + fi + + LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS" + CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in -lnss3" >&5 +$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; } +if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lnss3 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char SSL_VersionRangeSet (); +int +main () +{ +return SSL_VersionRangeSet (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_nss3_SSL_VersionRangeSet=yes +else + ac_cv_lib_nss3_SSL_VersionRangeSet=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nss3_SSL_VersionRangeSet" >&5 +$as_echo "$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; } +if test "x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBNSS3 1 +_ACEOF + + LIBS="-lnss3 $LIBS" + +else + as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5 +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in -lnspr4" >&5 +$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; } +if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lnspr4 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char PR_GetDefaultIOMethods (); +int +main () +{ +return PR_GetDefaultIOMethods (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes +else + ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5 +$as_echo "$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; } +if test "x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBNSPR4 1 +_ACEOF + + LIBS="-lnspr4 $LIBS" + +else + as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5 +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in -lssl3" >&5 +$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; } +if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lssl3 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char SSL_GetImplementedCiphers (); +int +main () +{ +return SSL_GetImplementedCiphers (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes +else + ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5 +$as_echo "$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; } +if test "x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBSSL3 1 +_ACEOF + + LIBS="-lssl3 $LIBS" + +else + as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5 +fi + +fi + if test "$with_pam" = yes ; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5 $as_echo_n "checking for pam_start in -lpam... " >&6; } @@ -13344,6 +13655,25 @@ else fi +fi + +if test "$with_nss" = yes ; then + ac_fn_c_check_header_mongrel "$LINENO" "nss/ssl.h" "ac_cv_header_nss_ssl_h" "$ac_includes_default" +if test "x$ac_cv_header_nss_ssl_h" = xyes; then : + +else + as_fn_error $? "header file 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 @@ -18061,11 +18391,13 @@ fi # in the template or configure command line. # If not selected manually, try to select a source automatically. -if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then +if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then if test x"$with_openssl" = x"yes" ; then USE_OPENSSL_RANDOM=1 elif test "$PORTNAME" = "win32" ; then USE_WIN32_RANDOM=1 + elif test x"$with_nss" = x"yes" ; then + USE_NSS_RANDOM=1 else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for /dev/urandom" >&5 $as_echo_n "checking for /dev/urandom... " >&6; } @@ -18107,6 +18439,12 @@ $as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h { $as_echo "$as_me:${as_lineno-$LINENO}: result: Windows native" >&5 $as_echo "Windows native" >&6; } +elif test x"$USE_NSS_RANDOM" = x"1" ; then + +$as_echo "#define USE_NSS_RANDOM 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: NSS" >&5 +$as_echo "NSS" >&6; } elif test x"$USE_DEV_URANDOM" = x"1" ; then $as_echo "#define USE_DEV_URANDOM 1" >>confdefs.h @@ -18116,7 +18454,7 @@ $as_echo "/dev/urandom" >&6; } else as_fn_error $? " no source of strong random numbers was found -PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers." "$LINENO" 5 +PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers." "$LINENO" 5 fi # If not set in template file, set bytes to use libc memset() diff --git a/configure.ac b/configure.ac index 5b91c83fd0..86311946d0 100644 --- a/configure.ac +++ b/configure.ac @@ -861,6 +861,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support], AC_MSG_RESULT([$with_openssl]) AC_SUBST(with_openssl) +# +# LibNSS +# +AC_MSG_CHECKING([whether to build with NSS support]) +PGAC_ARG_BOOL(with, nss, no, [build with NSS support], + [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. (--with-nss)])]) +AC_MSG_RESULT([$with_nss]) +AC_SUBST(with_nss) + # # SELinux # @@ -1210,6 +1219,9 @@ if test "$with_gssapi" = yes ; then fi if test "$with_openssl" = yes ; then + if test x"$with_nss" = x"yes" ; then + AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"]) + fi dnl Order matters! # Minimum required OpenSSL version is 1.0.1 AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L], @@ -1235,6 +1247,30 @@ if test "$with_openssl" = yes ; then AC_CHECK_FUNCS([CRYPTO_lock]) fi +if test "$with_nss" = yes ; then + if test x"$with_openssl" = x"yes" ; then + AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"]) + fi + # TODO: fallback in case nss-config/nspr-config aren't found. + PGAC_PATH_PROGS(NSS_CONFIG, nss-config) + PGAC_PATH_PROGS(NSPR_CONFIG, nspr-config) + if test -n "$NSS_CONFIG"; then + NSS_LIBS=`$NSS_CONFIG --libs` + NSS_CFLAGS=`$NSS_CONFIG --cflags` + fi + if test -n "$NSPR_CONFIG"; then + NSPR_LIBS=`$NSPR_CONFIG --libs` + NSPR_CFLAGS=`$NSPR_CONFIG --cflags` + fi + + LDFLAGS="$LDFLAGS $NSS_LIBS $NSPR_LIBS" + CFLAGS="$CFLAGS $NSS_CFLAGS $NSPR_CFLAGS" + + AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [AC_MSG_ERROR([library 'nss3' is required for NSS])]) + AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], [AC_MSG_ERROR([library 'nspr4' is required for NSS])]) + AC_CHECK_LIB(ssl3, SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])]) +fi + if test "$with_pam" = yes ; then AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])]) fi @@ -1410,6 +1446,11 @@ if test "$with_openssl" = yes ; then AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file is required for OpenSSL])]) fi +if test "$with_nss" = yes ; 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 AC_CHECK_HEADERS(security/pam_appl.h, [], [AC_CHECK_HEADERS(pam/pam_appl.h, [], @@ -2158,11 +2199,13 @@ fi # in the template or configure command line. # If not selected manually, try to select a source automatically. -if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then +if test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" && test x"$USE_NSS_RANDOM" = x"" ; then if test x"$with_openssl" = x"yes" ; then USE_OPENSSL_RANDOM=1 elif test "$PORTNAME" = "win32" ; then USE_WIN32_RANDOM=1 + elif test x"$with_nss" = x"yes" ; then + USE_NSS_RANDOM=1 else AC_CHECK_FILE([/dev/urandom], [], []) @@ -2179,13 +2222,16 @@ if test x"$USE_OPENSSL_RANDOM" = x"1" ; then elif test x"$USE_WIN32_RANDOM" = x"1" ; then AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation]) AC_MSG_RESULT([Windows native]) +elif test x"$USE_NSS_RANDOM" = x"1" ; then + AC_DEFINE(USE_NSS_RANDOM, 1, [Define to use NSS for random number generation]) + AC_MSG_RESULT([NSS]) elif test x"$USE_DEV_URANDOM" = x"1" ; then AC_DEFINE(USE_DEV_URANDOM, 1, [Define to use /dev/urandom for random number generation]) AC_MSG_RESULT([/dev/urandom]) else AC_MSG_ERROR([ no source of strong random numbers was found -PostgreSQL can use OpenSSL or /dev/urandom as a source of random numbers.]) +PostgreSQL can use OpenSSL, NSS, Windows native RNG or /dev/urandom as a source of random numbers.]) fi # If not set in template file, set bytes to use libc memset() diff --git a/contrib/Makefile b/contrib/Makefile index 7a4866e338..d0b01cb2cc 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -52,7 +52,7 @@ SUBDIRS = \ unaccent \ vacuumlo -ifeq ($(with_openssl),yes) +ifeq ($(with_ssl),yes) SUBDIRS += sslinfo else ALWAYS_SUBDIRS += sslinfo diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile index 61eabd2fc0..6c69ee5a6b 100644 --- a/contrib/pgcrypto/Makefile +++ b/contrib/pgcrypto/Makefile @@ -7,11 +7,16 @@ INT_TESTS = sha2 OSSL_SRCS = openssl.c pgp-mpi-openssl.c OSSL_TESTS = sha2 des 3des cast5 +NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c +NSS_TESTS = sha2 des 3des + ZLIB_TST = pgp-compression ZLIB_OFF_TST = pgp-zlib-DISABLED CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS)) +CF_SRCS = $(if $(subst no,,$(with_nss)), $(NSS_SRCS), $(INT_SRCS)) CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS)) +CF_TESTS = $(if $(subst no,,$(with_nss)), $(NSS_TESTS), $(INT_TESTS)) CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST)) SRCS = \ diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c new file mode 100644 index 0000000000..e1291ed403 --- /dev/null +++ b/contrib/pgcrypto/nss.c @@ -0,0 +1,773 @@ +/*------------------------------------------------------------------------- + * + * nss.c + * Wrapper for using NSS as a backend for pgcrypto PX + * + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * contrib/pgcrypto/nss.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "px.h" +#include "blf.h" +#include "utils/memutils.h" + +/* + * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef + * our version to avoid compiler warnings on redefinition. + */ +#define pg_BITS_PER_BYTE BITS_PER_BYTE +#undef BITS_PER_BYTE + +#include +#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 + +/* + * Define our own mechanisms for Blowfish as it's not implemented by NSS. + */ +#define BLOWFISH_CBC (1) +#define BLOWFISH_ECB (2) + +/* + * Data structures for recording cipher implementations as well as ongoing + * cipher operations. + */ +typedef struct nss_digest +{ + NSSInitContext *context; + PK11Context *hash_context; + HASH_HashType hash_type; +} nss_digest; + +typedef struct cipher_implementation +{ + /* Function pointers to cipher operations */ + int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv); + unsigned (*get_block_size) (PX_Cipher *pxc); + unsigned (*get_key_size) (PX_Cipher *pxc); + unsigned (*get_iv_size) (PX_Cipher *pxc); + int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res); + int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res); + void (*free) (PX_Cipher *pxc); + + /* The mechanism describing the cipher used */ + union + { + CK_MECHANISM_TYPE nss; + CK_ULONG internal; + } mechanism; + int keylen; + bool is_nss; +} cipher_implementation; + +typedef struct nss_cipher +{ + const cipher_implementation *impl; + NSSInitContext *context; + PK11Context *crypt_context; + SECItem *params; + + PK11SymKey *encrypt_key; + PK11SymKey *decrypt_key; +} nss_cipher; + +typedef struct internal_cipher +{ + const cipher_implementation *impl; + BlowfishContext context; +} internal_cipher; + +typedef struct nss_cipher_ref +{ + const char *name; + const cipher_implementation *impl; +} nss_cipher_ref; + +/* + * Prototypes + */ +static unsigned nss_get_iv_size(PX_Cipher *pxc); +static unsigned nss_get_block_size(PX_Cipher *pxc); + +/* + * nss_GetHashOidTagByHashType + * + * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43 + * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of + * writing is not commonly available so we need our own version till then. + */ +static SECOidTag +nss_GetHashOidTagByHashType(HASH_HashType type) +{ + if (type == HASH_AlgMD2) + return SEC_OID_MD2; + if (type == HASH_AlgMD5) + return SEC_OID_MD5; + if (type == HASH_AlgSHA1) + return SEC_OID_SHA1; + if (type == HASH_AlgSHA224) + return SEC_OID_SHA224; + if (type == HASH_AlgSHA256) + return SEC_OID_SHA256; + if (type == HASH_AlgSHA384) + return SEC_OID_SHA384; + if (type == HASH_AlgSHA512) + return SEC_OID_SHA512; + + return SEC_OID_UNKNOWN; +} + +static unsigned +nss_digest_block_size(PX_MD *pxmd) +{ + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + const SECHashObject *object; + + object = HASH_GetHashObject(digest->hash_type); + return object->blocklength; +} + +static unsigned +nss_digest_result_size(PX_MD *pxmd) +{ + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + const SECHashObject *object; + + object = HASH_GetHashObject(digest->hash_type); + return object->length; +} + +static void +nss_digest_free(PX_MD *pxmd) +{ + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + PRBool free_ctx = PR_TRUE; + + PK11_DestroyContext(digest->hash_context, free_ctx); + NSS_ShutdownContext(digest->context); +} + +static void +nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen) +{ + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + + PK11_DigestOp(digest->hash_context, data, dlen); +} + +static void +nss_digest_reset(PX_MD *pxmd) +{ + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + + PK11_DigestBegin(digest->hash_context); +} + +static void +nss_digest_finish(PX_MD *pxmd, uint8 *dst) +{ + unsigned int outlen; + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + const SECHashObject *object; + + object = HASH_GetHashObject(digest->hash_type); + PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length); +} + +int +px_find_digest(const char *name, PX_MD **res) +{ + PX_MD *pxmd; + NSSInitParameters params; + NSSInitContext *nss_context; + bool found = false; + nss_digest *digest; + SECStatus status; + SECOidData *hash; + HASH_HashType t; + + /* + * Initialize our own NSS context without a database backing it. + */ + memset(¶ms, 0, sizeof(params)); + params.length = sizeof(params); + nss_context = NSS_InitContext("", "", "", "", ¶ms, + NSS_INIT_READONLY | NSS_INIT_NOCERTDB | + NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN | + NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD); + + /* + * There is no API function for looking up a digest algorithm from a name + * string, but there is a publically accessible array which can be scanned + * for the name. + */ + for (t = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++) + { + SECOidTag hash_oid; + char *p; + + hash_oid = nss_GetHashOidTagByHashType(t); + + if (hash_oid == SEC_OID_UNKNOWN) + return PXE_NO_HASH; + + hash = SECOID_FindOIDByTag(hash_oid); + if (pg_strcasecmp(hash->desc, name) == 0) + { + found = true; + break; + } + + /* + * NSS saves the algorithm names using SHA-xxx notation whereas + * OpenSSL use SHAxxx. To make sure the user finds the requested + * algorithm let's remove the dash and compare that spelling as well. + */ + if ((p = strchr(hash->desc, '-')) != NULL) + { + char tmp[12]; + + memcpy(tmp, hash->desc, p - hash->desc); + memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1); + if (pg_strcasecmp(tmp, name) == 0) + { + found = true; + break; + } + } + } + + if (!found) + return PXE_NO_HASH; + + digest = palloc(sizeof(*digest)); + + digest->context = nss_context; + digest->hash_context = PK11_CreateDigestContext(hash->offset); + digest->hash_type = t; + if (digest->hash_context == NULL) + { + pfree(digest); + return -1; + } + + status = PK11_DigestBegin(digest->hash_context); + if (status != SECSuccess) + { + PK11_DestroyContext(digest->hash_context, PR_TRUE); + pfree(digest); + return -1; + } + + pxmd = palloc(sizeof(*pxmd)); + pxmd->result_size = nss_digest_result_size; + pxmd->block_size = nss_digest_block_size; + pxmd->reset = nss_digest_reset; + pxmd->update = nss_digest_update; + pxmd->finish = nss_digest_finish; + pxmd->free = nss_digest_free; + pxmd->p.ptr = (void *) digest; + + *res = pxmd; + + return 0; +} + +static int +bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv) +{ + internal_cipher *cipher = (internal_cipher *) pxc->ptr; + + blowfish_setkey(&cipher->context, key, klen); + if (iv) + blowfish_setiv(&cipher->context, iv); + + return 0; +} + +static int +bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res) +{ + internal_cipher *cipher = (internal_cipher *) pxc->ptr; + uint8 *buf; + + if (dlen == 0) + return 0; + + if (dlen & 7) + return PXE_NOTBLOCKSIZE; + + buf = palloc(dlen); + memcpy(buf, data, dlen); + + if (cipher->impl->mechanism.internal == BLOWFISH_ECB) + blowfish_encrypt_ecb(buf, dlen, &cipher->context); + else + blowfish_encrypt_cbc(buf, dlen, &cipher->context); + + memcpy(res, buf, dlen); + pfree(buf); + + return 0; +} + +static int +bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res) +{ + internal_cipher *cipher = (internal_cipher *) pxc->ptr; + + if (dlen == 0) + return 0; + + if (dlen & 7) + return PXE_NOTBLOCKSIZE; + + memcpy(res, data, dlen); + if (cipher->impl->mechanism.internal == BLOWFISH_ECB) + blowfish_decrypt_ecb(res, dlen, &cipher->context); + else + blowfish_decrypt_cbc(res, dlen, &cipher->context); + + return 0; +} + +static void +bf_free(PX_Cipher *pxc) +{ + internal_cipher *cipher = (internal_cipher *) pxc->ptr; + + pfree(cipher); + pfree(pxc); +} + +static int +nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, + const uint8 *iv) +{ + nss_cipher *cipher = (nss_cipher *) pxc->ptr; + SECItem iv_item; + unsigned char *iv_item_data; + SECItem key_item; + int keylen; + PK11SlotInfo *slot; + + if (cipher->impl->mechanism.nss == CKM_AES_CBC || + cipher->impl->mechanism.nss == CKM_AES_ECB) + { + if (klen <= 128 / 8) + keylen = 128 / 8; + else if (klen <= 192 / 8) + keylen = 192 / 8; + else if (klen <= 256 / 8) + keylen = 256 / 8; + else + return PXE_CIPHER_INIT; + } + else + keylen = cipher->impl->keylen; + + key_item.type = siBuffer; + key_item.data = (unsigned char *) key; + key_item.len = keylen; + + /* + * If hardware acceleration is configured in NSS one can theoretically get + * a better slot by calling PK11_GetBestSlot() with the mechanism passed + * to it. Unless there are complaints, using the simpler API will make + * error handling easier though. + */ + slot = PK11_GetInternalSlot(); + if (!slot) + return PXE_CIPHER_INIT; + + /* + * The key must be set up for the operation, and since we don't know at + * this point whether we are asked to encrypt or decrypt we need to store + * both versions. + */ + cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss, + PK11_OriginUnwrap, + CKA_DECRYPT, &key_item, + NULL); + cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss, + PK11_OriginUnwrap, + CKA_ENCRYPT, &key_item, + NULL); + PK11_FreeSlot(slot); + + if (!cipher->decrypt_key || !cipher->encrypt_key) + return PXE_CIPHER_INIT; + + if (iv) + { + iv_item.type = siBuffer; + iv_item.data = (unsigned char *) iv; + iv_item.len = nss_get_iv_size(pxc); + } + else + { + /* + * The documentation states that either passing .data = 0; .len = 0; + * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be + * done when IV is missing. That however leads to segfaults in the + * library, the workaround that works in modern library versions is + * to pass in a keysized zeroed out IV. + */ + iv_item_data = palloc0(nss_get_iv_size(pxc)); + iv_item.type = siBuffer; + iv_item.data = iv_item_data; + iv_item.len = nss_get_iv_size(pxc); + } + + cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item); + + /* If we had to make a mock IV, free it once made into a param */ + if (!iv) + pfree(iv_item_data); + + if (cipher->params == NULL) + return PXE_CIPHER_INIT; + + return 0; +} + +static int +nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res) +{ + nss_cipher *cipher = (nss_cipher *) pxc->ptr; + SECStatus status; + int outlen; + + if (!cipher->crypt_context) + { + cipher->crypt_context = + PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT, + cipher->decrypt_key, cipher->params); + } + + status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen); + if (status != SECSuccess) + return PXE_DECRYPT_FAILED; + + return 0; +} + +static int +nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res) +{ + nss_cipher *cipher = (nss_cipher *) pxc->ptr; + SECStatus status; + int outlen; + + if (!cipher->crypt_context) + { + cipher->crypt_context = + PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT, + cipher->decrypt_key, cipher->params); + } + + status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen); + + if (status != SECSuccess) + return PXE_DECRYPT_FAILED; + + return 0; +} + +static void +nss_free(PX_Cipher *pxc) +{ + nss_cipher *cipher = pxc->ptr; + PRBool free_ctx = PR_TRUE; + + PK11_FreeSymKey(cipher->encrypt_key); + PK11_FreeSymKey(cipher->decrypt_key); + PK11_DestroyContext(cipher->crypt_context, free_ctx); + NSS_ShutdownContext(cipher->context); + pfree(cipher); + + pfree(pxc); +} + +static unsigned +nss_get_block_size(PX_Cipher *pxc) +{ + nss_cipher *cipher = pxc->ptr; + + return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL); +} + +static unsigned +nss_get_key_size(PX_Cipher *pxc) +{ + nss_cipher *cipher = pxc->ptr; + + return cipher->impl->keylen; +} + +static unsigned +nss_get_iv_size(PX_Cipher *pxc) +{ + nss_cipher *cipher = pxc->ptr; + + return PK11_GetIVLength(cipher->impl->mechanism.nss); +} + +static unsigned +bf_get_block_size(PX_Cipher *pxc) +{ + return 8; +} + +static unsigned +bf_get_key_size(PX_Cipher *pxc) +{ + return 448 / 8; +} + +static unsigned +bf_get_iv_size(PX_Cipher *pxc) +{ + return 8; +} + +/* + * Cipher Implementations + */ +static const cipher_implementation nss_des_cbc = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_DES_CBC, + .keylen = 8, + .is_nss = true +}; + +static const cipher_implementation nss_des_ecb = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_DES_ECB, + .keylen = 8, + .is_nss = true +}; + +static const cipher_implementation nss_des3_cbc = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_DES3_CBC, + .keylen = 24, + .is_nss = true +}; + +static const cipher_implementation nss_des3_ecb = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_DES3_ECB, + .keylen = 24, + .is_nss = true +}; + +static const cipher_implementation nss_aes_cbc = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_AES_CBC, + .keylen = 32, + .is_nss = true +}; + +static const cipher_implementation nss_aes_ecb = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_AES_ECB, + .keylen = 32, + .is_nss = true +}; + +static const cipher_implementation nss_bf_ecb = { + .init = bf_init, + .get_block_size = bf_get_block_size, + .get_key_size = bf_get_key_size, + .get_iv_size = bf_get_iv_size, + .encrypt = bf_encrypt, + .decrypt = bf_decrypt, + .free = bf_free, + .mechanism.internal = BLOWFISH_ECB, + .keylen = 56, + .is_nss = false +}; + +static const cipher_implementation nss_bf_cbc = { + .init = bf_init, + .get_block_size = bf_get_block_size, + .get_key_size = bf_get_key_size, + .get_iv_size = bf_get_iv_size, + .encrypt = bf_encrypt, + .decrypt = bf_decrypt, + .free = bf_free, + .mechanism.internal = BLOWFISH_CBC, + .keylen = 56, + .is_nss = false +}; + +/* + * Lookup table for finding the implementation based on a name. CAST5 as well + * as BLOWFISH are defined as cipher mechanisms in NSS but error out with + * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal + * implementation while CAST5 isn't supported at all at this time, + */ +static const nss_cipher_ref nss_cipher_lookup[] = { + {"des", &nss_des_cbc}, + {"des-cbc", &nss_des_cbc}, + {"des-ecb", &nss_des_ecb}, + {"des3-cbc", &nss_des3_cbc}, + {"3des", &nss_des3_cbc}, + {"3des-cbc", &nss_des3_cbc}, + {"des3-ecb", &nss_des3_ecb}, + {"3des-ecb", &nss_des3_ecb}, + {"aes", &nss_aes_cbc}, + {"aes-cbc", &nss_aes_cbc}, + {"aes-ecb", &nss_aes_ecb}, + {"rijndael", &nss_aes_cbc}, + {"rijndael-cbc", &nss_aes_cbc}, + {"rijndael-ecb", &nss_aes_ecb}, + {"blowfish", &nss_bf_cbc}, + {"blowfish-cbc", &nss_bf_cbc}, + {"blowfish-ecb", &nss_bf_ecb}, + {"bf", &nss_bf_cbc}, + {"bf-cbc", &nss_bf_cbc}, + {"bf-ecb", &nss_bf_ecb}, + {NULL} +}; + +/* + * px_find_cipher + * + * Search for the requested cipher and see if there is support for it, and if + * so return an allocated object containing the playbook for how to encrypt + * and decrypt using the cipher. + */ +int +px_find_cipher(const char *alias, PX_Cipher **res) +{ + const nss_cipher_ref *cipher_ref; + PX_Cipher *px_cipher; + NSSInitParameters params; + NSSInitContext *nss_context; + nss_cipher *cipher; + internal_cipher *int_cipher; + + for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++) + { + if (strcmp(cipher_ref->name, alias) == 0) + break; + } + + if (!cipher_ref->name) + return PXE_NO_CIPHER; + + /* + * Fill in the PX_Cipher to pass back to PX describing the operations to + * perform in order to use the cipher. + */ + px_cipher = palloc(sizeof(*px_cipher)); + px_cipher->block_size = cipher_ref->impl->get_block_size; + px_cipher->key_size = cipher_ref->impl->get_key_size; + px_cipher->iv_size = cipher_ref->impl->get_iv_size; + px_cipher->free = cipher_ref->impl->free; + px_cipher->init = cipher_ref->impl->init; + px_cipher->encrypt = cipher_ref->impl->encrypt; + px_cipher->decrypt = cipher_ref->impl->decrypt; + + /* + * Set the private data, which is different for NSS and internal ciphers + */ + if (cipher_ref->impl->is_nss) + { + /* + * Initialize our own NSS context without a database backing it. At + * some point we might want to allow users to use stored keys in the + * database rather than passing them via SELECT encrypt(), and then + * this need to be changed to open a user specified database. + */ + memset(¶ms, 0, sizeof(params)); + params.length = sizeof(params); + nss_context = NSS_InitContext("", "", "", "", ¶ms, + NSS_INIT_READONLY | NSS_INIT_NOCERTDB | + NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN | + NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD); + + /* nss_cipher is the private state struct for the operation */ + cipher = palloc(sizeof(*cipher)); + cipher->context = nss_context; + cipher->impl = cipher_ref->impl; + cipher->crypt_context = NULL; + + px_cipher->ptr = cipher; + } + else + { + int_cipher = palloc0(sizeof(*int_cipher)); + + int_cipher->impl = cipher_ref->impl; + px_cipher->ptr = int_cipher; + } + + *res = px_cipher; + return 0; +} diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c index ed96e4ce53..5ebe213406 100644 --- a/contrib/pgcrypto/openssl.c +++ b/contrib/pgcrypto/openssl.c @@ -400,7 +400,7 @@ gen_ossl_encrypt(PX_Cipher *c, const uint8 *data, unsigned dlen, } if (!EVP_EncryptUpdate(od->evp_ctx, res, &outlen, data, dlen)) - return PXE_ERR_GENERIC; + return PXE_ENCRYPT_FAILED; return 0; } diff --git a/contrib/pgcrypto/px.c b/contrib/pgcrypto/px.c index 6a4681dae9..a243f575d3 100644 --- a/contrib/pgcrypto/px.c +++ b/contrib/pgcrypto/px.c @@ -58,6 +58,7 @@ static const struct error_desc px_err_list[] = { {PXE_MCRYPT_INTERNAL, "mcrypt internal error"}, {PXE_NO_RANDOM, "Failed to generate strong random bits"}, {PXE_DECRYPT_FAILED, "Decryption failed"}, + {PXE_ENCRYPT_FAILED, "Encryption failed"}, {PXE_PGP_CORRUPT_DATA, "Wrong key or corrupt data"}, {PXE_PGP_CORRUPT_ARMOR, "Corrupt ascii-armor"}, {PXE_PGP_UNSUPPORTED_COMPR, "Unsupported compression algorithm"}, diff --git a/contrib/pgcrypto/px.h b/contrib/pgcrypto/px.h index 5487923edb..17d6f22498 100644 --- a/contrib/pgcrypto/px.h +++ b/contrib/pgcrypto/px.h @@ -61,6 +61,7 @@ #define PXE_MCRYPT_INTERNAL -16 #define PXE_NO_RANDOM -17 #define PXE_DECRYPT_FAILED -18 +#define PXE_ENCRYPT_FAILED -19 #define PXE_PGP_CORRUPT_DATA -100 #define PXE_PGP_CORRUPT_ARMOR -101 diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 2d88d06358..984efc0d7e 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -8911,7 +8911,7 @@ DO $d$ END; $d$; ERROR: invalid option "password" -HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size +HINT: Valid options in this context are: service, passfile, channel_binding, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, tcp_user_timeout, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, ssl_min_protocol_version, ssl_max_protocol_version, gssencmode, krbsrvname, gsslib, target_session_attrs, cert_database, use_remote_estimate, fdw_startup_cost, fdw_tuple_cost, extensions, updatable, fetch_size CONTEXT: SQL statement "ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw')" PL/pgSQL function inline_code_block line 3 at EXECUTE -- If we add a password for our user mapping instead, we should get a different diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c index 5ba3988e27..84bb2c65b8 100644 --- a/contrib/sslinfo/sslinfo.c +++ b/contrib/sslinfo/sslinfo.c @@ -9,9 +9,11 @@ #include "postgres.h" +#ifdef USE_OPENSSL #include #include #include +#endif #include "access/htup_details.h" #include "funcapi.h" @@ -21,8 +23,8 @@ PG_MODULE_MAGIC; +#ifdef USE_OPENSSL static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName); -static Datum X509_NAME_to_text(X509_NAME *name); static Datum ASN1_STRING_to_text(ASN1_STRING *str); /* @@ -32,6 +34,7 @@ typedef struct { TupleDesc tupdesc; } SSLExtensionInfoContext; +#endif /* * Indicates whether current session uses SSL @@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version); Datum ssl_version(PG_FUNCTION_ARGS) { - if (MyProcPort->ssl == NULL) + const char *version; + + if (!MyProcPort->ssl_in_use) + PG_RETURN_NULL(); + + version = be_tls_get_version(MyProcPort); + if (version == NULL) PG_RETURN_NULL(); - PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl))); + + PG_RETURN_TEXT_P(cstring_to_text(version)); } @@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher); Datum ssl_cipher(PG_FUNCTION_ARGS) { - if (MyProcPort->ssl == NULL) + const char *cipher; + + if (!MyProcPort->ssl_in_use) PG_RETURN_NULL(); - PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl))); + + cipher = be_tls_get_cipher(MyProcPort); + if (cipher == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(cstring_to_text(cipher)); } @@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present); Datum ssl_client_cert_present(PG_FUNCTION_ARGS) { - PG_RETURN_BOOL(MyProcPort->peer != NULL); + PG_RETURN_BOOL(MyProcPort->peer_cert_valid); } @@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial); Datum ssl_client_serial(PG_FUNCTION_ARGS) { + char decimal[NAMEDATALEN]; Datum result; - Port *port = MyProcPort; - X509 *peer = port->peer; - ASN1_INTEGER *serial = NULL; - BIGNUM *b; - char *decimal; - if (!peer) + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN); + + if (!*decimal) PG_RETURN_NULL(); - serial = X509_get_serialNumber(peer); - b = ASN1_INTEGER_to_BN(serial, NULL); - decimal = BN_bn2dec(b); - BN_free(b); result = DirectFunctionCall3(numeric_in, CStringGetDatum(decimal), ObjectIdGetDatum(0), Int32GetDatum(-1)); - OPENSSL_free(decimal); return result; } +#ifdef USE_OPENSSL /* * Converts OpenSSL ASN1_STRING structure into text * @@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS) text *fieldname = PG_GETARG_TEXT_PP(0); Datum result; - if (!(MyProcPort->peer)) + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) PG_RETURN_NULL(); result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname); @@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS) else return result; } +#endif /* USE_OPENSSL */ - -/* - * Equivalent of X509_NAME_oneline that respects encoding - * - * This function converts X509_NAME structure to the text variable - * converting all textual data into current database encoding. - * - * Parameter: X509_NAME *name X509_NAME structure to be converted - * - * Returns: text datum which contains string representation of - * X509_NAME - */ -static Datum -X509_NAME_to_text(X509_NAME *name) +#ifdef USE_NSS +PG_FUNCTION_INFO_V1(ssl_client_dn_field); +Datum +ssl_client_dn_field(PG_FUNCTION_ARGS) { - BIO *membuf = BIO_new(BIO_s_mem()); - int i, - nid, - count = X509_NAME_entry_count(name); - X509_NAME_ENTRY *e; - ASN1_STRING *v; - const char *field_name; - size_t size; - char nullterm; - char *sp; - char *dp; - text *result; - - if (membuf == NULL) - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("could not create OpenSSL BIO structure"))); - - (void) BIO_set_close(membuf, BIO_CLOSE); - for (i = 0; i < count; i++) - { - e = X509_NAME_get_entry(name, i); - nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e)); - if (nid == NID_undef) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not get NID for ASN1_OBJECT object"))); - v = X509_NAME_ENTRY_get_data(e); - field_name = OBJ_nid2sn(nid); - if (field_name == NULL) - field_name = OBJ_nid2ln(nid); - if (field_name == NULL) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid))); - BIO_printf(membuf, "/%s=", field_name); - ASN1_STRING_print_ex(membuf, v, - ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB) - | ASN1_STRFLGS_UTF8_CONVERT)); - } - - /* ensure null termination of the BIO's content */ - nullterm = '\0'; - BIO_write(membuf, &nullterm, 1); - size = BIO_get_mem_data(membuf, &sp); - dp = pg_any_to_server(sp, size - 1, PG_UTF8); - result = cstring_to_text(dp); - if (dp != sp) - pfree(dp); - if (BIO_free(membuf) != 1) - elog(ERROR, "could not free OpenSSL BIO structure"); + PG_RETURN_NULL(); +} - PG_RETURN_TEXT_P(result); +PG_FUNCTION_INFO_V1(ssl_issuer_field); +Datum +ssl_issuer_field(PG_FUNCTION_ARGS) +{ + PG_RETURN_NULL(); } +#endif /* USE_NSS */ /* @@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn); Datum ssl_client_dn(PG_FUNCTION_ARGS) { - if (!(MyProcPort->peer)) + char subject[NAMEDATALEN]; + + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN); + + if (!*subject) PG_RETURN_NULL(); - return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer)); + + PG_RETURN_TEXT_P(cstring_to_text(subject)); } @@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn); Datum ssl_issuer_dn(PG_FUNCTION_ARGS) { - if (!(MyProcPort->peer)) + char issuer[NAMEDATALEN]; + + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN); + + if (!*issuer) PG_RETURN_NULL(); - return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer)); + + PG_RETURN_TEXT_P(cstring_to_text(issuer)); } +#ifdef USE_OPENSSL /* * Returns information about available SSL extensions. * @@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS) /* All done */ SRF_RETURN_DONE(funcctx); } +#endif /* USE_OPENSSL */ + +#ifdef USE_NSS +PG_FUNCTION_INFO_V1(ssl_extension_info); +Datum +ssl_extension_info(PG_FUNCTION_ARGS) +{ + PG_RETURN_NULL(); +} +#endif /* USE_NSS */ diff --git a/doc/src/sgml/acronyms.sgml b/doc/src/sgml/acronyms.sgml index 4e5ec983c0..4f6f0cf353 100644 --- a/doc/src/sgml/acronyms.sgml +++ b/doc/src/sgml/acronyms.sgml @@ -441,6 +441,28 @@ + + NSPR + + + + Netscape Portable Runtime + + + + + + NSS + + + + Network Security Services + + + + ODBC @@ -539,6 +561,17 @@ + + PKCS#12 + + + + Public-Key Cryptography Standards #12 + + + + PL @@ -684,6 +717,16 @@ + + TLS + + + + Transport Layer Security + + + + TOAST diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index f043433e31..f7ae3b1be1 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1208,6 +1208,23 @@ include_dir 'conf.d' + + ssl_database (string) + + ssl_database configuration parameter + + + + + Specifies the name of the file containing the server certificates and + keys when using NSS for SSL + connections. This parameter can only be set in the + postgresql.conf file or on the server command + line. + + + + ssl_ciphers (string) @@ -1224,7 +1241,9 @@ include_dir 'conf.d' connections using TLS version 1.2 and lower are affected. There is currently no setting that controls the cipher choices used by TLS version 1.3 connections. The default value is - HIGH:MEDIUM:+3DES:!aNULL. The default is usually a + HIGH:MEDIUM:+3DES:!aNULL for servers which have + been built with OpenSSL as the + SSL library. The default is usually a reasonable choice unless you have specific security requirements. @@ -1426,8 +1445,11 @@ include_dir 'conf.d' Sets an external command to be invoked when a passphrase for decrypting an SSL file such as a private key needs to be obtained. By - default, this parameter is empty, which means the built-in prompting - mechanism is used. + default, this parameter is empty. When the server is using + OpenSSL, this means the built-in prompting + mechanism is used. When using NSS, there is + no default prompting so a blank callback will be used returning an + empty password. The command must print the passphrase to the standard output and exit diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml index 0ac1cb9999..bd09124fb0 100644 --- a/doc/src/sgml/installation.sgml +++ b/doc/src/sgml/installation.sgml @@ -250,7 +250,7 @@ su - postgres - You need OpenSSL, if you want to support + You need a supported SSL library, if you want to support encrypted client connections. OpenSSL is also required for random number generation on platforms that do not have /dev/urandom (except Windows). The minimum @@ -966,6 +966,31 @@ build-postgresql: + + + + NSS + NSPR + SSL + + + + + Build with support for SSL (encrypted) + connections using NSS. This requires the + NSS package to be installed. Additionally, + NSS requires NSPR + to be installed. configure will check for the + required header files and libraries to make sure that your + NSS installation is sufficient before + proceeding. + + + This option is incompatible with --with-openssl. + + + + @@ -982,6 +1007,9 @@ build-postgresql: your OpenSSL installation is sufficient before proceeding. + + This option is incompatible with --with-nss. + diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 9ce32fb39b..61d9082052 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2459,6 +2459,8 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name); The struct(s) available depend on the SSL implementation in use. + + For OpenSSL, there is one struct, available under the name "OpenSSL", and it returns a pointer to the OpenSSL SSL struct. @@ -2482,9 +2484,14 @@ void *PQsslStruct(const PGconn *conn, const char *struct_name); ]]> - This structure can be used to verify encryption levels, check server - certificates, and more. Refer to the OpenSSL - documentation for information about this structure. + For NSS, there is one struct available under + the name "NSS", and it returns a pointer to the + NSS PRFileDesc. + + + These structures can be used to verify encryption levels, check server + certificates, and more. Refer to the SSL library + documentation for information about these structures. @@ -2511,6 +2518,10 @@ void *PQgetssl(const PGconn *conn); instead, and for more details about the connection, use . + + This function returns NULL when SSL + librariaes other than OpenSSL are used. + @@ -7943,6 +7954,11 @@ void PQinitOpenSSL(int do_ssl, int do_crypto); before first opening a database connection. Also be sure that you have done that initialization before opening a database connection. + + + This function does nothing when using NSS as + the SSL library. + @@ -7969,6 +7985,11 @@ void PQinitSSL(int do_ssl); might be preferable for applications that need to work with older versions of libpq. + + + This function does nothing when using NSS as + the SSL library. + diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml index 8748c64e2d..f03e1f5fcf 100644 --- a/doc/src/sgml/pgcrypto.sgml +++ b/doc/src/sgml/pgcrypto.sgml @@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea sha224, sha256, sha384 and sha512. If pgcrypto was built with - OpenSSL, more algorithms are available, as - detailed in . + OpenSSL or NSS, + more algorithms are available, as + detailed in . @@ -763,7 +764,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256') Which cipher algorithm to use. -Values: bf, aes128, aes192, aes256 (OpenSSL-only: 3des, cast5) +Values: bf, aes128, aes192, aes256 (OpenSSL-only: 3des, cast5; NSS-only: 3des) Default: aes128 Applies to: pgp_sym_encrypt, pgp_pub_encrypt @@ -1152,8 +1153,8 @@ gen_random_uuid() returns uuid pgcrypto configures itself according to the findings of the main PostgreSQL configure script. The options that - affect it are --with-zlib and - --with-openssl. + affect it are --with-zlib, + --with-openssl and --with-nss. @@ -1168,14 +1169,16 @@ gen_random_uuid() returns uuid BIGNUM functions. - - Summary of Functionality with and without OpenSSL - +
+ Summary of Functionality with and without external cryptographic + library + Functionality Built-in With OpenSSL + With NSS @@ -1183,51 +1186,67 @@ gen_random_uuid() returns uuid MD5yesyes + yes SHA1 yes yes + yes SHA224/256/384/512 yes yes + yes Other digest algorithms no yes (Note 1) + yes (Note 1) Blowfish yes yes + yes AES yes yes + yes DES/3DES/CAST5 no yes + yes + + + CAST5 + no + yes + no Raw encryption yes yes + yes PGP Symmetric encryption yes yes + yes PGP Public-Key encryption yes yes + yes @@ -1240,7 +1259,8 @@ gen_random_uuid() returns uuid - Any digest algorithm OpenSSL supports + Any digest algorithm OpenSSL and + NSS supports is automatically picked up. This is not possible with ciphers, which need to be supported explicitly. diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 17e938148c..fe7e305b75 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2183,15 +2183,21 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 SSL + TLS PostgreSQL has native support for using SSL connections to encrypt client/server communications for increased security. This requires that - OpenSSL is installed on both client and + a supported TLS library is installed on both client and server systems and that support in PostgreSQL is enabled at build time (see ). + Supported libraries are OpenSSL and + NSS. The terms SSL and + TLS are often used interchangeably to mean a secure + connection using a TLS protocol, even though + SSL protocols are no longer supported. @@ -2211,8 +2217,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 - To start in SSL mode, files containing the server certificate - and private key must exist. By default, these files are expected to be + To start in SSL mode, a server certificate + and private key must exist. The below sections on the different libraries + will discuss how to configure these. + + + + By default, these files are expected to be named server.crt and server.key, respectively, in the server's data directory, but other names and locations can be specified using the configuration parameters @@ -2302,6 +2313,18 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 + + NSS Configuration + + + PostgreSQL will look for certificates and keys + in the NSS database specified by the parameter + in postgresql.conf. + The paramaters for certificate and key filenames are used to identify the + nicknames in the database. + + + Using Client Certificates @@ -2376,7 +2399,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 - SSL Server File Usage + SSL Server File Parameter Usage summarizes the files that are @@ -2423,6 +2446,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 client certificate must not be on this list + + + certificate database + contains server certificates, keys and revocation lists; only + used when PostgreSQL is built with support + for NSS. + +
@@ -2550,6 +2581,45 @@ openssl x509 -req -in server.csr -text -days 365 \ + + NSS Certificate Databases + + + When using NSS, all certificates and keys must + be loaded into an NSS certificate database. + + + + To create a new NSS certificate database and + load the certificates created in , + use the following NSS commands: + +certutil -d "sql:server.db" -N --empty-password +certutil -d "sql:server.db" -A -n server.crt -i server.crt -t "CT,C,C" +certutil -d "sql:server.db" -A -n root.crt -i root.crt -t "CT,C,C" + + This will give the certificate the filename as the nickname identifier in + the database which is created as server.db. + + + Then load the server key, which require converting it to + PKCS#12 format using the + OpenSSL tools: + +openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt \ + -certfile root.crt -passout pass: +pk12util -i server.pfx -d server.db -W '' + + + + Finally a certificate revocation list can be loaded with the following + commands: + +crlutil -I -i server.crl -d server.db -B + + + + diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml index e16f61b41d..253bb697af 100644 --- a/doc/src/sgml/sslinfo.sgml +++ b/doc/src/sgml/sslinfo.sgml @@ -22,7 +22,8 @@ This extension won't build at all unless the installation was - configured with --with-openssl. + configured with SSL support, such as --with-openssl + or --with-nss. @@ -54,7 +55,7 @@ Returns the name of the protocol used for the SSL connection (e.g., TLSv1.0 - TLSv1.1, or TLSv1.2). + TLSv1.1, TLSv1.2 or TLSv1.3). @@ -208,6 +209,9 @@ emailAddress the X.500 and X.509 standards, so you cannot just assign arbitrary meaning to them. + + This function is only available when using OpenSSL. + @@ -223,6 +227,9 @@ emailAddress Same as ssl_client_dn_field, but for the certificate issuer rather than the certificate subject. + + This function is only available when using OpenSSL. + @@ -238,6 +245,9 @@ emailAddress Provide information about extensions of client certificate: extension name, extension value, and if it is a critical extension. + + This function is only available when using OpenSSL. + diff --git a/src/Makefile.global.in b/src/Makefile.global.in index 7ca1e9aac5..8946fa696a 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -184,6 +184,7 @@ with_perl = @with_perl@ with_python = @with_python@ with_tcl = @with_tcl@ with_openssl = @with_openssl@ +with_nss = @with_nss@ with_readline = @with_readline@ with_selinux = @with_selinux@ with_systemd = @with_systemd@ @@ -232,6 +233,15 @@ CLANG = @CLANG@ BITCODE_CFLAGS = @BITCODE_CFLAGS@ BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@ +ifeq ($(with_openssl),yes) +with_ssl = yes +else ifeq ($(with_nss),yes) +with_ssl = yes +else +with_ssl = no +endif + + ########################################################################## # # Programs and flags diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index efc5ef760a..191266a426 100644 --- a/src/backend/libpq/Makefile +++ b/src/backend/libpq/Makefile @@ -30,6 +30,10 @@ OBJS = \ ifeq ($(with_openssl),yes) OBJS += be-secure-openssl.o +else +ifeq ($(with_nss),yes) +OBJS += be-secure-nss.o +endif endif ifeq ($(with_gssapi),yes) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index d132c5cb48..d476101583 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port) { int status_check_usermap = STATUS_ERROR; +#if defined(USE_OPENSSL) Assert(port->ssl); +#elif defined(USE_NSS) + /* TODO: should we rename pr_fd to ssl, to keep consistency? */ + Assert(port->pr_fd); +#else + Assert(false); +#endif /* Make sure we have received a username in the certificate */ if (port->peer_cn == NULL || diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c new file mode 100644 index 0000000000..2b184be3d1 --- /dev/null +++ b/src/backend/libpq/be-secure-nss.c @@ -0,0 +1,1175 @@ +/*------------------------------------------------------------------------- + * + * be-secure-nss.c + * functions for supporting NSS as a TLS backend + * + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/libpq/be-secure-nss.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include + +/* + * 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 + +/* + * Ensure that the colliding definitions match, else throw an error. In case + * NSPR has removed the definition for some reason, make sure to put ours + * back again. + */ +#if defined(BITS_PER_BYTE) +#if BITS_PER_BYTE != pg_BITS_PER_BYTE +#error "incompatible byte widths between NSPR and postgres" +#endif +#else +#define BITS_PER_BYTE pg_BITS_PER_BYTE +#endif +#undef pg_BITS_PER_BYTE + +#include "common/pg_nss.h" +#include "lib/stringinfo.h" +#include "libpq/libpq.h" +#include "nodes/pg_list.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/guc.h" +#include "utils/memutils.h" + + +/* default init hook can be overridden by a shared library */ +static void default_nss_tls_init(bool isServerStart); +nss_tls_init_hook_type nss_tls_init_hook = default_nss_tls_init; + +static PRDescIdentity pr_id; + +static PRIOMethods pr_iomethods; +static NSSInitContext * nss_context = NULL; +static SSLVersionRange desired_sslver; + +static char *external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg); +static bool dummy_ssl_passwd_cb_called = false; +static bool ssl_is_server_start; + +/* + * PR_ImportTCPSocket() is a private API, but very widely used, as it's the + * only way to make NSS use an already set up POSIX file descriptor rather + * than opening one itself. To quote the NSS documentation: + * + * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's + * implementation changes. In practice, this is unlikely to happen because + * NSPR's implementation has been stable for years and because of NSPR's + * strong commitment to backward compatibility." + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket + * + * The function is declared in , but as it is a header marked + * private we declare it here rather than including it. + */ +NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int); + +/* NSS IO layer callback overrides */ +static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout); +static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout); +/* Utility functions */ +static PRFileDesc * init_iolayer(Port *port); +static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name); + +static char *pg_SSLerrmessage(PRErrorCode errcode); +static char *ssl_protocol_version_to_string(int v); +static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, + PRBool checksig, PRBool isServer); +static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd); +static char *dummy_ssl_passphrase_cb(PK11SlotInfo * slot, PRBool retry, void *arg); + +/* ------------------------------------------------------------ */ +/* Public interface */ +/* ------------------------------------------------------------ */ + +/* + * be_tls_init + * Initialize the nss TLS library in the postmaster + * + * The majority of the setup needs to happen in be_tls_open_server since the + * NSPR initialization must happen after the forking of the backend. We could + * potentially move some parts in under !isServerStart, but so far this is the + * separation chosen. + */ +int +be_tls_init(bool isServerStart) +{ + SECStatus status; + SSLVersionRange supported_sslver; + + /* + * Set up the connection cache for multi-processing application behavior. + * If we are in ServerStart then we initialize the cache. If the server is + * already started, we inherit the cache such that it can be used for + * connections. Calling SSL_ConfigMPServerSIDCache sets an environment + * variable which contains enough information for the forked child to know + * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will + * make the forked child look it up by the default name SSL_INHERITANCE, + * if env vars aren't inherited then the contents of the variable can be + * passed instead. + */ + if (isServerStart) + { + /* + * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting + * up the required session cache for NSS we still must supply timeout + * values for v2 and The minimum allowed value for both is 5 seconds, + * so opt for that in both cases (the defaults being 100 seconds and + * 24 hours). + * + * Passing NULL as the directory for the session cache will default to + * using /tmp on UNIX and \\temp on Windows. Deciding if we want to + * keep closer control on this directory is left as a TODO. + */ + status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL); + if (status != SECSuccess) + ereport(FATAL, + (errmsg("unable to set up TLS connection cache: %s", + pg_SSLerrmessage(PR_GetError())))); + + } + else + { + status = SSL_InheritMPServerSIDCache(NULL); + if (status != SECSuccess) + { + ereport(LOG, + (errmsg("unable to connect to TLS connection cache: %s", + pg_SSLerrmessage(PR_GetError())))); + return -1; + } + } + + if (!ssl_database || strlen(ssl_database) == 0) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("no certificate database specified"))); + goto error; + } + + /* + * We check for the desired TLS version range here, even though we cannot + * set it until be_open_server such that we can be compatible with how the + * OpenSSL backend reports errors for incompatible range configurations. + * Set either the default supported TLS version range, or the configured + * range from ssl_min_protocol_version and ssl_max_protocol version. In + * case the user hasn't defined the maximum allowed version we fall back + * to the highest version TLS that the library supports. + */ + if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != SECSuccess) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("unable to get default protocol support from NSS"))); + goto error; + } + + /* + * Set the fallback versions for the TLS protocol version range to a + * combination of our minimal requirement and the library maximum. + */ + desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0; + desired_sslver.max = supported_sslver.max; + + if (ssl_min_protocol_version) + { + int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version, + "ssl_min_protocol_version"); + + if (ver == -1) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("\"%s\" setting \"%s\" not supported by this build", + "ssl_min_protocol_version", + GetConfigOption("ssl_min_protocol_version", + false, false)))); + goto error; + } + + if (ver > 0) + desired_sslver.min = ver; + } + + if (ssl_max_protocol_version) + { + int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version, + "ssl_max_protocol_version"); + + if (ver == -1) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("\"%s\" setting \"%s\" not supported by this build", + "ssl_max_protocol_version", + GetConfigOption("ssl_max_protocol_version", + false, false)))); + goto error; + } + if (ver > 0) + desired_sslver.max = ver; + + if (ver < desired_sslver.min) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("could not set SSL protocol version range"), + errdetail("\"%s\" cannot be higher than \"%s\"", + "ssl_min_protocol_version", + "ssl_max_protocol_version"))); + goto error; + } + } + + /* + * Set the passphrase callback which will be used both to obtain the + * passphrase from the user, as well as by NSS to obtain the phrase + * repeatedly. + */ + ssl_is_server_start = isServerStart; + (*nss_tls_init_hook) (isServerStart); + + return 0; +error: + return -1; +} + +int +be_tls_open_server(Port *port) +{ + SECStatus status; + PRFileDesc *model; + PRFileDesc *pr_fd; + PRFileDesc *layer; + CERTCertificate *server_cert; + SECKEYPrivateKey *private_key; + CERTSignedCrl *crl; + SECItem crlname; + char *cert_database; + NSSInitParameters params; + + /* + * The NSPR documentation states that runtime initialization via PR_Init + * is no longer required, as the first caller into NSPR will perform the + * initialization implicitly. The documentation doesn't however clarify + * from which version this is holds true, so let's perform the potentially + * superfluous initialization anyways to avoid crashing on older versions + * of NSPR, as there is no difference in overhead. The NSS documentation + * still states that PR_Init must be called in some way (implicitly or + * explicitly). + * + * The below parameters are what the implicit initialization would've done + * for us, and should work even for older versions where it might not be + * done automatically. The last parameter, maxPTDs, is set to various + * values in other codebases, but has been unused since NSPR 2.1 which was + * released sometime in 1998. In current versions of NSPR all parameters + * are ignored. + */ + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ ); + + /* + * The certificate path (configdir) must contain a valid NSS database. If + * the certificate path isn't a valid directory, NSS will fall back on the + * system certificate database. If the certificate path is a directory but + * is empty then the initialization will fail. On the client side this can + * be allowed for any sslmode but the verify-xxx ones. + * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side + * we wont allow this to fail however, as we require the certificate and + * key to exist. + * + * The original design of NSS was for a single application to use a single + * copy of it, initialized with NSS_Initialize() which isn't returning any + * handle with which to refer to NSS. NSS initialization and shutdown are + * global for the application, so a shutdown in another NSS enabled + * library would cause NSS to be stopped for libpq as well. The fix has + * been to introduce NSS_InitContext which returns a context handle to + * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS + * 3.12, but the use of it is not very well documented. + * https://bugzilla.redhat.com/show_bug.cgi?id=738456 + * + * The InitParameters struct passed can be used to override internal + * values in NSS, but the usage is not documented at all. When using + * NSS_Init initializations, the values are instead set via PK11_Configure + * calls so the PK11_Configure documentation can be used to glean some + * details on these. + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs + */ + memset(¶ms, '\0', sizeof(params)); + params.length = sizeof(params); + + if (!ssl_database || strlen(ssl_database) == 0) + ereport(FATAL, + (errmsg("no certificate database specified"))); + + cert_database = psprintf("sql:%s", ssl_database); + nss_context = NSS_InitContext(cert_database, "", "", "", + ¶ms, + NSS_INIT_READONLY | NSS_INIT_PK11RELOAD); + pfree(cert_database); + + if (!nss_context) + ereport(FATAL, + (errmsg("unable to read certificate database \"%s\": %s", + ssl_database, pg_SSLerrmessage(PR_GetError())))); + + /* + * Import the already opened socket as we don't want to use NSPR functions + * for opening the network socket due to how the PostgreSQL protocol works + * with TLS connections. This function is not part of the NSPR public API, + * see the comment at the top of the file for the rationale of still using + * it. + */ + pr_fd = PR_ImportTCPSocket(port->sock); + if (!pr_fd) + ereport(ERROR, + (errmsg("unable to connect to socket"))); + + /* + * Most of the documentation available, and implementations of, NSS/NSPR + * use the PR_NewTCPSocket() function here, which has the drawback that it + * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which + * copes with IPv6 as well. + * + * We use a model filedescriptor here which is a construct in NSPR/NSS in + * order to create a configuration template for sockets which can then be + * applied to new sockets created. This makes more sense in a server which + * accepts multiple connections and want to perform the boilerplate just + * once, but it does provide a nice abstraction here aswell in that we can + * error out early without having performed any operation on the real + * socket. + */ + model = PR_OpenTCPSocket(port->laddr.addr.ss_family); + if (!model) + ereport(ERROR, + (errmsg("unable to open socket"))); + + /* + * Convert the NSPR socket to an SSL socket. Ensuring the success of this + * operation is critical as NSS SSL_* functions may return SECSuccess on + * the socket even though SSL hasn't been enabled, which introduce a risk + * of silent downgrades. + */ + model = SSL_ImportFD(NULL, model); + if (!model) + ereport(ERROR, + (errmsg("unable to enable TLS on socket"))); + + /* + * Configure basic settings for the connection over the SSL socket in + * order to set it up as a server. + */ + if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess) + ereport(ERROR, + (errmsg("unable to configure TLS connection"))); + + if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess || + SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess) + ereport(ERROR, + (errmsg("unable to configure TLS connection as server"))); + + /* + * SSLv2 is disabled by default, and SSLv3 will be excluded from the range + * of allowed protocols further down. Since we really don't want these to + * ever be enabled, let's use belts and suspenders and explicitly turn + * them off as well. + */ + SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE); + SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE); + +#ifdef SSL_CBC_RANDOM_IV + + /* + * Enable protection against the BEAST attack in case the NSS server has + * support for that. While SSLv3 is disabled, we may still allow TLSv1 + * which is affected. The option isn't documented as an SSL option, but as + * an NSS environment variable. + */ + SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE); +#endif + + /* + * Configure the allowed ciphers. If there are no user preferred suites, + * set the domestic policy. + * + * Historically there were different cipher policies based on export (and + * import) restrictions: Domestic, Export and France. These are since long + * removed with all ciphers being enabled by default. Due to backwards + * compatibility, the old API is still used even though all three policies + * now do the same thing. + * + * If SSLCipherSuites define a policy of the user, we set that rather than + * enabling all ciphers via NSS_SetDomesticPolicy. + * + * TODO: while this code works, the set of ciphers which can be set and + * still end up with a working socket is woefully underdocumented for + * anything more recent than SSLv3 (the code for TLS actually calls ssl3 + * functions under the hood for SSL_CipherPrefSet), so it's unclear if this + * is helpful or not. Using the policies works, but may be too coarsely + * grained. + * + * Another TODO: The SSL_ImplementedCiphers table returned with calling + * SSL_GetImplementedCiphers is sorted in server preference order. Sorting + * SSLCipherSuites according to the order of the ciphers therein could be + * a way to implement ssl_prefer_server_ciphers - if we at all want to use + * cipher selection for NSS like how we do it for OpenSSL that is. + */ + + /* + * If no ciphers are specified, enable them all. + */ + if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0) + { + status = NSS_SetDomesticPolicy(); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to set cipher policy: %s", + pg_SSLerrmessage(PR_GetError())))); + } + else + { + char *ciphers, + *c; + + char *sep = ":;, "; + PRUint16 ciphercode; + const PRUint16 *nss_ciphers; + + /* + * If the user has specified a set of preferred cipher suites we start + * by turning off all the existing suites to avoid the risk of down- + * grades to a weaker cipher than expected. + */ + nss_ciphers = SSL_GetImplementedCiphers(); + for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++) + SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE); + + ciphers = pstrdup(SSLCipherSuites); + + for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep)) + { + ciphercode = pg_find_cipher(c); + if (ciphercode != INVALID_CIPHER) + { + status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("invalid cipher-suite specified: %s", c))); + } + } + + pfree(ciphers); + } + + if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess) + ereport(ERROR, + (errmsg("unable to set requested SSL protocol version range"))); + + /* + * Set up the custom IO layer. + */ + layer = init_iolayer(port); + if (!layer) + goto error; + + /* Store the Port as private data available in callbacks */ + layer->secret = (void *) port; + + if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS) + { + PR_Close(layer); + ereport(ERROR, + (errmsg("unable to push IO layer"))); + } + + server_cert = PK11_FindCertFromNickname(ssl_cert_file, (void *) port); + if (!server_cert) + { + if (dummy_ssl_passwd_cb_called) + ereport(ERROR, + (errmsg("unable to load certificate for \"%s\": %s", + ssl_cert_file, pg_SSLerrmessage(PR_GetError())), + errhint("The certificate requires a password."))); + else + ereport(ERROR, + (errmsg("unable to find certificate for \"%s\": %s", + ssl_cert_file, pg_SSLerrmessage(PR_GetError())))); + } + + private_key = PK11_FindKeyByAnyCert(server_cert, (void *) port); + if (!private_key) + { + if (dummy_ssl_passwd_cb_called) + ereport(ERROR, + (errmsg("unable to load private key for \"%s\": %s", + ssl_cert_file, pg_SSLerrmessage(PR_GetError())), + errhint("The private key requires a password."))); + else + ereport(ERROR, + (errmsg("unable to find private key for \"%s\": %s", + ssl_cert_file, pg_SSLerrmessage(PR_GetError())))); + } + + /* + * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to + * contain the CRL nickname for the current server certificate in the NSS + * certificate database. The main difference from the OpenSSL backend is + * that NSS will use the CRL regardless, but being able to make sure the + * CRL is loaded seems like a good feature. + */ + if (ssl_crl_file[0]) + { + SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject); + crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE); + if (!crl) + ereport(ERROR, + (errmsg("specified CRL not found in database"))); + SEC_DestroyCrl(crl); + } + + /* + * Finally we must configure the socket for being a server by setting the + * certificate and key. + */ + status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to configure secure server: %s", + pg_SSLerrmessage(PR_GetError())))); + status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to configure server for TLS server connections: %s", + pg_SSLerrmessage(PR_GetError())))); + + ssl_loaded_verify_locations = true; + + /* + * At this point, we no longer have use for the certificate and private + * key as they have been copied into the context by NSS. Destroy our + * copies explicitly to clean out the memory as best we can. + */ + CERT_DestroyCertificate(server_cert); + SECKEY_DestroyPrivateKey(private_key); + + status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to install authcert hook: %s", + pg_SSLerrmessage(PR_GetError())))); + SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port); + SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE); + SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE); + + port->pr_fd = SSL_ImportFD(model, pr_fd); + if (!port->pr_fd) + ereport(ERROR, + (errmsg("unable to initialize"))); + + PR_Close(model); + + /* + * Force a handshake on the next I/O request, the second parameter means + * that we are a server, PR_FALSE would indicate being a client. NSPR + * requires us to call SSL_ResetHandshake since we imported an already + * established socket. + */ + status = SSL_ResetHandshake(port->pr_fd, PR_TRUE); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to initiate handshake: %s", + pg_SSLerrmessage(PR_GetError())))); + status = SSL_ForceHandshake(port->pr_fd); + if (status != SECSuccess) + ereport(ERROR, + (errmsg("unable to handshake: %s", + pg_SSLerrmessage(PR_GetError())))); + + port->ssl_in_use = true; + return 0; + +error: + return 1; +} + +ssize_t +be_tls_read(Port *port, void *ptr, size_t len, int *waitfor) +{ + ssize_t n_read; + PRErrorCode err; + + n_read = PR_Read(port->pr_fd, ptr, len); + + if (n_read < 0) + { + err = PR_GetError(); + + if (err == PR_WOULD_BLOCK_ERROR) + { + *waitfor = WL_SOCKET_READABLE; + errno = EWOULDBLOCK; + } + else + errno = ECONNRESET; + } + + return n_read; +} + +ssize_t +be_tls_write(Port *port, void *ptr, size_t len, int *waitfor) +{ + ssize_t n_write; + PRErrorCode err; + PRIntn flags = 0; + + /* + * The flags parameter to PR_Send is no longer used and is, according to + * the documentation, required to be zero. + */ + n_write = PR_Send(port->pr_fd, ptr, len, flags, PR_INTERVAL_NO_WAIT); + + if (n_write < 0) + { + err = PR_GetError(); + + if (err == PR_WOULD_BLOCK_ERROR) + { + *waitfor = WL_SOCKET_WRITEABLE; + errno = EWOULDBLOCK; + } + else + errno = ECONNRESET; + } + + return n_write; +} + +void +be_tls_close(Port *port) +{ + if (!port) + return; + + if (port->peer_cn) + { + SSL_InvalidateSession(port->pr_fd); + pfree(port->peer_cn); + port->peer_cn = NULL; + } + + PR_Close(port->pr_fd); + port->pr_fd = NULL; + port->ssl_in_use = false; + + if (nss_context) + { + NSS_ShutdownContext(nss_context); + nss_context = NULL; + } +} + +void +be_tls_destroy(void) +{ + /* + * It reads a bit odd to clear a session cache when we are destroying the + * context altogether, but if the session cache isn't cleared before + * shutting down the context it will fail with SEC_ERROR_BUSY. + */ + SSL_ClearSessionCache(); +} + +int +be_tls_get_cipher_bits(Port *port) +{ + SECStatus status; + SSLChannelInfo channel; + SSLCipherSuiteInfo suite; + + status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel)); + if (status != SECSuccess) + goto error; + + status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite)); + if (status != SECSuccess) + goto error; + + return suite.effectiveKeyBits; + +error: + ereport(WARNING, + (errmsg("unable to extract TLS session information: %s", + pg_SSLerrmessage(PR_GetError())))); + return 0; +} + +/* + * be_tls_get_compression + * + * NSS disabled support for TLS compression in version 3.33 and removed the + * code in a subsequent release. The API for retrieving information about + * compression as well as enabling it is kept for backwards compatibility, but + * we don't need to consult it since it was only available for SSLv3 which we + * don't support. + * + * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587 + */ +bool +be_tls_get_compression(Port *port) +{ + return false; +} + +/* + * be_tls_get_version + * + * Returns the protocol version used for the current connection, or NULL in + * case of errors. + */ +const char * +be_tls_get_version(Port *port) +{ + SECStatus status; + SSLChannelInfo channel; + + status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel)); + if (status != SECSuccess) + { + ereport(WARNING, + (errmsg("unable to extract TLS session information: %s", + pg_SSLerrmessage(PR_GetError())))); + return NULL; + } + + return ssl_protocol_version_to_string(channel.protocolVersion); +} + +const char * +be_tls_get_cipher(Port *port) +{ + SECStatus status; + SSLChannelInfo channel; + SSLCipherSuiteInfo suite; + + status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel)); + if (status != SECSuccess) + goto error; + + status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite)); + if (status != SECSuccess) + goto error; + + return suite.cipherSuiteName; + +error: + ereport(WARNING, + (errmsg("unable to extract TLS session information: %s", + pg_SSLerrmessage(PR_GetError())))); + return NULL; +} + +void +be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len) +{ + CERTCertificate *certificate; + + certificate = SSL_PeerCertificate(port->pr_fd); + if (certificate) + strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len); + else + ptr[0] = '\0'; +} + +void +be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len) +{ + CERTCertificate *certificate; + + certificate = SSL_PeerCertificate(port->pr_fd); + if (certificate) + strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len); + else + ptr[0] = '\0'; +} + +void +be_tls_get_peer_serial(Port *port, char *ptr, size_t len) +{ + CERTCertificate *certificate; + + certificate = SSL_PeerCertificate(port->pr_fd); + if (certificate) + snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber))); + else + ptr[0] = '\0'; +} + +/* ------------------------------------------------------------ */ +/* Internal functions */ +/* ------------------------------------------------------------ */ + +/* + * default_nss_tls_init + * + * The default TLS init hook function which users can override for installing + * their own passphrase callbacks and similar actions. In case no callback has + * been configured, or the callback isn't reload capable during a server + * reload, the dummy callback will be installed. + * + * The private data for the callback is set differently depending on how it's + * invoked. For calls which may invoke the callback deeper in the callstack + * the private data is set with SSL_SetPKCS11PinArg. When the call is directly + * invoking the callback, like PK11_FindCertFromNickname, then the private + * data is passed as a parameter. Setting the data with SSL_SetPKCS11PinArg is + * thus not required but good practice. + * + * NSS doesn't provide a default callback like OpenSSL does, but a callback is + * required to be set. The password callback can be installed at any time, but + * setting the private data with SSL_SetPKCS11PinArg requires a PR Filedesc. + */ +static void +default_nss_tls_init(bool isServerStart) +{ + /* + * No user-defined callback has been configured, install the dummy call- + * back since we must set something. + */ + if (!ssl_passphrase_command[0]) + PK11_SetPasswordFunc(dummy_ssl_passphrase_cb); + else + { + /* + * There is a user-defined callback, set it unless we are in a restart + * and cannot handle restarts due to an interactive callback. + */ + if (isServerStart) + PK11_SetPasswordFunc(external_ssl_passphrase_cb); + else + { + if (ssl_passphrase_command_supports_reload) + PK11_SetPasswordFunc(external_ssl_passphrase_cb); + else + PK11_SetPasswordFunc(dummy_ssl_passphrase_cb); + } + } +} + +/* + * external_ssl_passphrase_cb + * + * Runs the callback configured by ssl_passphrase_command and returns the + * captured password back to NSS. + */ +static char * +external_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + /* + * NSS use a hardcoded 256 byte buffer for reading the password so set the + * same limit for our callback buffer. + */ + char buf[256]; + int len; + char *password = NULL; + char *prompt; + + /* + * Since there is no password callback in NSS when the server starts up, + * it makes little sense to create an interactive callback. Thus, if this + * is a retry attempt then give up immediately. + */ + if (retry) + return NULL; + + /* + * Construct the same prompt that NSS uses internally even though it is + * unlikely to serve much purpose, but we must set a prompt so we might + * as well do it right. + */ + prompt = psprintf("Enter Password or Pin for \"%s\":", + PK11_GetTokenName(slot)); + + len = run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf)); + pfree(prompt); + + if (!len) + return NULL; + + /* + * At least one byte with password content was returned, and NSS requires + * that we return it allocated in NSS controlled memory. If we fail to + * allocate then abort without passing back NULL and bubble up the error + * on the PG side. + */ + password = (char *) PR_Malloc(len + 1); + if (!password) + { + explicit_bzero(buf, sizeof(buf)); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + } + strlcpy(password, buf, sizeof(password)); + explicit_bzero(buf, sizeof(buf)); + + return password; +} + +/* + * dummy_ssl_passphrase_cb + * + * Return unsuccessful if we are asked to provide the passphrase for a cert or + * key, without having a passphrase callback installed. + */ +static char * +dummy_ssl_passphrase_cb(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + dummy_ssl_passwd_cb_called = true; + return NULL; +} + +static SECStatus +pg_bad_cert_handler(void *arg, PRFileDesc * fd) +{ + Port *port = (Port *) arg; + + port->peer_cert_valid = false; + return SECFailure; +} + +static SECStatus +pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer) +{ + SECStatus status; + Port *port = (Port *) arg; + CERTCertificate *cert; + char *peer_cn; + int len; + + status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, PR_TRUE); + if (status == SECSuccess) + { + cert = SSL_PeerCertificate(port->pr_fd); + len = strlen(cert->subjectName); + peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1); + /* + * Skip over the key= portion of the key=value containing the the + * peer CN. + */ + if (strncmp(cert->subjectName, "CN=", 3) == 0) + strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1); + else + strlcpy(peer_cn, cert->subjectName, len + 1); + CERT_DestroyCertificate(cert); + + port->peer_cn = peer_cn; + port->peer_cert_valid = true; + } + + return status; +} + +static PRInt32 +pg_ssl_read(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRRecvFN read_fn; + PRInt32 n_read; + + read_fn = fd->lower->methods->recv; + n_read = read_fn(fd->lower, buf, amount, flags, timeout); + + return n_read; +} + +static PRInt32 +pg_ssl_write(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, + PRIntervalTime timeout) +{ + PRSendFN send_fn; + PRInt32 n_write; + + send_fn = fd->lower->methods->send; + n_write = send_fn(fd->lower, buf, amount, flags, timeout); + + return n_write; +} + +static PRFileDesc * +init_iolayer(Port *port) +{ + const PRIOMethods *default_methods; + PRFileDesc *layer; + + /* + * Start by initializing our layer with all the default methods so that we + * can selectively override the ones we want while still ensuring that we + * have a complete layer specification. + */ + default_methods = PR_GetDefaultIOMethods(); + memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods)); + + pr_iomethods.recv = pg_ssl_read; + pr_iomethods.send = pg_ssl_write; + + /* + * Each IO layer must be identified by a unique name, where uniqueness is + * per connection. Each connection in a postgres cluster can generate the + * identity from the same string as they will create their IO layers on + * different sockets. Only one layer per socket can have the same name. + */ + pr_id = PR_GetUniqueIdentity("PostgreSQL Server"); + if (pr_id == PR_INVALID_IO_LAYER) + { + ereport(ERROR, + (errmsg("out of memory when setting up TLS connection"))); + return NULL; + } + + /* + * Create the actual IO layer as a stub such that it can be pushed onto + * the layer stack. The step via a stub is required as we define custom + * callbacks. + */ + layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods); + if (!layer) + { + ereport(ERROR, + (errmsg("unable to create NSS I/O layer"))); + return NULL; + } + + return layer; +} + +static char * +ssl_protocol_version_to_string(int v) +{ + switch (v) + { + /* SSL v2 and v3 are not supported */ + case SSL_LIBRARY_VERSION_2: + case SSL_LIBRARY_VERSION_3_0: + Assert(false); + break; + + case SSL_LIBRARY_VERSION_TLS_1_0: + return pstrdup("TLSv1.0"); + case SSL_LIBRARY_VERSION_TLS_1_1: + return pstrdup("TLSv1.1"); + case SSL_LIBRARY_VERSION_TLS_1_2: + return pstrdup("TLSv1.2"); + case SSL_LIBRARY_VERSION_TLS_1_3: + return pstrdup("TLSv1.3"); + } + + return pstrdup("unknown"); +} + + +/* + * ssl_protocol_version_to_nss + * Translate PostgreSQL TLS version to NSS version + * + * Returns zero in case the requested TLS version is undefined (PG_ANY) and + * should be set by the caller, or -1 on failure. + */ +static uint16 +ssl_protocol_version_to_nss(int v, const char *guc_name) +{ + switch (v) + { + /* + * There is no SSL_LIBRARY_ macro defined in NSS with the value + * zero, so we use this to signal the caller that the highest + * useful version should be set on the connection. + */ + case PG_TLS_ANY: + return 0; + + /* + * No guard is required here as there are no versions of NSS + * without support for TLS1. + */ + case PG_TLS1_VERSION: + return SSL_LIBRARY_VERSION_TLS_1_0; + case PG_TLS1_1_VERSION: +#ifdef SSL_LIBRARY_VERSION_TLS_1_1 + return SSL_LIBRARY_VERSION_TLS_1_1; +#else + break; +#endif + case PG_TLS1_2_VERSION: +#ifdef SSL_LIBRARY_VERSION_TLS_1_2 + return SSL_LIBRARY_VERSION_TLS_1_2; +#else + break; +#endif + case PG_TLS1_3_VERSION: +#ifdef SSL_LIBRARY_VERSION_TLS_1_3 + return SSL_LIBRARY_VERSION_TLS_1_3; +#else + break; +#endif + default: + break; + } + + return -1; +} + +/* + * pg_SSLerrmessage + * Create and return a human readable error message given + * the specified error code + * + * PR_ErrorToName only converts the enum identifier of the error to string, + * but that can be quite useful for debugging (and in case PR_ErrorToString is + * unable to render a message then we at least have something). + */ +static char * +pg_SSLerrmessage(PRErrorCode errcode) +{ + return psprintf("%s (%s)", + PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT), + PR_ErrorToName(errcode)); +} diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index 8b21ff4065..5962cffc0c 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name) char *dp; char *result; + if (membuf == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("failed to create BIO"))); + (void) BIO_set_close(membuf, BIO_CLOSE); for (i = 0; i < count; i++) { e = X509_NAME_get_entry(name, i); nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e)); + if (nid == NID_undef) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not get NID for ASN1_OBJECT object"))); v = X509_NAME_ENTRY_get_data(e); field_name = OBJ_nid2sn(nid); if (!field_name) field_name = OBJ_nid2ln(nid); + if (field_name == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid))); BIO_printf(membuf, "/%s=", field_name); ASN1_STRING_print_ex(membuf, v, ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB) @@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name) result = pstrdup(dp); if (dp != sp) pfree(dp); - BIO_free(membuf); + if (BIO_free(membuf) != 1) + elog(ERROR, "could not free OpenSSL BIO structure"); return result; } diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 2ae507a902..f39977b80c 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload; #ifdef USE_SSL bool ssl_loaded_verify_locations = false; #endif +#ifdef USE_NSS +char *ssl_database; +#endif /* GUC variable controlling SSL cipher list */ char *SSLCipherSuites = NULL; diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 4c86fb6087..8821c60a34 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1040,7 +1040,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) ereport(elevel, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("hostssl record cannot match because SSL is not supported by this build"), - errhint("Compile with --with-openssl to use SSL connections."), + errhint("Compile with --with-openssl or --with-nss to use SSL connections."), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); *err_msg = "hostssl record cannot match because SSL is not supported by this build"; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index a62d64eaa4..050f673c68 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] = }, &ssl_library, #ifdef USE_SSL +#if defined(USE_OPENSSL) "OpenSSL", +#elif defined(USE_NSS) + "NSS", +#endif #else "", #endif @@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] = check_canonical_path, assign_pgstat_temp_directory, NULL }, +#ifdef USE_NSS + { + {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the NSS certificate database."), + NULL + }, + &ssl_database, + "", + NULL, NULL, NULL + }, +#endif + { {"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY, gettext_noop("Number of synchronous standbys and list of names of potential synchronous ones."), @@ -4348,8 +4364,10 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &SSLCipherSuites, -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) "HIGH:MEDIUM:+3DES:!aNULL", +#elif defined (USE_NSS) + "", #else "none", #endif diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h new file mode 100644 index 0000000000..9205f0fc3b --- /dev/null +++ b/src/include/common/pg_nss.h @@ -0,0 +1,141 @@ +/*------------------------------------------------------------------------- + * + * pg_nss.h + * Support for NSS as a TLS backend + * + * These definitions are used by both frontend and backend code. + * + * Copyright (c) 2020, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/include/common/pg_nss.h + * + *------------------------------------------------------------------------- + */ +#ifndef PG_NSS_H +#define PG_NSS_H + +#ifdef USE_NSS + +#include + +PRUint16 pg_find_cipher(char *name); + +typedef struct +{ + const char *name; + PRUint16 number; +} NSSCiphers; + +#define INVALID_CIPHER 0xFFFF + +/* + * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h + * in order to provide a human readable version of the ciphers. It would be + * nice to not have to have this, but NSS doesn't provide any API addressing + * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less? + */ +static const NSSCiphers NSS_CipherList[] = { + + {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL}, + + {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5}, + {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA}, + {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5}, + {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA}, + {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA}, + {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA}, + {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA}, + + {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA}, + {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA}, + {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA}, + {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA}, + + {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA}, + {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA}, + {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA}, + {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA}, + + {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5}, + {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA}, + {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA}, + + {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA}, + {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA}, + {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA}, + {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA}, + {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA}, + {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA}, + + {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA}, + {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA}, + {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA}, + {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA}, + {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA}, + {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA}, + {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256}, + {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256}, + {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256}, + + {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256}, + {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA}, + {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA}, + {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA}, + {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA}, + {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA}, + {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA}, + + {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA}, + {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256}, + {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256}, + {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256}, + + {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA}, + {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA}, + {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA}, + {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA}, + {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA}, + {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA}, + + {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA}, + + {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256}, + {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384}, + {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256}, + {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384}, + {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256}, + {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384}, + + {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256}, + {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384}, + {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256}, + {NULL, 0} +}; + +/* + * pg_find_cipher + * Translate an NSS ciphername to the cipher code + * + * Searches the configured ciphers for the corresponding cipher code to the + * name. Search is performed case insensitive. + */ +PRUint16 +pg_find_cipher(char *name) +{ + const NSSCiphers *cipher_list = NSS_CipherList; + + while (cipher_list->name) + { + if (pg_strcasecmp(cipher_list->name, name) == 0) + return cipher_list->number; + + cipher_list++; + } + + return 0xFFFF; +} + +#endif /* USE_NSS */ + +#endif /* PG_NSS_H */ diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 0a23281ad5..b68ef64ec9 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -192,13 +192,18 @@ typedef struct Port bool peer_cert_valid; /* - * OpenSSL structures. (Keep these last so that the locations of other - * fields are the same whether or not you build with OpenSSL.) + * SSL backend specific structures. (Keep these last so that the locations + * of other fields are the same whether or not you build with SSL + * enabled.) */ #ifdef USE_OPENSSL SSL *ssl; X509 *peer; #endif + +#ifdef USE_NSS + void *pr_fd; +#endif } Port; #ifdef USE_SSL @@ -292,6 +297,10 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len); typedef void (*openssl_tls_init_hook_typ) (SSL_CTX *context, bool isServerStart); extern PGDLLIMPORT openssl_tls_init_hook_typ openssl_tls_init_hook; #endif +#ifdef USE_NSS +typedef void (*nss_tls_init_hook_type) (bool isServerStart); +extern PGDLLIMPORT nss_tls_init_hook_type nss_tls_init_hook; +#endif #endif /* USE_SSL */ diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index b1152475ac..298d87ecae 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload; #ifdef USE_SSL extern bool ssl_loaded_verify_locations; #endif +#ifdef USE_NSS +extern char *ssl_database; +#endif extern int secure_initialize(bool isServerStart); extern bool secure_loaded_verify_locations(void); diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index fb270df678..73c39b449c 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -893,6 +893,12 @@ /* Define to 1 to build with PAM support. (--with-pam) */ #undef USE_PAM +/* Define to build with NSS support (--with-nss) */ +#undef USE_NSS + +/* Define to use NSS for random number generation */ +#undef USE_NSS_RANDOM + /* Define to 1 to use software CRC-32C implementation (slicing-by-8). */ #undef USE_SLICING_BY_8_CRC32C diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h index 705dc69c06..c28b84126d 100644 --- a/src/include/pg_config_manual.h +++ b/src/include/pg_config_manual.h @@ -176,10 +176,9 @@ /* * USE_SSL code should be compiled only when compiling with an SSL - * implementation. (Currently, only OpenSSL is supported, but we might add - * more implementations in the future.) + * implementation. */ -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) || defined(USE_NSS) #define USE_SSL #endif diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index 4ac5f4b340..7b396d04af 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -45,9 +45,13 @@ OBJS = \ pqexpbuffer.o \ fe-auth.o +ifeq ($(with_ssl),yes) +OBJS += \ + fe-secure-common.o +endif + ifeq ($(with_openssl),yes) OBJS += \ - fe-secure-common.o \ fe-secure-openssl.o endif @@ -57,6 +61,11 @@ OBJS += \ fe-secure-gssapi.o endif +ifeq ($(with_nss), yes) +OBJS += \ + fe-secure-nss.o +endif + ifeq ($(PORTNAME), cygwin) override shlib = cyg$(NAME)$(DLSUFFIX) endif diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index b0ca37c2ed..a15a89c50c 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -355,6 +355,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */ offsetof(struct pg_conn, target_session_attrs)}, + {"cert_database", NULL, NULL, NULL, + "CertificateDatabase", "", 64, + offsetof(struct pg_conn, cert_database)}, + /* Terminating entry --- MUST BE LAST */ {NULL, NULL, NULL, NULL, NULL, NULL, 0} diff --git a/src/interfaces/libpq/fe-secure-nss.c b/src/interfaces/libpq/fe-secure-nss.c new file mode 100644 index 0000000000..395147f551 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-nss.c @@ -0,0 +1,1018 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-nss.c + * functions for supporting NSS as a TLS backend for frontend libpq + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-secure-nss.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "libpq-fe.h" +#include "fe-auth.h" +#include "fe-secure-common.h" +#include "libpq-int.h" + +/* + * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef + * our version to avoid compiler warnings on redefinition. + */ +#define pg_BITS_PER_BYTE BITS_PER_BYTE +#undef BITS_PER_BYTE + +/* + * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with + * colliding definitions from ours, causing a much expected compiler error. + * Remove backwards compatibility with ancient NSPR versions to avoid this. + */ +#define NO_NSPR_10_SUPPORT +#include +#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; + +/* + * Track whether the NSS database has a password set or not. There is no API + * function for retrieving password status, so we simply flip this to true in + * case NSS invoked the password callback - as that will only happen in case + * there is a password. The reason for tracking this is that there are calls + * which require a password parameter, but doesn't use the callbacks provided, + * so we must call the callback on behalf of these. + */ +static bool has_password = false; + +/* + * This logic exist in NSS as well, but it's only available for when there is + * a database to open, and not only using the system trust store. Thus, we + * need to keep our own copy. + */ +#if defined(WIN32) +static const char *ca_trust_name = "nssckbi.dll"; +#elif defined(__darwin__) +static const char *ca_trust_name = "libnssckbi.dylib"; +#else +static const char *ca_trust_name = "libnssckbi.so"; +#endif + +static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL; + +/* ------------------------------------------------------------ */ +/* Procedures common to all secure sessions */ +/* ------------------------------------------------------------ */ + +/* + * pgtls_init_library + * + * There is no direct equivalent for PQinitOpenSSL in NSS/NSPR, with PR_Init + * being the closest match there is. PR_Init is however already documented to + * not be required so simply making this a noop seems like the best option. + */ +void +pgtls_init_library(bool do_ssl, int do_crypto) +{ + /* noop */ +} + +int +pgtls_init(PGconn *conn) +{ + conn->ssl_in_use = false; + + return 0; +} + +void +pgtls_close(PGconn *conn) +{ + if (nss_context) + { + NSS_ShutdownContext(nss_context); + nss_context = NULL; + } +} + +PostgresPollingStatusType +pgtls_open_client(PGconn *conn) +{ + SECStatus status; + PRFileDesc *pr_fd; + PRFileDesc *model; + NSSInitParameters params; + SSLVersionRange desired_range; + + /* + * The NSPR documentation states that runtime initialization via PR_Init + * is no longer required, as the first caller into NSPR will perform the + * initialization implicitly. See be-secure-nss.c for further discussion + * on PR_Init. + */ + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + + /* + * NSS initialization and the use of contexts is further discussed in + * be-secure-nss.c + */ + memset(¶ms, 0, sizeof(params)); + params.length = sizeof(params); + + if (conn->cert_database && strlen(conn->cert_database) > 0) + { + char *cert_database_path = psprintf("sql:%s", conn->cert_database); + + nss_context = NSS_InitContext(cert_database_path, "", "", "", + ¶ms, + NSS_INIT_READONLY | NSS_INIT_PK11RELOAD); + pfree(cert_database_path); + } + else + nss_context = NSS_InitContext("", "", "", "", ¶ms, + NSS_INIT_READONLY | NSS_INIT_NOCERTDB | + NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN | + NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD); + + if (!nss_context) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to %s certificate database: %s"), + conn->cert_database ? "open" : "create", + pg_SSLerrmessage(PR_GetError())); + + return PGRES_POLLING_FAILED; + } + + /* + * Configure cipher policy. + */ + status = NSS_SetDomesticPolicy(); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to configure cipher policy: %s"), + pg_SSLerrmessage(PR_GetError())); + + return PGRES_POLLING_FAILED; + } + + /* + * If we don't have a certificate database, the system trust store is the + * fallback we can use. If we fail to initialize that as well, we can + * still attempt a connection as long as the sslmode isn't verify*. + */ + if (!conn->cert_database && conn->sslmode[0] == 'v') + { + status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\""); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), + ca_trust_name, + pg_SSLerrmessage(PR_GetError())); + + return PGRES_POLLING_FAILED; + } + } + + + PK11_SetPasswordFunc(PQssl_passwd_cb); + + /* + * Import the already opened socket as we don't want to use NSPR functions + * for opening the network socket due to how the PostgreSQL protocol works + * with TLS connections. This function is not part of the NSPR public API, + * see the comment at the top of the file for the rationale of still using + * it. + */ + pr_fd = PR_ImportTCPSocket(conn->sock); + if (!pr_fd) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to attach to socket: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + /* + * Most of the documentation available, and implementations of, NSS/NSPR + * use the PR_NewTCPSocket() function here, which has the drawback that it + * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which + * copes with IPv6 as well. + */ + model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family)); + if (!model) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to enable TLS: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + /* Disable old protocol versions (SSLv2 and SSLv3) */ + SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE); + SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE); + SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE); + +#ifdef SSL_CBC_RANDOM_IV + + /* + * Enable protection against the BEAST attack in case the NSS library has + * support for that. While SSLv3 is disabled, we may still allow TLSv1 + * which is affected. The option isn't documented as an SSL option, but as + * an NSS environment variable. + */ + SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE); +#endif + + /* Set us up as a TLS client for the handshake */ + SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE); + + /* + * When setting the available protocols, we either use the user defined + * configuration values, and if missing we accept whatever is the highest + * version supported by the library as the max and only limit the range in + * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum + * for Stream protocols, rather than datagram. + */ + SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range); + desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0; + + if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0) + { + int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version); + + if (ssl_min_ver == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid value \"%s\" for minimum version of SSL protocol\n"), + conn->ssl_min_protocol_version); + return -1; + } + + desired_range.min = ssl_min_ver; + } + + if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0) + { + int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version); + + if (ssl_max_ver == -1) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid value \"%s\" for maximum version of SSL protocol\n"), + conn->ssl_max_protocol_version); + return -1; + } + + desired_range.max = ssl_max_ver; + } + + if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to set allowed SSL protocol version range: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + /* + * Set up callback for verifying server certificates, as well as for how + * to handle failed verifications. + */ + SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn); + SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn); + + /* + * Convert the NSPR socket to an SSL socket. Ensuring the success of this + * operation is critical as NSS SSL_* functions may return SECSuccess on + * the socket even though SSL hasn't been enabled, which introduce a risk + * of silent downgrades. + */ + conn->pr_fd = SSL_ImportFD(model, pr_fd); + if (!conn->pr_fd) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to configure client for TLS: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + /* + * The model can now we closed as we've applied the settings of the model + * onto the real socket. From hereon we should only use conn->pr_fd. + */ + PR_Close(model); + + /* Set the private data to be passed to the password callback */ + SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn); + + /* + * If a CRL file has been specified, verify if it exists in the database + * but don't fail in case it doesn't. + */ + if (conn->sslcrl && strlen(conn->sslcrl) > 0) + { + /* XXX: Implement me.. */ + } + + status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to initiate handshake: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + /* + * Set callback for client authentication when requested by the server. + */ + SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn); + + /* + * Specify which hostname we are expecting to talk to. This is required, + * albeit mostly applies to when opening a connection to a traditional + * http server it seems. + */ + SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host); + + do + { + status = SSL_ForceHandshake(conn->pr_fd); + } + while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR); + + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s"), + pg_SSLerrmessage(PR_GetError())); + return PGRES_POLLING_FAILED; + } + + conn->ssl_in_use = true; + return PGRES_POLLING_OK; +} + +ssize_t +pgtls_read(PGconn *conn, void *ptr, size_t len) +{ + PRInt32 nread; + PRErrorCode status; + int read_errno = 0; + + /* + * PR_Recv blocks until there is data to read or the timeout expires. We + * don't want to sit blocked here, so the timeout is turned off by using + * PR_INTERVAL_NO_WAIT which means "return immediately", Zero is returned + * for closed connections, while -1 indicates an error within the ongoing + * connection. + */ + nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT); + + if (nread == 0) + { + read_errno = ECONNRESET; + return -1; + } + + if (nread == -1) + { + status = PR_GetError(); + + switch (status) + { + case PR_WOULD_BLOCK_ERROR: + read_errno = EWOULDBLOCK; + break; + + /* + * The error cases for PR_Recv are not documented, but can be + * reverse engineered from _MD_unix_map_default_error() in the + * NSPR code, defined in pr/src/md/unix/unix_errors.c. + */ + default: + read_errno = ECONNRESET; + break; + } + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("TLS read error: %s"), + pg_SSLerrmessage(status)); + } + + SOCK_ERRNO_SET(read_errno); + return (ssize_t) nread; +} + +/* + * pgtls_read_pending + * Check for the existence of data to be read. + * + * This is part of the PostgreSQL TLS backend API. + */ +bool +pgtls_read_pending(PGconn *conn) +{ + unsigned char c; + int n; + + /* + * PR_Recv peeks into the stream with the timeount turned off, to see if + * there is another byte to read off the wire. There is an NSS function + * SSL_DataPending() which might seem like a better fit, but it will only + * check already encrypted data in the SSL buffer, not still unencrypted + * data, thus it doesn't guarantee that a subsequent call to + * PR_Read/PR_Recv wont block. + */ + n = PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT); + return (n > 0); +} + +ssize_t +pgtls_write(PGconn *conn, const void *ptr, size_t len) +{ + PRInt32 n; + PRErrorCode status; + int write_errno = 0; + + n = PR_Write(conn->pr_fd, ptr, len); + + if (n < 0) + { + status = PR_GetError(); + + switch (status) + { + case PR_WOULD_BLOCK_ERROR: +#ifdef EAGAIN + write_errno = EAGAIN; +#else + write_errno = EINTR; +#endif + break; + + default: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("TLS write error: %s"), + pg_SSLerrmessage(status)); + write_errno = ECONNRESET; + break; + } + } + + SOCK_ERRNO_SET(write_errno); + return (ssize_t) n; +} + +/* + * Verify that the server certificate matches the hostname we connected to. + * + * The certificate's Common Name and Subject Alternative Names are considered. + */ +int +pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, + int *names_examined, + char **first_name) +{ + char *server_hostname = NULL; + CERTCertificate *server_cert = NULL; + SECStatus status = SECSuccess; + SECItem altname_item; + PLArenaPool *arena = NULL; + CERTGeneralName *san_list; + CERTGeneralName *cn; + + server_hostname = SSL_RevealURL(conn->pr_fd); + if (!server_hostname || server_hostname[0] == '\0') + goto done; + + /* + * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName + * verification. + */ + server_cert = SSL_PeerCertificate(conn->pr_fd); + status = CERT_VerifyCertName(server_cert, server_hostname); + if (status != SECSuccess) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to verify server hostname: %s"), + pg_SSLerrmessage(PR_GetError())); + goto done; + } + + status = CERT_FindCertExtension(server_cert, SEC_OID_X509_SUBJECT_ALT_NAME, + &altname_item); + if (status == SECSuccess) + { + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) + { + status = SECFailure; + goto done; + } + san_list = CERT_DecodeAltNameExtension(arena, &altname_item); + if (!san_list) + { + status = SECFailure; + goto done; + } + + for (cn = san_list; cn != san_list; cn = CERT_GetNextGeneralName(cn)) + { + char *alt_name; + int rv; + char tmp[512]; + + status = CERT_RFC1485_EscapeAndQuote(tmp, sizeof(tmp), + (char *) cn->name.other.data, + cn->name.other.len); + + if (status != SECSuccess) + goto done; + + rv = pq_verify_peer_name_matches_certificate_name(conn, tmp, + strlen(tmp), + &alt_name); + if (alt_name) + { + if (!*first_name) + *first_name = alt_name; + else + free(alt_name); + } + + if (rv == 1) + status = SECSuccess; + else + { + status = SECFailure; + break; + } + } + } + else if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND) + status = SECSuccess; + else + status = SECSuccess; + +done: + /* san_list will be freed by freeing the arena it was allocated in */ + if (arena) + PORT_FreeArena(arena, PR_TRUE); + PR_Free(server_hostname); + + if (status == SECSuccess) + return 1; + + return 0; +} + +/* ------------------------------------------------------------ */ +/* PostgreSQL specific TLS support functions */ +/* ------------------------------------------------------------ */ + +static const char * +pg_SSLerrmessage(PRErrorCode errcode) +{ + const char *error; + + error = PR_ErrorToName(errcode); + if (error) + return error; + + return "unknown TLS error"; +} + +static SECStatus +pg_load_nss_module(SECMODModule * *module, const char *library, const char *name) +{ + SECMODModule *mod; + char *modulespec; + + modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name); + + /* + * Attempt to load the specified module. The second parameter is "parent" + * which should always be NULL for application code. The third parameter + * defines if loading should recurse which is only applicable when loading + * a module from within another module. This hierarchy would have to be + * defined in the modulespec, and since we don't support anything but + * directly addressed modules we should pass PR_FALSE. + */ + mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE); + pfree(modulespec); + + if (mod && mod->loaded) + { + *module = mod; + return SECSuccess; + } + + SECMOD_DestroyModule(mod); + return SECFailure; +} + +/* ------------------------------------------------------------ */ +/* NSS Callbacks */ +/* ------------------------------------------------------------ */ + +/* + * pg_cert_auth_handler + * Callback for authenticating server certificate + * + * This is pretty much the same procedure as the SSL_AuthCertificate function + * provided by NSS, with the difference being server hostname validation. With + * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full + * flavor of our sslmodes, so we need our own implementation. + */ +static SECStatus +pg_cert_auth_handler(void *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer) +{ + SECStatus status; + PGconn *conn = (PGconn *) arg; + CERTCertificate *server_cert; + void *pin; + + Assert(!isServer); + + pin = SSL_RevealPinArg(conn->pr_fd); + server_cert = SSL_PeerCertificate(conn->pr_fd); + + status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), + server_cert, + checksig, + certificateUsageSSLServer, + pin, + NULL); + + /* + * If we've already failed validation then there is no point in also + * performing the hostname check for verify-full. + */ + if (status == SECSuccess) + { + if (!pq_verify_peer_name_matches_certificate(conn)) + status = SECFailure; + } + else + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unable to verify certificate: %s"), + pg_SSLerrmessage(PR_GetError())); + } + + return status; +} + +/* + * pg_client_auth_handler + * Callback for client certificate validation + * + * The client auth callback is not on by default in NSS, so we need to invoke + * it ourselves to ensure we can do cert authentication. A TODO is to support + * running without a specified sslcert parameter. By retrieving all the certs + * via nickname from the cert database and see if we find one which apply with + * NSS_CmpCertChainWCANames() and PK11_FindKeyByAnyCert() we could support + * just running with a ssl database specified. + * + * For now, we use the default client certificate validation which requires a + * defined nickname to identify the cert in the database. + */ +static SECStatus +pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames, + CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey) +{ + PGconn *conn = (PGconn *) arg; + + return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey); +} + +/* + * pg_bad_cert_handler + * Callback for failed certificate validation + * + * The TLS handshake will call this function iff the server certificate failed + * validation. Depending on the sslmode, we allow the connection anyways. + */ +static SECStatus +pg_bad_cert_handler(void *arg, PRFileDesc * fd) +{ + PGconn *conn = (PGconn *) arg; + PRErrorCode err; + + /* + * This really shouldn't happen, as we've the the PGconn object as our + * callback data, and at the callsite we know it will be populated. That + * being said, the NSS code itself performs this check even when it should + * not be required so let's use the same belts with our suspenders. + */ + if (!arg) + return SECFailure; + + /* + * For sslmodes other than verify-full and verify-ca we don't perform peer + * validation, so return immediately. sslmode require with a database + * specified which contains a CA certificate will work like verify-ca to + * be compatible with the OpenSSL implementation. + */ + if (strcmp(conn->sslmode, "require") == 0) + { + if (conn->cert_database && strlen(conn->cert_database) > 0 && cert_database_has_CA(conn)) + return SECFailure; + } + if (conn->sslmode[0] == 'v') + return SECFailure; + + err = PORT_GetError(); + + /* + * TODO: these are relevant error codes that can occur in certificate + * validation, figure out which we dont want for require/prefer etc. + */ + switch (err) + { + case SEC_ERROR_INVALID_AVA: + case SEC_ERROR_INVALID_TIME: + case SEC_ERROR_BAD_SIGNATURE: + case SEC_ERROR_EXPIRED_CERTIFICATE: + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_UNTRUSTED_ISSUER: + case SEC_ERROR_UNTRUSTED_CERT: + case SEC_ERROR_CERT_VALID: + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + case SEC_ERROR_CRL_EXPIRED: + case SEC_ERROR_CRL_BAD_SIGNATURE: + case SEC_ERROR_EXTENSION_VALUE_INVALID: + case SEC_ERROR_CA_CERT_INVALID: + case SEC_ERROR_CERT_USAGES_INVALID: + case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: + return SECSuccess; + break; + default: + return SECFailure; + break; + } + + /* Unreachable */ + return SECSuccess; +} + +/* ------------------------------------------------------------ */ +/* SSL information functions */ +/* ------------------------------------------------------------ */ + +/* + * PQgetssl + * + * Return NULL as this is legacy and defined to always be equal to calling + * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged warning + * somewhere as it's nonsensical to run in a non-OpenSSL build, but the color + * of said bikeshed hasn't yet been determined. + */ +void * +PQgetssl(PGconn *conn) +{ + return NULL; +} + +void * +PQsslStruct(PGconn *conn, const char *struct_name) +{ + if (!conn) + return NULL; + + /* + * Return the underlying PRFileDesc which can be used to access + * information on the connection details. There is no SSL context per se. + */ + if (strcmp(struct_name, "NSS") == 0) + return conn->pr_fd; + return NULL; +} + +const char *const * +PQsslAttributeNames(PGconn *conn) +{ + static const char *const result[] = { + "library", + "cipher", + "protocol", + "key_bits", + "compression", + NULL + }; + + return result; +} + +const char * +PQsslAttribute(PGconn *conn, const char *attribute_name) +{ + SECStatus status; + SSLChannelInfo channel; + SSLCipherSuiteInfo suite; + + if (!conn || !conn->pr_fd) + return NULL; + + if (strcmp(attribute_name, "library") == 0) + return "NSS"; + + status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel)); + if (status != SECSuccess) + return NULL; + + status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite)); + if (status != SECSuccess) + return NULL; + + if (strcmp(attribute_name, "cipher") == 0) + return suite.cipherSuiteName; + + if (strcmp(attribute_name, "key_bits") == 0) + { + static char key_bits_str[8]; + + snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits); + return key_bits_str; + } + + if (strcmp(attribute_name, "protocol") == 0) + { + switch (channel.protocolVersion) + { +#ifdef SSL_LIBRARY_VERSION_TLS_1_3 + case SSL_LIBRARY_VERSION_TLS_1_3: + return "TLSv1.3"; +#endif +#ifdef SSL_LIBRARY_VERSION_TLS_1_2 + case SSL_LIBRARY_VERSION_TLS_1_2: + return "TLSv1.2"; +#endif +#ifdef SSL_LIBRARY_VERSION_TLS_1_1 + case SSL_LIBRARY_VERSION_TLS_1_1: + return "TLSv1.1"; +#endif + case SSL_LIBRARY_VERSION_TLS_1_0: + return "TLSv1.0"; + default: + return "unknown"; + } + } + + /* + * NSS disabled support for compression in version 3.33, and it was only + * available for SSLv3 at that point anyways, so we can safely return off + * here without checking. + */ + if (strcmp(attribute_name, "compression") == 0) + return "off"; + + return NULL; +} + +static int +ssl_protocol_version_to_nss(const char *protocol) +{ + if (pg_strcasecmp("TLSv1", protocol) == 0) + return SSL_LIBRARY_VERSION_TLS_1_0; + +#ifdef SSL_LIBRARY_VERSION_TLS_1_1 + if (pg_strcasecmp("TLSv1.1", protocol) == 0) + return SSL_LIBRARY_VERSION_TLS_1_1; +#endif + +#ifdef SSL_LIBRARY_VERSION_TLS_1_2 + if (pg_strcasecmp("TLSv1.2", protocol) == 0) + return SSL_LIBRARY_VERSION_TLS_1_2; +#endif + +#ifdef SSL_LIBRARY_VERSION_TLS_1_3 + if (pg_strcasecmp("TLSv1.3", protocol) == 0) + return SSL_LIBRARY_VERSION_TLS_1_3; +#endif + + return -1; +} + +static bool +cert_database_has_CA(PGconn *conn) +{ + CERTCertList *certificates; + bool hasCA; + + /* + * If the certificate database has a password we must provide it, since + * this API doesn't invoke the standard password callback. + */ + if (has_password) + certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, (void *) conn)); + else + certificates = PK11_ListCerts(PK11CertListCA, NULL); + hasCA = !CERT_LIST_EMPTY(certificates); + CERT_DestroyCertList(certificates); + + return hasCA; +} + +PQsslKeyPassHook_nss_type +PQgetSSLKeyPassHook_nss(void) +{ + return PQsslKeyPassHook; +} + +void +PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook) +{ + PQsslKeyPassHook = hook; +} + +/* + * Supply a password to decrypt a client certificate. + * + * This must match NSS type PK11PasswordFunc. + */ +static char * +PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg) +{ + has_password = true; + + if (PQsslKeyPassHook) + return PQsslKeyPassHook(slot, (PRBool) retry, arg); + else + return PQdefaultSSLKeyPassHook_nss(slot, retry, arg); +} + +/* + * The default password handler callback. + */ +char * +PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg) +{ + PGconn *conn = (PGconn *) arg; + + /* + * If the password didn't work the first time there is no point in + * retrying as it hasn't changed. + */ + if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0) + return PORT_Strdup(conn->sslpassword); + + return NULL; +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index 97c3805303..dd6ee15c5a 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -427,6 +427,9 @@ PQsslAttributeNames(PGconn *conn) return result; } +#endif /* USE_SSL */ + +#ifndef USE_OPENSSL PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void) @@ -445,7 +448,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn) { return 0; } -#endif /* USE_SSL */ +#endif /* USE_OPENSSL */ /* Dummy version of GSSAPI information functions, when built without GSS support */ #ifndef ENABLE_GSS diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 3b6a9fbce3..27c16e187f 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void); extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook); extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn); +/* == in fe-secure-nss.c === */ +typedef struct PK11SlotInfoStr PK11SlotInfo; +typedef int PRIntn; +typedef PRIntn PRBool; + +/* Support for overriding sslpassword handling with a callback. */ +typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void *arg); +extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void); +extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook); +extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg); + #ifdef __cplusplus } #endif diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 1de91ae295..12717ca720 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -362,6 +362,7 @@ struct pg_conn char *sslpassword; /* client key file password */ char *sslrootcert; /* root certificate filename */ char *sslcrl; /* certificate revocation list filename */ + char *cert_database; char *requirepeer; /* required peer credentials for local sockets */ char *gssencmode; /* GSS mode (require,prefer,disable) */ char *krbsrvname; /* Kerberos service name */ @@ -485,6 +486,10 @@ struct pg_conn * OpenSSL version changes */ #endif #endif /* USE_OPENSSL */ + +#ifdef USE_NSS + void *pr_fd; +#endif /* USE_NSS */ #endif /* USE_SSL */ #ifdef ENABLE_GSS diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c index 14e8382cd8..33af92a60b 100644 --- a/src/port/pg_strong_random.c +++ b/src/port/pg_strong_random.c @@ -30,6 +30,20 @@ #ifdef USE_WIN32_RANDOM #include #endif +#ifdef USE_NSS_RANDOM +#define pg_BITS_PER_BYTE BITS_PER_BYTE +#undef BITS_PER_BYTE +#include +#include +#if defined(BITS_PER_BYTE) +#if BITS_PER_BYTE != pg_BITS_PER_BYTE +#error "incompatible byte widths between NSPR and postgres" +#endif +#else +#define BITS_PER_BYTE pg_BITS_PER_BYTE +#endif +#undef pg_BITS_PER_BYTE +#endif #ifdef USE_WIN32_RANDOM /* @@ -158,6 +172,29 @@ pg_strong_random(void *buf, size_t len) } return false; +#elif defined(USE_NSS_RANDOM) + NSSInitParameters params; + NSSInitContext *nss_context; + SECStatus status; + + memset(¶ms, 0, sizeof(params)); + params.length = sizeof(params); + nss_context = NSS_InitContext("", "", "", "", ¶ms, + NSS_INIT_READONLY | NSS_INIT_NOCERTDB | + NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN | + NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD); + + if (!nss_context) + return false; + + status = PK11_GenerateRandom(buf, len); + NSS_ShutdownContext(nss_context); + + if (status == SECSuccess) + return true; + + return false; + /* * Read /dev/urandom ourselves. */ diff --git a/src/test/Makefile b/src/test/Makefile index 9774f534d9..a09c350939 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA))) SUBDIRS += ldap endif endif -ifeq ($(with_openssl),yes) +ifeq ($(with_ssl),yes) ifneq (,$(filter ssl,$(PG_TEST_EXTRA))) SUBDIRS += ssl endif diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile index 777ee39413..4af971ef77 100644 --- a/src/test/ssl/Makefile +++ b/src/test/ssl/Makefile @@ -14,6 +14,7 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global export with_openssl +export with_nss CERTIFICATES := server_ca server-cn-and-alt-names \ server-cn-only server-single-alt-name server-multiple-alt-names \ @@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \ ssl/client+client_ca.crt ssl/client-der.key \ ssl/client-encrypted-pem.key ssl/client-encrypted-der.key +# Even though we in practice could get away with far fewer NSS databases, they +# are generated to mimick the setup for the OpenSSL tests in order to ensure +# we isolate the same behavior between the backends. The database name should +# contain the files included for easier test suite code reading. +NSSFILES := ssl/nss/client_ca.crt.db \ + ssl/nss/server_ca.crt.db \ + ssl/nss/root+server_ca.crt.db \ + ssl/nss/root+client_ca.crt.db \ + ssl/nss/client.crt__client.key.db \ + ssl/nss/client-revoked.crt__client-revoked.key.db \ + ssl/nss/server-cn-only.crt__server-password.key.db \ + ssl/nss/server-cn-only.crt__server-cn-only.key.db \ + ssl/nss/root.crl \ + ssl/nss/server.crl \ + ssl/nss/client.crl \ + ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \ + ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \ + ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \ + ssl/nss/server-no-names.crt__server-no-names.key.db \ + ssl/nss/server-revoked.crt__server-revoked.key.db \ + ssl/nss/root+client.crl \ + ssl/nss/client+client_ca.crt__client.key.db \ + ssl/nss/client.crt__client-encrypted-pem.key.db \ + ssl/nss/root+server_ca.crt__server.crl.db \ + ssl/nss/root+server_ca.crt__root+server.crl.db + # This target re-generates all the key and certificate files. Usually we just # use the ones that are committed to the tree without rebuilding them. # @@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \ # sslfiles: $(SSLFILES) +# Generate NSS certificate databases corresponding to the OpenSSL certificates. +# This target will fail unless preceded by nssfiles-clean. +nssfiles: $(NSSFILES) + # OpenSSL requires a directory to put all generated certificates in. We don't # use this for anything, but we need a location. ssl/new_certs_dir: @@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt ssl/new_certs_dir rm ssl/temp_ca.crt ssl/temp_ca_signed.crt echo "01" > ssl/$*_ca.srl +ssl/nss/%_ca.crt.db: ssl/%_ca.crt + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C" + +ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C" + crlutil -I -i ssl/nss/server.crl -d $@ -B + +ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt ssl/nss/root.crl ssl/nss/server.crl + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C" + crlutil -I -i ssl/nss/root.crl -d $@ -B + crlutil -I -i ssl/nss/server.crl -d $@ -B + # Server certificates, signed by server CA: ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config server-$*.config @@ -77,6 +126,78 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only. openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config rm ssl/server-ss.csr +# pk12util won't preserve the password when importing the password protected +# key, the password must be set on the database *before* importing it as the +# password in the pkcs12 envelope will be dropped. +ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt + $(MKDIR_P) $@ + echo "secret1" > password.txt + certutil -d "sql:$@" -N -f password.txt + certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" -f password.txt + certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" -f password.txt + certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" -f password.txt + certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" -f password.txt + openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passin 'pass:secret1' -passout 'pass:secret1' + pk12util -i ssl/nss/server-password.pfx -d $@ -W 'secret1' -K 'secret1' + +ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt ssl/server-cn-only.key + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt -passout pass: + pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W '' + +ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: ssl/server-multiple-alt-names.crt + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i ssl/server-multiple-alt-names.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt -passout pass: + pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W '' + +ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: ssl/server-single-alt-name.crt + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i ssl/server-single-alt-name.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout pass: + pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W '' + +ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: ssl/server-cn-and-alt-names.crt + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i ssl/server-cn-and-alt-names.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout pass: + pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W '' + +ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt -passout pass: + pk12util -i ssl/nss/server-no-names.pfx -d $@ -W '' + +ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt -passout pass: + pk12util -i ssl/nss/server-revoked.pfx -d $@ -W '' + # Password-protected version of server-cn-only.key ssl/server-password.key: ssl/server-cn-only.key openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1' @@ -88,6 +209,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert rm ssl/client.csr ssl/temp.crt +# Client certificate, signed by client CA +ssl/nss/client.crt__client.key.db: ssl/client.crt + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass: + pk12util -i ssl/nss/client.pfx -d $@ -W '' + +# Client certificate with encrypted key, signed by client CA +ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt + $(MKDIR_P) $@ + echo 'dUmmyP^#+' > $@.pass + certutil -d "sql:$@" -N -f $@.pass + certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C" + certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout pass:'dUmmyP^#+' + pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' -k $@.pass + # Another client certificate, signed by the client CA. This one is revoked. ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config client.config @@ -95,6 +237,14 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert rm ssl/client-revoked.csr ssl/temp.crt +ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt -passout pass: + pk12util -i ssl/nss/client-revoked.pfx -d $@ -W '' + # Convert the key to DER, to test our behaviour there too ssl/client-der.key: ssl/client.key openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key @@ -127,19 +277,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt cat $^ > $@ +# Client certificate, signed by client CA +ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt + $(MKDIR_P) $@ + certutil -d "sql:$@" -N --empty-password + certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass: + pk12util -i ssl/nss/client.pfx -d $@ -W '' + #### CRLs ssl/client.crl: ssl/client-revoked.crt openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl +ssl/nss/client.crl: ssl/client.crl + openssl crl -in $^ -outform der -out $@ + ssl/server.crl: ssl/server-revoked.crt openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl +ssl/nss/server.crl: ssl/server.crl + openssl crl -in $^ -outform der -out $@ + ssl/root.crl: ssl/root_ca.crt openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl +ssl/nss/root.crl: ssl/root.crl + openssl crl -in $^ -outform der -out $@ + +ssl/nss/root+client.crl: ssl/root+client.crl + openssl crl -in $^ -outform der -out $@ + # If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the # chain, even if some of them are empty. ssl/root+server.crl: ssl/root.crl ssl/server.crl @@ -151,9 +322,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl sslfiles-clean: rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt +.PHONY: nssfiles-clean +nssfiles-clean: + rm -rf ssl/nss + clean distclean maintainer-clean: rm -rf tmp_check rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key + rm -rf ssl/nss # Doesn't depend on $(SSLFILES) because we don't rebuild them by default check: diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl index fd2727b568..ff9c8ae3f3 100644 --- a/src/test/ssl/t/001_ssltests.pl +++ b/src/test/ssl/t/001_ssltests.pl @@ -4,17 +4,24 @@ use PostgresNode; use TestLib; use Test::More; -use File::Copy; - use FindBin; use lib $FindBin::RealBin; -use SSLServer; +use SSL::Server; + +my $openssl; +my $nss; if ($ENV{with_openssl} eq 'yes') { + $openssl = 1; plan tests => 93; } +elsif ($ENV{with_nss} eq 'yes') +{ + $nss = 1; + plan tests => 98; +} else { plan skip_all => 'SSL not supported by this build'; @@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32'; # Allocation of base connection string shared among multiple tests. my $common_connstr; -# The client's private key must not be world-readable, so take a copy -# of the key stored in the code tree and update its permissions. -# -# This changes ssl/client.key to ssl/client_tmp.key etc for the rest -# of the tests. -my @keys = ( - "client", "client-revoked", - "client-der", "client-encrypted-pem", - "client-encrypted-der"); -foreach my $key (@keys) -{ - copy("ssl/${key}.key", "ssl/${key}_tmp.key") - or die - "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!"; - chmod 0600, "ssl/${key}_tmp.key" - or die "failed to change permissions on ssl/${key}_tmp.key: $!"; -} - -# Also make a copy of that explicitly world-readable. We can't -# necessarily rely on the file in the source tree having those -# permissions. Add it to @keys to include it in the final clean -# up phase. -copy("ssl/client.key", "ssl/client_wrongperms_tmp.key"); -chmod 0644, "ssl/client_wrongperms_tmp.key"; -push @keys, 'client_wrongperms'; - #### Set up the server. note "setting up data directory"; @@ -72,36 +53,65 @@ $node->start; # Run this before we lock down access below. my $result = $node->safe_psql('postgres', "SHOW ssl_library"); -is($result, 'OpenSSL', 'ssl_library parameter'); +is($result, SSL::Server::ssl_library(), 'ssl_library parameter'); configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR, 'trust'); note "testing password-protected keys"; -open my $sslconf, '>', $node->data_dir . "/sslconfig.conf"; -print $sslconf "ssl=on\n"; -print $sslconf "ssl_cert_file='server-cn-only.crt'\n"; -print $sslconf "ssl_key_file='server-password.key'\n"; -print $sslconf "ssl_passphrase_command='echo wrongpassword'\n"; -close $sslconf; +# Since the passphrase callbacks operate at different stages in OpenSSL and +# NSS we have two separate blocks for them. +SKIP: +{ + skip "Certificate passphrases aren't checked on server restart in NSS", 2 + if ($nss); + + set_server_cert($node, 'server-cn-only', 'root+client_ca', + 'server-password', 'echo wrongpassword'); + command_fails( + [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ], + 'restart fails with password-protected key file with wrong password'); + $node->_update_pid(0); + + set_server_cert($node, 'server-cn-only', 'root+client_ca', + 'server-password', 'echo secret1'); + command_ok( + [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ], + 'restart succeeds with password-protected key file'); + $node->_update_pid(1); +} -command_fails( - [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ], - 'restart fails with password-protected key file with wrong password'); -$node->_update_pid(0); +SKIP: +{ + skip "Certificate passphrases are checked on connection in NSS", 5 + if ($openssl); -open $sslconf, '>', $node->data_dir . "/sslconfig.conf"; -print $sslconf "ssl=on\n"; -print $sslconf "ssl_cert_file='server-cn-only.crt'\n"; -print $sslconf "ssl_key_file='server-password.key'\n"; -print $sslconf "ssl_passphrase_command='echo secret1'\n"; -close $sslconf; + set_server_cert($node, 'server-cn-only', 'root+client_ca', + 'server-password', 'echo wrongpassword'); + command_ok( + [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ], + 'restart fails with password-protected key file with wrong password'); + $node->_update_pid(1); -command_ok( - [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ], - 'restart succeeds with password-protected key file'); -$node->_update_pid(1); + test_connect_fails( + "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test", + "sslrootcert=invalid sslmode=require", + qr/\QSSL error\E/, + "connect to server with incorrect key password configured"); + + set_server_cert($node, 'server-cn-only', 'root+client_ca', + 'server-password', 'echo secret1'); + command_ok( + [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ], + 'restart fails with password-protected key file with wrong password'); + $node->_update_pid(1); + + test_connect_ok( + "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test", + "sslrootcert=invalid sslmode=require", + "connect to server with correct key password configured"); +} # Test compatibility of SSL protocols. # TLSv1.1 is lower than TLSv1.2, so it won't work. @@ -149,82 +159,105 @@ test_connect_ok( test_connect_fails( $common_connstr, "sslrootcert=invalid sslmode=verify-ca", - qr/root certificate file "invalid" does not exist/, + qr/root certificate file "invalid" does not exist|could not connect to server/, "connect without server root cert sslmode=verify-ca"); test_connect_fails( $common_connstr, "sslrootcert=invalid sslmode=verify-full", - qr/root certificate file "invalid" does not exist/, + qr/root certificate file "invalid" does not exist|could not connect to server/, "connect without server root cert sslmode=verify-full"); # Try with wrong root cert, should fail. (We're using the client CA as the # root, but the server's key is signed by the server CA.) -test_connect_fails($common_connstr, - "sslrootcert=ssl/client_ca.crt sslmode=require", - qr/SSL error/, "connect with wrong server root cert sslmode=require"); -test_connect_fails($common_connstr, - "sslrootcert=ssl/client_ca.crt sslmode=verify-ca", - qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca"); -test_connect_fails($common_connstr, - "sslrootcert=ssl/client_ca.crt sslmode=verify-full", - qr/SSL error/, "connect with wrong server root cert sslmode=verify-full"); - -# Try with just the server CA's cert. This fails because the root file -# must contain the whole chain up to the root CA. -test_connect_fails($common_connstr, - "sslrootcert=ssl/server_ca.crt sslmode=verify-ca", - qr/SSL error/, "connect with server CA cert, without root CA"); +test_connect_fails( + $common_connstr, + "sslrootcert=ssl/client_ca.crt sslmode=require cert_database=ssl/nss/client_ca.crt.db", + qr/SSL error/, + "connect with wrong server root cert sslmode=require"); +test_connect_fails( + $common_connstr, + "sslrootcert=ssl/client_ca.crt sslmode=verify-ca cert_database=ssl/nss/client_ca.crt.db", + qr/SSL error/, + "connect with wrong server root cert sslmode=verify-ca"); +test_connect_fails( + $common_connstr, + "sslrootcert=ssl/client_ca.crt sslmode=verify-full cert_database=ssl/nss/client_ca.crt.db", + qr/SSL error/, + "connect with wrong server root cert sslmode=verify-full"); + +SKIP: +{ + # NSS supports partial chain validation, so this test doesnt work there. + # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which + # we don't allow. + skip "NSS support partial chain validation", 2 if ($nss); + # Try with just the server CA's cert. This fails because the root file + # must contain the whole chain up to the root CA. + test_connect_fails($common_connstr, + "sslrootcert=ssl/server_ca.crt sslmode=verify-ca", + qr/SSL error/, "connect with server CA cert, without root CA"); +} # And finally, with the correct root cert. test_connect_ok( $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=require", + "sslrootcert=ssl/root+server_ca.crt sslmode=require cert_database=ssl/nss/root+server_ca.crt.db", "connect with correct server CA cert file sslmode=require"); test_connect_ok( $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca", + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db", "connect with correct server CA cert file sslmode=verify-ca"); test_connect_ok( $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full", + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db", "connect with correct server CA cert file sslmode=verify-full"); -# Test with cert root file that contains two certificates. The client should -# be able to pick the right one, regardless of the order in the file. -test_connect_ok( - $common_connstr, - "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca", - "cert root file that contains two certificates, order 1"); -test_connect_ok( - $common_connstr, - "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca", - "cert root file that contains two certificates, order 2"); +SKIP: +{ + skip "CA ordering is irrelevant in NSS databases", 2 if ($nss); + # Test with cert root file that contains two certificates. The client should + # be able to pick the right one, regardless of the order in the file. + test_connect_ok( + $common_connstr, + "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca", + "cert root file that contains two certificates, order 1"); + + # How about import the both-file into a database? + test_connect_ok( + $common_connstr, + "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca", + "cert root file that contains two certificates, order 2"); +} # CRL tests # Invalid CRL filename is the same as no CRL, succeeds test_connect_ok( $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid", + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid cert_database=ssl/nss/root+server_ca.crt.db", "sslcrl option with invalid file name"); -# A CRL belonging to a different CA is not accepted, fails -test_connect_fails( - $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl", - qr/SSL error/, - "CRL belonging to a different CA"); +SKIP: +{ + skip "CRL's are verified when adding to NSS database", 2 if ($nss); + # A CRL belonging to a different CA is not accepted, fails + test_connect_fails( + $common_connstr, + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl", + qr/SSL error/, + "CRL belonging to a different CA"); +} # With the correct CRL, succeeds (this cert is not revoked) test_connect_ok( $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl", + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db", "CRL with a non-revoked cert"); # Check that connecting with verify-full fails, when the hostname doesn't # match the hostname in the server's certificate. $common_connstr = - "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR"; + "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db"; test_connect_ok( $common_connstr, @@ -237,14 +270,14 @@ test_connect_ok( test_connect_fails( $common_connstr, "sslmode=verify-full host=wronghost.test", - qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/, + qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, "mismatch between host name and server certificate sslmode=verify-full"); # Test Subject Alternative Names. switch_server_cert($node, 'server-multiple-alt-names'); $common_connstr = - "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full"; + "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db"; test_connect_ok( $common_connstr, @@ -262,12 +295,12 @@ test_connect_ok( test_connect_fails( $common_connstr, "host=wronghost.alt-name.pg-ssltest.test", - qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E/, + qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, "host name not matching with X.509 Subject Alternative Names"); test_connect_fails( $common_connstr, "host=deep.subdomain.wildcard.pg-ssltest.test", - qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/, + qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, "host name not matching with X.509 Subject Alternative Names wildcard"); # Test certificate with a single Subject Alternative Name. (this gives a @@ -275,7 +308,7 @@ test_connect_fails( switch_server_cert($node, 'server-single-alt-name'); $common_connstr = - "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full"; + "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db"; test_connect_ok( $common_connstr, @@ -285,12 +318,12 @@ test_connect_ok( test_connect_fails( $common_connstr, "host=wronghost.alt-name.pg-ssltest.test", - qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/, + qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, "host name not matching with a single X.509 Subject Alternative Name"); test_connect_fails( $common_connstr, "host=deep.subdomain.wildcard.pg-ssltest.test", - qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/, + qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, "host name not matching with a single X.509 Subject Alternative Name wildcard" ); @@ -299,7 +332,7 @@ test_connect_fails( switch_server_cert($node, 'server-cn-and-alt-names'); $common_connstr = - "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full"; + "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full cert_database=ssl/nss/root+server_ca.crt.db"; test_connect_ok( $common_connstr, @@ -312,14 +345,14 @@ test_connect_ok( test_connect_fails( $common_connstr, "host=common-name.pg-ssltest.test", - qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E/, + qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name "common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, "certificate with both a CN and SANs ignores CN"); # Finally, test a server certificate that has no CN or SANs. Of course, that's # not a very sensible certificate, but libpq should handle it gracefully. switch_server_cert($node, 'server-no-names'); $common_connstr = - "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR"; + "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db"; test_connect_ok( $common_connstr, @@ -328,7 +361,7 @@ test_connect_ok( test_connect_fails( $common_connstr, "sslmode=verify-full host=common-name.pg-ssltest.test", - qr/could not get server's host name from server certificate/, + qr/could not get server's host name from server certificate|SSL_ERROR_BAD_CERT_DOMAIN/, "server certificate without CN or SANs sslmode=verify-full"); # Test that the CRL works @@ -340,11 +373,11 @@ $common_connstr = # Without the CRL, succeeds. With it, fails. test_connect_ok( $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca", + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca cert_database=ssl/nss/root+server_ca.crt.db", "connects without client-side CRL"); test_connect_fails( $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl", + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl cert_database=ssl/nss/root+server_ca.crt__server.crl.db", qr/SSL error/, "does not connect with client-side CRL"); @@ -365,21 +398,21 @@ command_like( # Test min/max SSL protocol versions. test_connect_ok( $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2", + "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 cert_database=ssl/nss/root+server_ca.crt.db", "connection success with correct range of TLS protocol versions"); test_connect_fails( $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1", + "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 cert_database=ssl/nss/root+server_ca.crt.db", qr/invalid SSL protocol version range/, "connection failure with incorrect range of TLS protocol versions"); test_connect_fails( $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls", + "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_min_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db", qr/invalid ssl_min_protocol_version value/, "connection failure with an incorrect SSL protocol minimum bound"); test_connect_fails( $common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls", + "sslrootcert=ssl/root+server_ca.crt sslmode=require ssl_max_protocol_version=incorrect_tls cert_database=ssl/nss/root+server_ca.crt.db", qr/invalid ssl_max_protocol_version value/, "connection failure with an incorrect SSL protocol maximum bound"); @@ -390,7 +423,7 @@ test_connect_fails( note "running server tests"; $common_connstr = - "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR"; + "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db"; # no client cert test_connect_fails( @@ -406,32 +439,43 @@ test_connect_ok( "certificate authorization succeeds with correct client cert in PEM format" ); -# correct client cert in unencrypted DER -test_connect_ok( - $common_connstr, - "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key", - "certificate authorization succeeds with correct client cert in DER format" -); +$common_connstr = + "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR"; + +SKIP: +{ + skip "NSS database not implemented in the Makefile", 1 if ($nss); + # correct client cert in unencrypted DER + test_connect_ok( + $common_connstr, + "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key", + "certificate authorization succeeds with correct client cert in DER format" + ); +} # correct client cert in encrypted PEM test_connect_ok( $common_connstr, - "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'", + "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db", "certificate authorization succeeds with correct client cert in encrypted PEM format" ); -# correct client cert in encrypted DER -test_connect_ok( - $common_connstr, - "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'", - "certificate authorization succeeds with correct client cert in encrypted DER format" -); +SKIP: +{ + skip "NSS database not implemented in the Makefile", 1 if ($nss); + # correct client cert in encrypted DER + test_connect_ok( + $common_connstr, + "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'", + "certificate authorization succeeds with correct client cert in encrypted DER format" + ); +} # correct client cert in encrypted PEM with wrong password test_connect_fails( $common_connstr, - "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'", - qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!, + "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db", + qr!connection requires a valid client certificate|\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!, "certificate authorization fails with correct client cert and wrong password in encrypted PEM format" ); @@ -471,18 +515,19 @@ command_like( '-P', 'null=_null_', '-d', - "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key", + "$common_connstr user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key cert_database=ssl/nss/client.crt__client.key.db", '-c', "SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()" ], qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n - ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx, + ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx, 'pg_stat_ssl with client certificate'); # client key with wrong permissions SKIP: { skip "Permissions check not enforced on Windows", 2 if ($windows_os); + skip "Key not on filesystem with NSS", 2 if ($nss); test_connect_fails( $common_connstr, @@ -495,10 +540,13 @@ SKIP: test_connect_fails( $common_connstr, "user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key", - qr/certificate authentication failed for user "anotheruser"/, + qr/unable to verify certificate|certificate authentication failed for user "anotheruser"/, "certificate authorization fails with client cert belonging to another user" ); +$common_connstr = + "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db"; + # revoked client cert test_connect_fails( $common_connstr, @@ -510,7 +558,7 @@ test_connect_fails( # works, iff username matches Common Name # fails, iff username doesn't match Common Name. $common_connstr = - "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR"; + "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client.crt__client.key.db"; test_connect_ok( $common_connstr, @@ -536,17 +584,23 @@ test_connect_ok( # intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file switch_server_cert($node, 'server-cn-only', 'root_ca'); $common_connstr = - "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR"; + "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db"; -test_connect_ok( +TODO: +{ + local $TODO = "WIP failure cause currently unknown"; + test_connect_ok( + $common_connstr, + "sslmode=require sslcert=ssl/client+client_ca.crt", + "intermediate client certificate is provided by client"); +} + +test_connect_fails( $common_connstr, - "sslmode=require sslcert=ssl/client+client_ca.crt", - "intermediate client certificate is provided by client"); -test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt", - qr/SSL error/, "intermediate client certificate is missing"); + "sslmode=require sslcert=ssl/client.crt", + qr/connection requires a valid client certificate|SSL error/, + "intermediate client certificate is missing"); # clean up -foreach my $key (@keys) -{ - unlink("ssl/${key}_tmp.key"); -} + +SSL::Server::cleanup(); diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl index 20ab0d5b0b..b50659e568 100644 --- a/src/test/ssl/t/002_scram.pl +++ b/src/test/ssl/t/002_scram.pl @@ -11,11 +11,11 @@ use File::Copy; use FindBin; use lib $FindBin::RealBin; -use SSLServer; +use SSL::Server; if ($ENV{with_openssl} ne 'yes') { - plan skip_all => 'SSL not supported by this build'; + plan skip_all => 'OpenSSL not supported by this build'; } # This is the hostname used to connect to the server. diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm new file mode 100644 index 0000000000..837f0d9891 --- /dev/null +++ b/src/test/ssl/t/SSL/Backend/NSS.pm @@ -0,0 +1,64 @@ +package SSL::Backend::NSS; + +use strict; +use warnings; +use Exporter; + +our @ISA = qw(Exporter); +our @EXPORT_OK = qw(get_new_nss_backend); + +sub new +{ + my ($class) = @_; + + my $self = { _library => 'NSS' }; + + bless $self, $class; + + return $self; +} + +sub get_new_nss_backend +{ + my $class = 'SSL::Backend::NSS'; + + return $class->new(); +} + +sub init +{ + # Make sure the certificate databases are in place? +} + +sub get_library +{ + my ($self) = @_; + + return $self->{_library}; +} + +sub set_server_cert +{ + my $self = $_[0]; + my $certfile = $_[1]; + my $cafile = $_[2]; + my $keyfile = $_[3]; + + my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key'; + my $cert_database = $cert_nickname . '.db'; + + my $sslconf = + "ssl_ca_file='$cafile.crt'\n" + . "ssl_cert_file='ssl/$certfile.crt'\n" + . "ssl_crl_file=''\n" + . "ssl_database='nss/$cert_database'\n"; + + return $sslconf; +} + +sub cleanup +{ + # Something? +} + +1; diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm new file mode 100644 index 0000000000..62b11b7632 --- /dev/null +++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm @@ -0,0 +1,103 @@ +package SSL::Backend::OpenSSL; + +use strict; +use warnings; +use Exporter; +use File::Copy; + +our @ISA = qw(Exporter); +our @EXPORT_OK = qw(get_new_openssl_backend); + +our (@keys); + +INIT +{ + @keys = ( + "client", "client-revoked", + "client-der", "client-encrypted-pem", + "client-encrypted-der"); +} + +sub new +{ + my ($class) = @_; + + my $self = { _library => 'OpenSSL' }; + + bless $self, $class; + + return $self; +} + +sub get_new_openssl_backend +{ + my $class = 'SSL::Backend::OpenSSL'; + + my $backend = $class->new(); + + return $backend; +} + +sub init +{ + # The client's private key must not be world-readable, so take a copy + # of the key stored in the code tree and update its permissions. + # + # This changes ssl/client.key to ssl/client_tmp.key etc for the rest + # of the tests. + foreach my $key (@keys) + { + copy("ssl/${key}.key", "ssl/${key}_tmp.key") + or die + "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!"; + chmod 0600, "ssl/${key}_tmp.key" + or die "failed to change permissions on ssl/${key}_tmp.key: $!"; + } + + # Also make a copy of that explicitly world-readable. We can't + # necessarily rely on the file in the source tree having those + # permissions. Add it to @keys to include it in the final clean + # up phase. + copy("ssl/client.key", "ssl/client_wrongperms_tmp.key") + or die + "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!"; + chmod 0644, "ssl/client_wrongperms_tmp.key" + or die + "failed to change permissions on ssl/client_wrongperms_tmp.key: $!"; + push @keys, 'client_wrongperms'; +} + +# Change the configuration to use given server cert file, and reload +# the server so that the configuration takes effect. +sub set_server_cert +{ + my $self = $_[0]; + my $certfile = $_[1]; + my $cafile = $_[2] || "root+client_ca"; + my $keyfile = $_[3] || $certfile; + + my $sslconf = + "ssl_ca_file='$cafile.crt'\n" + . "ssl_cert_file='$certfile.crt'\n" + . "ssl_key_file='$keyfile.key'\n" + . "ssl_crl_file='root+client.crl'\n"; + + return $sslconf; +} + +sub get_library +{ + my ($self) = @_; + + return $self->{_library}; +} + +sub cleanup +{ + foreach my $key (@keys) + { + unlink("ssl/${key}_tmp.key"); + } +} + +1; diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSL/Server.pm similarity index 78% rename from src/test/ssl/t/SSLServer.pm rename to src/test/ssl/t/SSL/Server.pm index f5987a003e..1261d21861 100644 --- a/src/test/ssl/t/SSLServer.pm +++ b/src/test/ssl/t/SSL/Server.pm @@ -23,19 +23,39 @@ # explicitly because an invalid sslcert or sslrootcert, respectively, # causes those to be ignored.) -package SSLServer; +package SSL::Server; use strict; use warnings; use PostgresNode; +use RecursiveCopy; use TestLib; use File::Basename; use File::Copy; use Test::More; +use SSL::Backend::OpenSSL qw(get_new_openssl_backend); +use SSL::Backend::NSS qw(get_new_nss_backend); + +our ($openssl, $nss, $backend); + +# The TLS backend which the server is using should be mostly transparent for +# the user, apart from individual configuration settings, so keep the backend +# specific things abstracted behind SSL::Server. +if ($ENV{with_openssl} eq 'yes') +{ + $backend = get_new_openssl_backend(); + $openssl = 1; +} +elsif ($ENV{with_nss} eq 'yes') +{ + $backend = get_new_nss_backend(); + $nss = 1; +} use Exporter 'import'; our @EXPORT = qw( configure_test_server_for_ssl + set_server_cert switch_server_cert test_connect_fails test_connect_ok @@ -144,12 +164,19 @@ sub configure_test_server_for_ssl close $sslconf; # Copy all server certificates and keys, and client root cert, to the data dir - copy_files("ssl/server-*.crt", $pgdata); - copy_files("ssl/server-*.key", $pgdata); - chmod(0600, glob "$pgdata/server-*.key") or die $!; - copy_files("ssl/root+client_ca.crt", $pgdata); - copy_files("ssl/root_ca.crt", $pgdata); - copy_files("ssl/root+client.crl", $pgdata); + if (defined($openssl)) + { + copy_files("ssl/server-*.crt", $pgdata); + copy_files("ssl/server-*.key", $pgdata); + chmod(0600, glob "$pgdata/server-*.key") or die $!; + copy_files("ssl/root+client_ca.crt", $pgdata); + copy_files("ssl/root_ca.crt", $pgdata); + copy_files("ssl/root+client.crl", $pgdata); + } + elsif (defined($nss)) + { + RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss"; + } # Stop and restart server to load new listen_addresses. $node->restart; @@ -157,26 +184,51 @@ sub configure_test_server_for_ssl # Change pg_hba after restart because hostssl requires ssl=on configure_hba_for_ssl($node, $servercidr, $authmethod); + # Finally, perform backend specific configuration + $backend->init(); + return; } -# Change the configuration to use given server cert file, and reload -# the server so that the configuration takes effect. -sub switch_server_cert +sub ssl_library +{ + return $backend->get_library(); +} + +sub cleanup +{ + $backend->cleanup(); +} + +# Change the configuration to use given server cert file, +sub set_server_cert { my $node = $_[0]; my $certfile = $_[1]; my $cafile = $_[2] || "root+client_ca"; + my $keyfile = $_[3] || ''; + my $pwcmd = $_[4] || ''; my $pgdata = $node->data_dir; + $keyfile = $certfile if $keyfile eq ''; + open my $sslconf, '>', "$pgdata/sslconfig.conf"; print $sslconf "ssl=on\n"; - print $sslconf "ssl_ca_file='$cafile.crt'\n"; - print $sslconf "ssl_cert_file='$certfile.crt'\n"; - print $sslconf "ssl_key_file='$certfile.key'\n"; - print $sslconf "ssl_crl_file='root+client.crl'\n"; + print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile); + print $sslconf "ssl_passphrase_command='$pwcmd'\n" + unless $pwcmd eq ''; close $sslconf; + return; +} +# Change the configuration to use given server cert file, and reload +# the server so that the configuration takes effect. +# Takes the same arguments as set_server_cert, which it calls to do that +# piece of the work. +sub switch_server_cert +{ + my $node = $_[0]; + set_server_cert(@_); $node->restart; return; } diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm index ea3af48777..afe72a7e97 100644 --- a/src/tools/msvc/Install.pm +++ b/src/tools/msvc/Install.pm @@ -438,7 +438,8 @@ sub CopyContribFiles { # These configuration-based exclusions must match vcregress.pl next if ($d eq "uuid-ossp" && !defined($config->{uuid})); - next if ($d eq "sslinfo" && !defined($config->{openssl})); + next if ($d eq "sslinfo" && !defined($config->{openssl}) + && !defined($config->{nss})); next if ($d eq "xml2" && !defined($config->{xml})); next if ($d =~ /_plperl$/ && !defined($config->{perl})); next if ($d =~ /_plpython$/ && !defined($config->{python})); diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 90594bd41b..d6aefbfcbf 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -192,12 +192,19 @@ sub mkvcbuild $postgres->FullExportDLL('postgres.lib'); # The OBJS scraper doesn't know about ifdefs, so remove appropriate files - # if building without OpenSSL. - if (!$solution->{options}->{openssl}) + # if building without various options. + if (!$solution->{options}->{openssl} && !$solution->{options}->{nss}) { $postgres->RemoveFile('src/backend/libpq/be-secure-common.c'); + } + if (!$solution->{options}->{openssl}) + { $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{nss}) + { + $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c'); + } if (!$solution->{options}->{gss}) { $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); @@ -255,12 +262,19 @@ sub mkvcbuild $libpq->AddReference($libpgcommon, $libpgport); # The OBJS scraper doesn't know about ifdefs, so remove appropriate files - # if building without OpenSSL. - if (!$solution->{options}->{openssl}) + # if building without various options + if (!$solution->{options}->{openssl} && !$solution->{options}->{nss}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c'); + } + if (!$solution->{options}->{openssl}) + { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); } + if (!$solution->{options}->{nss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c'); + } if (!$solution->{options}->{gss}) { $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); @@ -428,9 +442,14 @@ sub mkvcbuild push @contrib_excludes, 'xml2'; } + if (!$solution->{options}->{openssl} && !$solution->{options}->{nss}) + { + push @contrib_excludes, 'sslinfo'; + } + if (!$solution->{options}->{openssl}) { - push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback'; + push @contrib_excludes, 'ssl_passphrase_callback'; } if (!$solution->{options}->{uuid}) diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm index bc8904732f..ac11d9ab26 100644 --- a/src/tools/msvc/Solution.pm +++ b/src/tools/msvc/Solution.pm @@ -484,6 +484,7 @@ sub GenerateFiles USE_NAMED_POSIX_SEMAPHORES => undef, USE_OPENSSL => undef, USE_OPENSSL_RANDOM => undef, + USE_NSS => undef, USE_PAM => undef, USE_SLICING_BY_8_CRC32C => undef, USE_SSE42_CRC32C => undef, @@ -537,6 +538,10 @@ sub GenerateFiles $define{HAVE_OPENSSL_INIT_SSL} = 1; } } + if ($self->{options}->{nss}) + { + $define{USE_NSS} = 1; + } $self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1); $self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0); @@ -1004,6 +1009,21 @@ sub AddProject } } } + if ($self->{options}->{nss}) + { + $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss'); + $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr'); + foreach my $lib (qw(plds4 plc4 nspr4)) + { + $proj->AddLibrary($self->{options}->{nss} . + '\lib\lib' . "$lib.lib", 0); + } + foreach my $lib (qw(ssl3 smime3 nss3)) + { + $proj->AddLibrary($self->{options}->{nss} . + '\lib' . "\\$lib.dll.lib", 0); + } + } if ($self->{options}->{nls}) { $proj->AddIncludeDir($self->{options}->{nls} . '\include'); diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl index 2ef2cfc4e9..49dc4d5864 100644 --- a/src/tools/msvc/config_default.pl +++ b/src/tools/msvc/config_default.pl @@ -17,6 +17,7 @@ our $config = { perl => undef, # --with-perl= python => undef, # --with-python= openssl => undef, # --with-openssl= + nss => undef, # --with-nss= uuid => undef, # --with-uuid= xml => undef, # --with-libxml= xslt => undef, # --with-libxslt= -- 2.21.1 (Apple Git-122.3)