From c76b6efca0f52fe4deb6003009f4f8730201f041 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 8 Mar 2018 14:05:33 -0500 Subject: [PATCH v6] GnuTLS support --- configure | 258 ++++++-- configure.in | 37 +- doc/src/sgml/client-auth.sgml | 2 +- doc/src/sgml/config.sgml | 81 ++- doc/src/sgml/installation.sgml | 47 +- doc/src/sgml/libpq.sgml | 54 +- doc/src/sgml/runtime.sgml | 13 +- doc/src/sgml/sslinfo.sgml | 1 + src/Makefile.global.in | 1 + src/backend/libpq/Makefile | 4 +- src/backend/libpq/be-secure-gnutls.c | 820 +++++++++++++++++++++++++ src/backend/libpq/be-secure-openssl.c | 4 +- src/backend/libpq/be-secure.c | 4 + src/backend/libpq/hba.c | 2 +- src/backend/utils/misc/guc.c | 38 ++ src/backend/utils/misc/postgresql.conf.sample | 7 +- src/common/Makefile | 4 +- src/common/sha2_gnutls.c | 99 +++ src/include/common/sha2.h | 14 +- src/include/libpq/libpq-be.h | 13 +- src/include/libpq/libpq.h | 2 + src/include/pg_config.h.in | 17 + src/include/pg_config_manual.h | 2 +- src/interfaces/libpq/.gitignore | 1 + src/interfaces/libpq/Makefile | 14 +- src/interfaces/libpq/fe-secure-gnutls.c | 836 ++++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 2 +- src/interfaces/libpq/libpq-fe.h | 2 +- src/interfaces/libpq/libpq-int.h | 14 +- src/port/pg_strong_random.c | 18 +- src/test/Makefile | 2 +- src/test/ssl/Makefile | 2 +- src/test/ssl/t/001_ssltests.pl | 65 +- src/test/ssl/t/002_scram.pl | 2 +- src/tools/msvc/Mkvcbuild.pm | 10 + src/tools/pgindent/typedefs.list | 3 + 36 files changed, 2360 insertions(+), 135 deletions(-) create mode 100644 src/backend/libpq/be-secure-gnutls.c create mode 100644 src/common/sha2_gnutls.c create mode 100644 src/interfaces/libpq/fe-secure-gnutls.c diff --git a/configure b/configure index 3943711283..f2d4aa502a 100755 --- a/configure +++ b/configure @@ -707,6 +707,7 @@ UUID_EXTRA_OBJS with_uuid with_systemd with_selinux +with_gnutls with_openssl with_ldap with_krb_srvnam @@ -838,6 +839,7 @@ with_bsd_auth with_ldap with_bonjour with_openssl +with_gnutls with_selinux with_systemd with_readline @@ -1532,6 +1534,7 @@ Optional Packages: --with-ldap build with LDAP support --with-bonjour build with Bonjour support --with-openssl build with OpenSSL support + --with-gnutls build with GnuTLS 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 @@ -1999,6 +2002,52 @@ $as_echo "$ac_res" >&6; } } # ac_fn_c_check_func +# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES +# --------------------------------------------- +# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR +# accordingly. +ac_fn_c_check_decl () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + as_decl_name=`echo $2|sed 's/ *(.*//'` + as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'` + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5 +$as_echo_n "checking whether $as_decl_name is declared... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main () +{ +#ifndef $as_decl_name +#ifdef __cplusplus + (void) $as_decl_use; +#else + (void) $as_decl_name; +#endif +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_decl + # ac_fn_c_check_member LINENO AGGR MEMBER VAR INCLUDES # ---------------------------------------------------- # Tries to find if the field MEMBER exists in type AGGR, after including @@ -2292,52 +2341,6 @@ rm -f conftest.val as_fn_set_status $ac_retval } # ac_fn_c_compute_int - -# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES -# --------------------------------------------- -# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR -# accordingly. -ac_fn_c_check_decl () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - as_decl_name=`echo $2|sed 's/ *(.*//'` - as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'` - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5 -$as_echo_n "checking whether $as_decl_name is declared... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$4 -int -main () -{ -#ifndef $as_decl_name -#ifdef __cplusplus - (void) $as_decl_use; -#else - (void) $as_decl_name; -#endif -#endif - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - eval "$3=yes" -else - eval "$3=no" -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - -} # ac_fn_c_check_decl cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. @@ -6001,6 +6004,47 @@ fi $as_echo "$with_openssl" >&6; } + +# +# GnuTLS +# +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with GnuTLS support" >&5 +$as_echo_n "checking whether to build with GnuTLS support... " >&6; } + + + +# Check whether --with-gnutls was given. +if test "${with_gnutls+set}" = set; then : + withval=$with_gnutls; + case $withval in + yes) + +$as_echo "#define USE_GNUTLS 1" >>confdefs.h + + ;; + no) + : + ;; + *) + as_fn_error $? "no argument expected for --with-gnutls option" "$LINENO" 5 + ;; + esac + +else + with_gnutls=no + +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_gnutls" >&5 +$as_echo "$with_gnutls" >&6; } + + +if test "$with_openssl" = yes && test "$with_gnutls" = yes; then + as_fn_error $? "cannot specify both --with-openssl and --with-gnutls" "$LINENO" 5 +fi + + # # SELinux # @@ -10176,6 +10220,107 @@ done fi +if test "$with_gnutls" = yes ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing gnutls_init" >&5 +$as_echo_n "checking for library containing gnutls_init... " >&6; } +if ${ac_cv_search_gnutls_init+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$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 gnutls_init (); +int +main () +{ +return gnutls_init (); + ; + return 0; +} +_ACEOF +for ac_lib in '' gnutls; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_gnutls_init=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_gnutls_init+:} false; then : + break +fi +done +if ${ac_cv_search_gnutls_init+:} false; then : + +else + ac_cv_search_gnutls_init=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_gnutls_init" >&5 +$as_echo "$ac_cv_search_gnutls_init" >&6; } +ac_res=$ac_cv_search_gnutls_init +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +else + as_fn_error $? "library 'gnutls' is required for GnuTLS" "$LINENO" 5 +fi + + # GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted + # certificate chains. + ac_fn_c_check_decl "$LINENO" "GNUTLS_ALPN_SERVER_PRECEDENCE" "ac_cv_have_decl_GNUTLS_ALPN_SERVER_PRECEDENCE" "#include +#include + +" +if test "x$ac_cv_have_decl_GNUTLS_ALPN_SERVER_PRECEDENCE" = xyes; then : + ac_have_decl=1 +else + ac_have_decl=0 +fi + +cat >>confdefs.h <<_ACEOF +#define HAVE_DECL_GNUTLS_ALPN_SERVER_PRECEDENCE $ac_have_decl +_ACEOF +ac_fn_c_check_decl "$LINENO" "GNUTLS_X509_CRT_LIST_SORT" "ac_cv_have_decl_GNUTLS_X509_CRT_LIST_SORT" "#include +#include + +" +if test "x$ac_cv_have_decl_GNUTLS_X509_CRT_LIST_SORT" = xyes; then : + ac_have_decl=1 +else + ac_have_decl=0 +fi + +cat >>confdefs.h <<_ACEOF +#define HAVE_DECL_GNUTLS_X509_CRT_LIST_SORT $ac_have_decl +_ACEOF + + for ac_func in gnutls_pkcs11_set_pin_function +do : + ac_fn_c_check_func "$LINENO" "gnutls_pkcs11_set_pin_function" "ac_cv_func_gnutls_pkcs11_set_pin_function" +if test "x$ac_cv_func_gnutls_pkcs11_set_pin_function" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION 1 +_ACEOF + +fi +done + +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; } @@ -10984,6 +11129,17 @@ else fi +fi + +if test "$with_gnutls" = yes ; then + ac_fn_c_check_header_mongrel "$LINENO" "gnutls/gnutls.h" "ac_cv_header_gnutls_gnutls_h" "$ac_includes_default" +if test "x$ac_cv_header_gnutls_gnutls_h" = xyes; then : + +else + as_fn_error $? "header file is required for GnuTLS" "$LINENO" 5 +fi + + fi if test "$with_pam" = yes ; then @@ -15741,9 +15897,11 @@ fi # in the template or configure command line. # If not selected manually, try to select a source automatically. -if test "$enable_strong_random" = "yes" && test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then +if test "$enable_strong_random" = "yes" && test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_GNUTLS_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then if test x"$with_openssl" = x"yes" ; then USE_OPENSSL_RANDOM=1 + elif test x"$with_gnutls" = x"yes" ; then + USE_GNUTLS_RANDOM=1 elif test "$PORTNAME" = "win32" ; then USE_WIN32_RANDOM=1 else @@ -15782,6 +15940,12 @@ $as_echo "#define USE_OPENSSL_RANDOM 1" >>confdefs.h { $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5 $as_echo "OpenSSL" >&6; } + elif test x"$USE_GNUTLS_RANDOM" = x"1" ; then + +$as_echo "#define USE_GNUTLS_RANDOM 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: GnuTLS" >&5 +$as_echo "GnuTLS" >&6; } elif test x"$USE_WIN32_RANDOM" = x"1" ; then $as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h diff --git a/configure.in b/configure.in index 1babdbb755..2aa132b7d1 100644 --- a/configure.in +++ b/configure.in @@ -706,6 +706,21 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support], AC_MSG_RESULT([$with_openssl]) AC_SUBST(with_openssl) + +# +# GnuTLS +# +AC_MSG_CHECKING([whether to build with GnuTLS support]) +PGAC_ARG_BOOL(with, gnutls, no, [build with GnuTLS support], + [AC_DEFINE([USE_GNUTLS], 1, [Define to build with GnuTLS support. (--with-gnutls)])]) +AC_MSG_RESULT([$with_gnutls]) +AC_SUBST(with_gnutls) + +if test "$with_openssl" = yes && test "$with_gnutls" = yes; then + AC_MSG_ERROR([cannot specify both --with-openssl and --with-gnutls]) +fi + + # # SELinux # @@ -1080,6 +1095,17 @@ if test "$with_openssl" = yes ; then AC_CHECK_FUNCS([CRYPTO_lock]) fi +if test "$with_gnutls" = yes ; then + AC_SEARCH_LIBS(gnutls_init, gnutls, [], [AC_MSG_ERROR([library 'gnutls' is required for GnuTLS])]) + # GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted + # certificate chains. + AC_CHECK_DECLS([GNUTLS_ALPN_SERVER_PRECEDENCE, GNUTLS_X509_CRT_LIST_SORT], [], [], +[#include +#include +]) + AC_CHECK_FUNCS([gnutls_pkcs11_set_pin_function]) +fi + if test "$with_pam" = yes ; then AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])]) fi @@ -1228,6 +1254,10 @@ 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_gnutls" = yes ; then + AC_CHECK_HEADER(gnutls/gnutls.h, [], [AC_MSG_ERROR([header file is required for GnuTLS])]) +fi + if test "$with_pam" = yes ; then AC_CHECK_HEADERS(security/pam_appl.h, [], [AC_CHECK_HEADERS(pam/pam_appl.h, [], @@ -1997,9 +2027,11 @@ fi # in the template or configure command line. # If not selected manually, try to select a source automatically. -if test "$enable_strong_random" = "yes" && test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then +if test "$enable_strong_random" = "yes" && test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_GNUTLS_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then if test x"$with_openssl" = x"yes" ; then USE_OPENSSL_RANDOM=1 + elif test x"$with_gnutls" = x"yes" ; then + USE_GNUTLS_RANDOM=1 elif test "$PORTNAME" = "win32" ; then USE_WIN32_RANDOM=1 else @@ -2016,6 +2048,9 @@ if test "$enable_strong_random" = yes ; then if test x"$USE_OPENSSL_RANDOM" = x"1" ; then AC_DEFINE(USE_OPENSSL_RANDOM, 1, [Define to use OpenSSL for random number generation]) AC_MSG_RESULT([OpenSSL]) + elif test x"$USE_GNUTLS_RANDOM" = x"1" ; then + AC_DEFINE(USE_GNUTLS_RANDOM, 1, [Define to use GnuTLS for random number generation]) + AC_MSG_RESULT([GnuTLS]) 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]) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 53832d08e2..27f31102d7 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1789,7 +1789,7 @@ RADIUS Authentication The encryption vector used will only be cryptographically strong if PostgreSQL is built with support for - OpenSSL. In other cases, the transmission to the + an SSL library (e.g., OpenSSL). In other cases, the transmission to the RADIUS server should only be considered obfuscated, not secured, and external security measures should be applied if necessary. diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 3a8fc7d803..1770ed5025 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1156,6 +1156,37 @@ SSL + + ssl_dh_params_file (string) + + ssl_dh_params_file configuration parameter + + + + + Specifies the name of the file containing Diffie-Hellman parameters + used for so-called ephemeral DH family of SSL ciphers. The default is + empty, in which case compiled-in default DH parameters used. Using + custom DH parameters reduces the exposure if an attacker manages to + crack the well-known compiled-in DH parameters. You can create your own + DH parameters file with the command + openssl dhparam -out dhparams.pem 2048. + + + + This parameter can only be set in the postgresql.conf + file or on the server command line. + + + + + + + The following settings are only applicable if the + OpenSSL library is used. + + + ssl_ciphers (string) @@ -1289,31 +1320,41 @@ SSL + - - ssl_dh_params_file (string) + + The following settings are only applicable if the + GnuTLS library is used. + + + + + gnutls_priority (string) - ssl_dh_params_file configuration parameter + gnutls_priority configuration parameter - Specifies the name of the file containing Diffie-Hellman parameters - used for so-called ephemeral DH family of SSL ciphers. The default is - empty, in which case compiled-in default DH parameters used. Using - custom DH parameters reduces the exposure if an attacker manages to - crack the well-known compiled-in DH parameters. You can create your own - DH parameters file with the command - openssl dhparam -out dhparams.pem 2048. + Sets the priorities for the cipher suites supported by GnuTLS. This + can be used to specify which SSL cipher suites are allowed to be + used on secure connections and related settings. See the + documentation on GnuTLS priority strings for details. + This parameter can only be set in the postgresql.conf + file or on the server command line. - This parameter can only be set in the postgresql.conf - file or on the server command line. + The default value is NORMAL:%SERVER_PRECEDENCE (or + just NORMAL with older + GnuTLS versions that don't support the + server precedence setting). The default is usually a reasonable + choice unless you have specific security requirements. + @@ -8120,6 +8161,22 @@ Preset Options + + ssl_library (string) + + ssl_library configuration parameter + + + + + Reports the name of the SSL library that this PostgreSQL server was + built with (even if SSL is not currently configured or in use on this + instance), for example OpenSSL or + GnuTLS, or an empty string if none. + + + + wal_block_size (integer) diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml index 141494c651..9d10844ea8 100644 --- a/doc/src/sgml/installation.sgml +++ b/doc/src/sgml/installation.sgml @@ -244,9 +244,9 @@ Requirements - You need OpenSSL, if you want to support - encrypted client connections. The minimum required version is - 0.9.8. + If you want to support encrypted client connections (SSL/TLS), you need + OpenSSL (at least version 0.9.8) or + GnuTLS. @@ -727,6 +727,29 @@ Configuration + + + + GnuTLS + SSL + + + + + Build with support for TLS/SSL + (encrypted) connections using the GnuTLS + package. configure will check for the required + header files and libraries to make sure that your + GnuTLS installation is sufficient before + proceeding. + + + + See also for an alternative. + + + + @@ -798,12 +821,16 @@ Configuration - Build with support for SSL (encrypted) - connections. This requires the OpenSSL - package to be installed. configure will check - for the required header files and libraries to make sure that - your OpenSSL installation is sufficient - before proceeding. + Build with support for SSL/TLS + (encrypted) connections using the OpenSSL + package. configure will check for the required + header files and libraries to make sure that your + OpenSSL installation is sufficient before + proceeding. + + + + See also for an alternative. @@ -2284,7 +2311,7 @@ Cygwin - OpenSSL is not supported. + SSL is not supported. diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index da9421486b..9d3ee7229b 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1442,8 +1442,9 @@ Parameter Key Words compressed. If set to 0, compression will be disabled (this requires OpenSSL 1.0.0 or later). + GnuTLS does not support SSL compression. This parameter is ignored if a connection without SSL is made, - or if the version of OpenSSL used does not support + or if the SSL library used does not support it. @@ -2141,8 +2142,8 @@ Connection Status Functions library - Name of the SSL implementation in use. (Currently, only - "OpenSSL" is implemented) + Name of the SSL implementation in use, e.g., + OpenSSL or GnuTLS @@ -2211,11 +2212,11 @@ Connection Status Functions void *PQsslStruct(const PGconn *conn, const char *struct_name); + The struct(s) available depend on the SSL implementation in use. - 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. + For OpenSSL, there is one struct, available under the name "OpenSSL", + and it returns a pointer to the OpenSSL SSL struct. To use this function, code along the following lines could be used: @@ -2234,12 +2235,31 @@ Connection Status Functions /* use OpenSSL functions to access ssl */ } ]]> - - This structure can be used to verify encryption levels, check server certificates, and more. Refer to the OpenSSL documentation for information about this structure. + + Similarly, for GnuTLS, the gnutls_session_t can be + obtained under the name "GnuTLS". For example: + +#include + +... + + gnutls_session_t ssl; + + dbconn = PQconnectdb(...); + ... + + ssl = PQsslStruct(dbconn, "GnuTLS"); + if (ssl) + { + /* use GnuTLS functions to access ssl */ + } + ]]> + @@ -7632,6 +7652,7 @@ SSL Support + When OpenSSL is used, then libpq reads the system-wide OpenSSL configuration file. By default, this file is named openssl.cnf and is located in the @@ -8006,6 +8027,13 @@ SSL Library Initialization for details on the SSL API. + + If you are using OpenSSL version 1.1.0 or later + and use initialization function OPENSSL_init_ssl() or + the implicit initialization, then you don't need to do this. Similarly, + this is not necessary with GnuTLS. + + @@ -8041,6 +8069,10 @@ SSL Library Initialization before first opening a database connection. Also be sure that you have done that initialization before opening a database connection. + + + With GnuTLS, this function does nothing. + @@ -8061,12 +8093,16 @@ SSL Library Initialization - This function is equivalent to + With OpenSSL, this function is equivalent to PQinitOpenSSL(do_ssl, do_ssl). It is sufficient for applications that initialize both or neither of OpenSSL and libcrypto. + + With GnuTLS, this function does nothing. + + PQinitSSL has been present since PostgreSQL 8.0, while PQinitOpenSSL diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 587b430527..f18ce4c9ac 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2152,10 +2152,14 @@ Secure TCP/IP Connections with SSL 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 - server systems and that support in PostgreSQL is - enabled at build time (see ). + for increased security. This requires that support in + PostgreSQL is enabled at build time (see ). PostgreSQL supports + both OpenSSL and + GnuTLS as the SSL implementation. This is + chosen at build time. Third-party clients may also use other SSL + implementations. Clients and servers using different SSL implementations + are compatible. @@ -2225,6 +2229,7 @@ Basic Setup OpenSSL Configuration + When using OpenSSL, PostgreSQL reads the system-wide OpenSSL configuration file. By default, this file is named openssl.cnf and is located in the diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml index cda09aaafd..782d63fd40 100644 --- a/doc/src/sgml/sslinfo.sgml +++ b/doc/src/sgml/sslinfo.sgml @@ -17,6 +17,7 @@ sslinfo This extension won't build at all unless the installation was configured with --with-openssl. + (GnuTLS is currently not supported.) diff --git a/src/Makefile.global.in b/src/Makefile.global.in index 15c14951e8..a4c2761424 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -183,6 +183,7 @@ with_icu = @with_icu@ with_perl = @with_perl@ with_python = @with_python@ with_tcl = @with_tcl@ +with_gnutls = @with_gnutls@ with_openssl = @with_openssl@ with_selinux = @with_selinux@ with_systemd = @with_systemd@ diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 7fa2b02743..7af6e22744 100644 --- a/src/backend/libpq/Makefile +++ b/src/backend/libpq/Makefile @@ -17,7 +17,9 @@ include $(top_builddir)/src/Makefile.global OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ifaddr.o pqcomm.o \ pqformat.o pqmq.o pqsignal.o auth-scram.o -ifeq ($(with_openssl),yes) +ifeq ($(with_gnutls),yes) +OBJS += be-secure-gnutls.o +else ifeq ($(with_openssl),yes) OBJS += be-secure-openssl.o endif diff --git a/src/backend/libpq/be-secure-gnutls.c b/src/backend/libpq/be-secure-gnutls.c new file mode 100644 index 0000000000..cc8b86331d --- /dev/null +++ b/src/backend/libpq/be-secure-gnutls.c @@ -0,0 +1,820 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gnutls.c + * functions for GnuTLS support in the backend. + * + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gnutls.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_NETINET_TCP_H +#include +#include +#endif +#include +#include + +#include "libpq/libpq.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "storage/fd.h" +#include "storage/latch.h" +#include "tcop/tcopprot.h" +#include "utils/memutils.h" + + +static ssize_t my_sock_read(gnutls_transport_ptr_t h, void *buf, size_t size); +static ssize_t my_sock_write(gnutls_transport_ptr_t h, const void *buf, size_t size); +static int get_peer_certificate(gnutls_session_t ssl, gnutls_x509_crt_t * peer); +static bool load_dh_file(gnutls_dh_params_t dh, char *filename, bool isServerStart); +static bool load_dh_buffer(gnutls_dh_params_t dh, const char *, size_t, bool isServerStart); +#ifdef HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION +static int pin_function(void *userdata, int attempt, + const char *token_url, + const char *token_label, + unsigned int flags, + char *pin, size_t pin_max); +static int dummy_pin_function(void *userdata, int attempt, + const char *token_url, + const char *token_label, + unsigned int flags, + char *pin, size_t pin_max); +#endif +static int verify_cb(gnutls_session_t ssl); +static bool initialize_dh(gnutls_dh_params_t *dh_params, bool isServerStart); + +static gnutls_certificate_credentials_t tls_credentials = NULL; +static gnutls_dh_params_t tls_dh_params = NULL; +static gnutls_priority_t tls_priority = NULL; +static bool tls_initialized = false; +static bool pin_function_called = false; + + +/* ------------------------------------------------------------ */ +/* Public interface */ +/* ------------------------------------------------------------ */ + +int +be_tls_init(bool isServerStart) +{ + gnutls_certificate_credentials_t credentials = NULL; + gnutls_priority_t priority = NULL; + gnutls_dh_params_t dh_params = NULL; + int ret; + const char *err_pos; + + /* This stuff need be done only once. */ + if (!tls_initialized) + { + gnutls_global_init(); + tls_initialized = true; + } + + ret = gnutls_certificate_allocate_credentials(&credentials); + if (ret < 0) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg("could not create SSL credentials: %s", + gnutls_strerror(ret)))); + goto error; + } + + /* + * Set PIN (passphrase) callback. Note that there is no default for this + * in GnuTLS. + * + * If reloading, substitute a dummy callback, because we don't want to + * prompt for a passphrase in an already-running server. + */ +#ifdef HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION + if (isServerStart) + gnutls_pkcs11_set_pin_function(pin_function, NULL); + else + gnutls_pkcs11_set_pin_function(dummy_pin_function, NULL); +#endif + + /* + * Load and verify server's certificate and private key + */ + if (!check_ssl_key_file_permissions(ssl_key_file, isServerStart)) + goto error; + + pin_function_called = false; + + ret = gnutls_certificate_set_x509_key_file(credentials, ssl_cert_file, ssl_key_file, GNUTLS_X509_FMT_PEM); + if (ret < 0) + { + if (pin_function_called) + ereport(isServerStart ? FATAL : LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase", + ssl_key_file))); + else + ereport(isServerStart ? FATAL : LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not load server certificate \"%s\" or key file \"%s\": %s", + ssl_cert_file, ssl_key_file, gnutls_strerror(ret)))); + goto error; + } + + /* set up ephemeral DH keys */ + if (!initialize_dh(&dh_params, isServerStart)) + goto error; + + gnutls_certificate_set_dh_params(credentials, dh_params); + + /* set up the allowed cipher list */ + ret = gnutls_priority_init(&priority, gnutls_priority, &err_pos); + if (ret < 0) + { + if (ret == GNUTLS_E_INVALID_REQUEST) + ereport(isServerStart ? FATAL : LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not set the cipher list: syntax error at %s", err_pos))); + else + ereport(isServerStart ? FATAL : LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not set the cipher list: %s", gnutls_strerror(ret)))); + goto error; + } + + /* + * Load CA store, so we can verify client certificates if needed. + */ + if (ssl_ca_file[0]) + { + ret = gnutls_certificate_set_x509_trust_file(credentials, ssl_ca_file, GNUTLS_X509_FMT_PEM); + if (ret < 0) + { + ereport(isServerStart ? FATAL : LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not load root certificate file \"%s\": %s", + ssl_ca_file, gnutls_strerror(ret)))); + goto error; + } + + gnutls_certificate_set_verify_function(credentials, verify_cb); + } + + /* + * Load the Certificate Revocation List (CRL). + */ + if (ssl_crl_file[0]) + { + ret = gnutls_certificate_set_x509_crl_file(credentials, ssl_crl_file, GNUTLS_X509_FMT_PEM); + if (ret < 0) + { + ereport(isServerStart ? FATAL : LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not load SSL certificate revocation list file \"%s\": %s", + ssl_crl_file, gnutls_strerror(ret)))); + goto error; + } + } + + /* + * Success! Replace any existing credentials. + */ + if (tls_credentials) + gnutls_certificate_free_credentials(tls_credentials); + if (tls_priority) + gnutls_priority_deinit(tls_priority); + if (tls_dh_params) + gnutls_dh_params_deinit(tls_dh_params); + + tls_credentials = credentials; + tls_priority = priority; + tls_dh_params = dh_params; + + /* + * Set flag to remember whether CA store has been loaded. + */ + if (ssl_ca_file[0]) + ssl_loaded_verify_locations = true; + else + ssl_loaded_verify_locations = false; + + return 0; + +error: + if (credentials) + gnutls_certificate_free_credentials(credentials); + if (priority) + gnutls_priority_deinit(priority); + if (dh_params) + gnutls_dh_params_deinit(dh_params); + return -1; +} + +void +be_tls_destroy(void) +{ + if (tls_credentials) + gnutls_certificate_free_credentials(tls_credentials); + if (tls_priority) + gnutls_priority_deinit(tls_priority); + if (tls_dh_params) + gnutls_dh_params_deinit(tls_dh_params); + tls_credentials = NULL; + tls_priority = NULL; + tls_dh_params = NULL; + ssl_loaded_verify_locations = false; +} + +int +be_tls_open_server(Port *port) +{ + int ret; + + Assert(!port->ssl); + Assert(!port->peer); + + if (!tls_credentials || !tls_priority) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not initialize SSL connection: SSL context not set up"))); + return -1; + } + + ret = gnutls_init(&port->ssl, GNUTLS_SERVER); + if (ret < 0) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not initialize SSL connection: %s", + gnutls_strerror(ret)))); + return -1; + } + + gnutls_transport_set_ptr(port->ssl, port); + gnutls_transport_set_pull_function(port->ssl, my_sock_read); + gnutls_transport_set_push_function(port->ssl, my_sock_write); + + ret = gnutls_priority_set(port->ssl, tls_priority); + if (ret < 0) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not initialize SSL connection: %s", + gnutls_strerror(ret)))); + return -1; + } + + ret = gnutls_credentials_set(port->ssl, GNUTLS_CRD_CERTIFICATE, tls_credentials); + if (ret < 0) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not initialize SSL connection: %s", + gnutls_strerror(ret)))); + return -1; + } + + if (ssl_loaded_verify_locations) + gnutls_certificate_server_set_request(port->ssl, GNUTLS_CERT_REQUEST); + + port->ssl_in_use = true; + + do + { + ret = gnutls_handshake(port->ssl); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + if (ret < 0) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not accept SSL connection: %s", + gnutls_strerror(ret)))); + return -1; + } + + /* Get client certificate, if available. */ + ret = get_peer_certificate(port->ssl, &port->peer); + if (ret < 0 && ret != GNUTLS_E_NO_CERTIFICATE_FOUND) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("could not load peer certificates: %s", + gnutls_strerror(ret)))); + } + + /* and extract the Common Name from it. */ + port->peer_cn = NULL; + port->peer_cert_valid = false; + if (port->peer != NULL) + { + size_t len = 0; + + gnutls_x509_crt_get_dn_by_oid(port->peer, + GNUTLS_OID_X520_COMMON_NAME, + 0, 0, NULL, &len); + + if (len > 0) + { + char *peer_cn; + + peer_cn = MemoryContextAlloc(TopMemoryContext, len); + + ret = gnutls_x509_crt_get_dn_by_oid(port->peer, + GNUTLS_OID_X520_COMMON_NAME, + 0, 0, peer_cn, &len); + + if (ret != 0) + { + /* shouldn't happen */ + pfree(peer_cn); + return -1; + } + + /* + * Reject embedded NULLs in certificate common name to prevent + * attacks like CVE-2009-4034. + */ + if (len != strlen(peer_cn)) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL certificate's common name contains embedded null"))); + pfree(peer_cn); + return -1; + } + + + if (ret == 0) + port->peer_cn = peer_cn; + else + pfree(peer_cn); + + } + + port->peer_cert_valid = true; + } + + return 0; +} + +void +be_tls_close(Port *port) +{ + if (port->ssl) + { + gnutls_bye(port->ssl, GNUTLS_SHUT_RDWR); + gnutls_deinit(port->ssl); + port->ssl = NULL; + port->ssl_in_use = false; + } + + if (port->peer) + { + gnutls_x509_crt_deinit(port->peer); + port->peer = NULL; + } + + if (port->peer_cn) + { + pfree(port->peer_cn); + port->peer_cn = NULL; + } +} + +ssize_t +be_tls_read(Port *port, void *ptr, size_t len, int *waitfor) +{ + ssize_t n; + + n = gnutls_record_recv(port->ssl, ptr, len); + + if (n > 0) + return n; + + switch (n) + { + case 0: + + /* + * the SSL connnection was closed, leave it to the caller to + * ereport it + */ + errno = ECONNRESET; + n = -1; + break; + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + *waitfor = WL_SOCKET_READABLE; + errno = EWOULDBLOCK; + n = -1; + break; + default: + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL error: %s", + gnutls_strerror(n)))); + errno = ECONNRESET; + n = -1; + break; + } + + return n; +} + +ssize_t +be_tls_write(Port *port, void *ptr, size_t len, int *waitfor) +{ + ssize_t n; + + n = gnutls_record_send(port->ssl, ptr, len); + + if (n >= 0) + return n; + + switch (n) + { + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + *waitfor = WL_SOCKET_WRITEABLE; + errno = EWOULDBLOCK; + n = -1; + break; + default: + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL error: %s", + gnutls_strerror(n)))); + errno = ECONNRESET; + n = -1; + break; + } + + return n; +} + +/* ------------------------------------------------------------ */ +/* Internal functions */ +/* ------------------------------------------------------------ */ + +/* + * Private substitute transport layer: this does the sending and receiving + * using send() and recv() instead. This is so that we can enable and disable + * interrupts just while calling recv(). We cannot have interrupts occurring + * while the bulk of GnuTLS runs, because it uses malloc() and possibly other + * non-reentrant libc facilities. We also need to call send() and recv() + * directly so it gets passed through the socket/signals layer on Win32. + */ + +static ssize_t +my_sock_read(gnutls_transport_ptr_t port, void *buf, size_t size) +{ + return secure_raw_read((Port *) port, buf, size); +} + +static ssize_t +my_sock_write(gnutls_transport_ptr_t port, const void *buf, size_t size) +{ + return secure_raw_write((Port *) port, buf, size); +} + +#if !HAVE_DECL_GNUTLS_X509_CRT_LIST_SORT +/* + * GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted + * certificate chains, so we skip doing so in these earlier versions. + */ +#define GNUTLS_X509_CRT_LIST_SORT 0 +#endif + +/* + * Get peer certificate from a session + * + * Returns GNUTLS_E_NO_CERTIFICATE_FOUND when not x509 certifcate was found. + */ +static int +get_peer_certificate(gnutls_session_t ssl, gnutls_x509_crt_t * peer) +{ + if (gnutls_certificate_type_get(ssl) == GNUTLS_CRT_X509) + { + unsigned int n; + int ret; + gnutls_datum_t const *raw_certs; + gnutls_x509_crt_t *certs; + + raw_certs = gnutls_certificate_get_peers(ssl, &n); + + if (n == 0) + return GNUTLS_E_NO_CERTIFICATE_FOUND; + + certs = palloc(n * sizeof(gnutls_x509_crt_t)); + + ret = gnutls_x509_crt_list_import(certs, &n, raw_certs, + GNUTLS_X509_FMT_DER, + GNUTLS_X509_CRT_LIST_SORT); + + if (ret >= 1) + { + unsigned int i; + + for (i = 1; i < ret; i++) + gnutls_x509_crt_deinit(certs[i]); + + *peer = certs[0]; + + ret = GNUTLS_E_SUCCESS; + } + else if (ret == 0) + ret = GNUTLS_E_NO_CERTIFICATE_FOUND; + + pfree(certs); + + return ret; + } + + return GNUTLS_E_NO_CERTIFICATE_FOUND; +} + +#define MAX_DH_FILE_SIZE 10240 + +/* + * Load precomputed DH parameters. + * + * To prevent "downgrade" attacks, we perform a number of checks + * to verify that the DBA-generated DH parameters file contains + * what we expect it to contain. + */ +static bool +load_dh_file(gnutls_dh_params_t dh_params, char *filename, bool isServerStart) +{ + FILE *fp; + char buffer[MAX_DH_FILE_SIZE]; + gnutls_datum_t datum = {(unsigned char *) buffer}; + int ret; + + /* attempt to open file. It's not an error if it doesn't exist. */ + if ((fp = AllocateFile(filename, "r")) == NULL) + { + ereport(isServerStart ? FATAL : LOG, + (errcode_for_file_access(), + errmsg("could not open DH parameters file \"%s\": %m", + filename))); + return false; + } + + datum.size = fread(buffer, sizeof(buffer[0]), sizeof(buffer), fp); + + FreeFile(fp); + + if (datum.size < 0) + { + ereport(isServerStart ? FATAL : LOG, + (errcode_for_file_access(), + errmsg("could not load DH parameters file: %s", + gnutls_strerror(ret)))); + return false; + } + + ret = gnutls_dh_params_import_pkcs3(dh_params, &datum, GNUTLS_X509_FMT_PEM); + + if (ret < 0) + { + ereport(isServerStart ? FATAL : LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not load DH parameters file: %s", + gnutls_strerror(ret)))); + return false; + } + + return true; +} + +/* + * Load hardcoded DH parameters. + * + * To prevent problems if the DH parameters files don't even + * exist, we can load DH parameters hardcoded into this file. + */ +static bool +load_dh_buffer(gnutls_dh_params_t dh_params, const char *buffer, size_t len, bool isServerStart) +{ + gnutls_datum_t datum = {(unsigned char *) buffer, len}; + int ret; + + ret = gnutls_dh_params_import_pkcs3(dh_params, &datum, GNUTLS_X509_FMT_PEM); + + if (ret < 0) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg_internal("DH load buffer: %s", gnutls_strerror(ret)))); + return false; + } + + return true; +} + +#ifdef HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION +/* + * PIN callback + */ +static int +pin_function(void *userdata, int attempt, + const char *token_url, + const char *token_label, + unsigned int flags, + char *pin, size_t pin_max) +{ + simple_prompt(token_url, pin, pin_max, false); + return 0; +} + +/* + * Dummy PIN callback during server reload + * + * The standard callback is no good during a postmaster SIGHUP cycle, not to + * mention SSL context reload in an EXEC_BACKEND postmaster child. So + * override it with this dummy function that just returns an error, + * guaranteeing failure. + */ +static int +dummy_pin_function(void *userdata, int attempt, + const char *token_url, + const char *token_label, + unsigned int flags, + char *pin, size_t pin_max) +{ + /* Set flag to change the error message we'll report */ + pin_function_called = true; + return -1; +} +#endif + +/* + * Certificate verification callback + * + * This callback is where we verify the identity of the client. + */ +static int +verify_cb(gnutls_session_t ssl) +{ + unsigned int status; + int ret; + + ret = gnutls_certificate_verify_peers2(ssl, &status); + + if (ret == GNUTLS_E_NO_CERTIFICATE_FOUND) + return 0; + else if (ret < 0) + return ret; + + return status; +} + +/* + * Set DH parameters for generating ephemeral DH keys. The + * DH parameters can take a long time to compute, so they must be + * precomputed. + * + * Since few sites will bother to create a parameter file, we also + * also provide a fallback to the parameters provided by the + * OpenSSL project. + * + * These values can be static (once loaded or computed) since the + * OpenSSL library can efficiently generate random keys from the + * information provided. + */ +static bool +initialize_dh(gnutls_dh_params_t *dh_params, bool isServerStart) +{ + bool loaded = false; + int ret; + + ret = gnutls_dh_params_init(dh_params); + if (ret < 0) + { + ereport(isServerStart ? FATAL : LOG, + (errmsg_internal("DH init error: %s", + gnutls_strerror(ret)))); + return false; + } + + if (ssl_dh_params_file[0]) + loaded = load_dh_file(*dh_params, ssl_dh_params_file, isServerStart); + if (!loaded) + loaded = load_dh_buffer(*dh_params, FILE_DH2048, sizeof(FILE_DH2048), isServerStart); + if (!loaded) + { + ereport(isServerStart ? FATAL : LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + (errmsg("DH: could not load DH parameters")))); + return false; + } + + return true; +} + +int +be_tls_get_cipher_bits(Port *port) +{ + if (port->ssl) + return gnutls_cipher_get_key_size(gnutls_cipher_get(port->ssl)) * 8; + else + return 0; +} + +bool +be_tls_get_compression(Port *port) +{ + if (port->ssl) + { + gnutls_compression_method_t comp = gnutls_compression_get(port->ssl); + + return comp != GNUTLS_COMP_UNKNOWN && comp != GNUTLS_COMP_NULL; + } + else + return false; +} + +const char * +be_tls_get_version(Port *port) +{ + if (port->ssl) + return gnutls_protocol_get_name(gnutls_protocol_get_version(port->ssl)); + else + return NULL; +} + +const char * +be_tls_get_cipher(Port *port) +{ + if (port->ssl) + return gnutls_cipher_get_name(gnutls_cipher_get(port->ssl)); + else + return NULL; +} + +void +be_tls_get_peerdn_name(Port *port, char *ptr, size_t len) +{ + if (port->peer) + { + int ret; + + ret = gnutls_x509_crt_get_dn_by_oid(port->peer, + GNUTLS_OID_X520_COMMON_NAME, + 0, 0, ptr, &len); + + if (ret != 0) + ptr[0] = '\0'; + } + else + ptr[0] = '\0'; +} + +char * +be_tls_get_peer_finished(Port *port, size_t *len) +{ + gnutls_datum_t cb; + int ret; + char *result; + + ret = gnutls_session_channel_binding(port->ssl, GNUTLS_CB_TLS_UNIQUE, &cb); + + if (ret < 0) + ereport(FATAL, + (errmsg("could not get SSL channel binding data: %s", + gnutls_strerror(ret)))); + + result = palloc(cb.size); + memcpy(result, cb.data, cb.size); + *len = cb.size; + + return result; +} + +/* + * Stub function for SCRAM channel binding type tls-server-end-point, + * currently not supported with GnuTLS. + */ +char * +be_tls_get_certificate_hash(Port *port, size_t *len) +{ + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("channel binding type \"tls-server-end-point\" is not supported by this build"))); + return NULL; +} diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index e1ddfb3c16..f2aeb58a30 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -212,10 +212,8 @@ be_tls_init(bool isServerStart) } } - /*---------- + /* * Load the Certificate Revocation List (CRL). - * http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,00.html - *---------- */ if (ssl_crl_file[0]) { diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 76c0a9e39b..624765aec3 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -40,6 +40,7 @@ #include "storage/proc.h" +char *ssl_library; char *ssl_cert_file; char *ssl_key_file; char *ssl_ca_file; @@ -59,6 +60,9 @@ char *SSLECDHCurve; /* GUC variable: if false, prefer client ciphers */ bool SSLPreferServerCiphers; +/* GUC variable controlling GnuTLS priorities */ +char *gnutls_priority; + /* ------------------------------------------------------------ */ /* Procedures common to all secure sessions */ /* ------------------------------------------------------------ */ diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index acf625e4ec..62531c990b 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1016,7 +1016,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-gnutls or --with-openssl to use SSL connections."), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); *err_msg = "hostssl record cannot match because SSL is not supported by this build"; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index a4f9b3668e..88ce482d3c 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -3542,6 +3542,25 @@ static struct config_string ConfigureNamesString[] = check_canonical_path, NULL, NULL }, + { + {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Name of the SSL library."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &ssl_library, +#ifdef USE_SSL +#if defined(USE_OPENSSL) + "OpenSSL", +#elif defined(USE_GNUTLS) + "GnuTLS", +#else +#error SSL implementation must set ssl_library string +#endif +#endif + NULL, NULL, NULL + }, + { {"ssl_cert_file", PGC_SIGHUP, CONN_AUTH_SSL, gettext_noop("Location of the SSL server certificate file."), @@ -3644,6 +3663,25 @@ static struct config_string ConfigureNamesString[] = NULL, NULL, NULL }, + { + {"gnutls_priority", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Sets priorities for the cipher suites supported by GnuTLS."), + NULL, + GUC_SUPERUSER_ONLY + }, + &gnutls_priority, +#ifdef USE_SSL +#if HAVE_DECL_GNUTLS_ALPN_SERVER_PRECEDENCE + "NORMAL:%SERVER_PRECEDENCE", +#else + "NORMAL", +#endif +#else + "none", +#endif + NULL, NULL, NULL + }, + { {"ssl_dh_params_file", PGC_SIGHUP, CONN_AUTH_SSL, gettext_noop("Location of the SSL DH parameters file."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 39272925fb..45f892b968 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -100,10 +100,15 @@ #ssl_cert_file = 'server.crt' #ssl_crl_file = '' #ssl_key_file = 'server.key' +#ssl_dh_params_file = '' + +# Parameters for OpenSSL. Leave these commented out if not using OpenSSL. #ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers #ssl_prefer_server_ciphers = on #ssl_ecdh_curve = 'prime256v1' -#ssl_dh_params_file = '' + +# Parameters for GnuTLS. Leave these commented out if not using GnuTLS. +#gnutls_priority = 'NORMAL:%SERVER_PRECEDENCE' #------------------------------------------------------------------------------ diff --git a/src/common/Makefile b/src/common/Makefile index 80e78d72fe..cb1c91373a 100644 --- a/src/common/Makefile +++ b/src/common/Makefile @@ -45,7 +45,9 @@ OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \ rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \ username.o wait_error.o -ifeq ($(with_openssl),yes) +ifeq ($(with_gnutls),yes) +OBJS_COMMON += sha2_gnutls.o +else ifeq ($(with_openssl),yes) OBJS_COMMON += sha2_openssl.o else OBJS_COMMON += sha2.o diff --git a/src/common/sha2_gnutls.c b/src/common/sha2_gnutls.c new file mode 100644 index 0000000000..279b5370fa --- /dev/null +++ b/src/common/sha2_gnutls.c @@ -0,0 +1,99 @@ +/*------------------------------------------------------------------------- + * + * sha2_gnutlsl.c + * Set of wrapper routines on top of GnuTLS to support SHA-224 + * SHA-256, SHA-384 and SHA-512 functions. + * + * This should only be used if code is compiled with GnuTLS support. + * + * Portions Copyright (c) 2017, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/common/sha2_gnutls.c + * + *------------------------------------------------------------------------- + */ + +#ifndef FRONTEND +#include "postgres.h" +#else +#include "postgres_fe.h" +#endif + +#include "common/sha2.h" + +/* Interface routines for SHA-256 */ +void +pg_sha256_init(pg_sha256_ctx *ctx) +{ + gnutls_hash_init(ctx, GNUTLS_DIG_SHA256); +} + +void +pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len) +{ + gnutls_hash(*ctx, data, len); +} + +void +pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest) +{ + gnutls_hash_deinit(*ctx, dest); +} + +/* Interface routines for SHA-512 */ +void +pg_sha512_init(pg_sha512_ctx *ctx) +{ + gnutls_hash_init(ctx, GNUTLS_DIG_SHA512); +} + +void +pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len) +{ + gnutls_hash(*ctx, data, len); +} + +void +pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest) +{ + gnutls_hash_deinit(*ctx, dest); +} + +/* Interface routines for SHA-384 */ +void +pg_sha384_init(pg_sha384_ctx *ctx) +{ + gnutls_hash_init(ctx, GNUTLS_DIG_SHA384); +} + +void +pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len) +{ + gnutls_hash(*ctx, data, len); +} + +void +pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest) +{ + gnutls_hash_deinit(*ctx, dest); +} + +/* Interface routines for SHA-224 */ +void +pg_sha224_init(pg_sha224_ctx *ctx) +{ + gnutls_hash_init(ctx, GNUTLS_DIG_SHA224); +} + +void +pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len) +{ + gnutls_hash(*ctx, data, len); +} + +void +pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest) +{ + gnutls_hash_deinit(*ctx, dest); +} diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h index f3fd0d0d28..b064554a3e 100644 --- a/src/include/common/sha2.h +++ b/src/include/common/sha2.h @@ -50,8 +50,11 @@ #ifndef _PG_SHA2_H_ #define _PG_SHA2_H_ -#ifdef USE_SSL +#if defined(USE_OPENSSL) #include +#elif defined(USE_GNUTLS) +#include +#include #endif /*** SHA224/256/384/512 Various Length Definitions ***********************/ @@ -69,11 +72,16 @@ #define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1) /* Context Structures for SHA-1/224/256/384/512 */ -#ifdef USE_SSL +#if defined(USE_OPENSSL) typedef SHA256_CTX pg_sha256_ctx; typedef SHA512_CTX pg_sha512_ctx; typedef SHA256_CTX pg_sha224_ctx; typedef SHA512_CTX pg_sha384_ctx; +#elif defined(USE_GNUTLS) +typedef gnutls_hash_hd_t pg_sha256_ctx; +typedef gnutls_hash_hd_t pg_sha512_ctx; +typedef gnutls_hash_hd_t pg_sha224_ctx; +typedef gnutls_hash_hd_t pg_sha384_ctx; #else typedef struct pg_sha256_ctx { @@ -89,7 +97,7 @@ typedef struct pg_sha512_ctx } pg_sha512_ctx; typedef struct pg_sha256_ctx pg_sha224_ctx; typedef struct pg_sha512_ctx pg_sha384_ctx; -#endif /* USE_SSL */ +#endif /* Interface routines for SHA224/256/384/512 */ extern void pg_sha224_init(pg_sha224_ctx *ctx); diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 7698cd1f88..0208ac6f3f 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -19,9 +19,11 @@ #define LIBPQ_BE_H #include -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) #include #include +#elif defined(USE_GNUTLS) +#include #endif #ifdef HAVE_NETINET_TCP_H #include @@ -183,12 +185,15 @@ 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 library specific structures. (Keep these last so that the locations + * of other fields are the same whether or not you build with SSL.) */ -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) SSL *ssl; X509 *peer; +#elif defined(USE_GNUTLS) + gnutls_session_t ssl; + gnutls_x509_crt_t peer; #endif } Port; diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 255222acd7..b7e5d41344 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -75,6 +75,7 @@ extern int pq_putbytes(const char *s, size_t len); /* * prototypes for functions in be-secure.c */ +extern char *ssl_library; extern char *ssl_cert_file; extern char *ssl_key_file; extern char *ssl_ca_file; @@ -100,5 +101,6 @@ extern WaitEventSet *FeBeWaitSet; extern char *SSLCipherSuites; extern char *SSLECDHCurve; extern bool SSLPreferServerCiphers; +extern char *gnutls_priority; #endif /* LIBPQ_H */ diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index f98f773ff0..023b68bee2 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -134,6 +134,14 @@ don't. */ #undef HAVE_DECL_F_FULLFSYNC +/* Define to 1 if you have the declaration of `GNUTLS_ALPN_SERVER_PRECEDENCE', + and to 0 if you don't. */ +#undef HAVE_DECL_GNUTLS_ALPN_SERVER_PRECEDENCE + +/* Define to 1 if you have the declaration of `GNUTLS_X509_CRT_LIST_SORT', and + to 0 if you don't. */ +#undef HAVE_DECL_GNUTLS_X509_CRT_LIST_SORT + /* Define to 1 if you have the declaration of `posix_fadvise', and to 0 if you don't. */ #undef HAVE_DECL_POSIX_FADVISE @@ -256,6 +264,9 @@ /* Define to 1 if you have the `gettimeofday' function. */ #undef HAVE_GETTIMEOFDAY +/* Define to 1 if you have the `gnutls_pkcs11_set_pin_function' function. */ +#undef HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION + /* Define to 1 if you have the header file. */ #undef HAVE_GSSAPI_GSSAPI_H @@ -834,6 +845,12 @@ (--enable-float8-byval) */ #undef USE_FLOAT8_BYVAL +/* Define to build with GnuTLS support. (--with-gnutls) */ +#undef USE_GNUTLS + +/* Define to use GnuTLS for random number generation */ +#undef USE_GNUTLS_RANDOM + /* Define to build with ICU support. (--with-icu) */ #undef USE_ICU diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h index b309395f11..e2ff2ce693 100644 --- a/src/include/pg_config_manual.h +++ b/src/include/pg_config_manual.h @@ -165,7 +165,7 @@ * implementation. (Currently, only OpenSSL is supported, but we might add * more implementations in the future.) */ -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) || defined(USE_GNUTLS) #define USE_SSL #endif diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore index 5c232ae2d1..371800f0c7 100644 --- a/src/interfaces/libpq/.gitignore +++ b/src/interfaces/libpq/.gitignore @@ -27,6 +27,7 @@ /base64.c /scram-common.c /sha2.c +/sha2_gnutls.c /sha2_openssl.c /saslprep.c /unicode_norm.c diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index abe0a50e98..b5958daf1d 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -51,7 +51,9 @@ OBJS += encnames.o wchar.o # src/common OBJS += base64.o ip.o md5.o scram-common.o saslprep.o unicode_norm.o -ifeq ($(with_openssl),yes) +ifeq ($(with_gnutls),yes) +OBJS += fe-secure-gnutls.o fe-secure-common.o sha2_gnutls.o +else ifeq ($(with_openssl),yes) OBJS += fe-secure-openssl.o fe-secure-common.o sha2_openssl.o else OBJS += sha2.o @@ -78,12 +80,12 @@ endif # shared library link. (The order in which you list them here doesn't # matter.) ifneq ($(PORTNAME), win32) -SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS) +SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lgnutls -lsocket -lnsl -lresolv -lintl, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS) else -SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE) +SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lgnutls -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE) endif ifeq ($(PORTNAME), win32) -SHLIB_LINK += -lshell32 -lws2_32 -lsecur32 $(filter -leay32 -lssleay32 -lcomerr32 -lkrb5_32, $(LIBS)) +SHLIB_LINK += -lshell32 -lws2_32 -lsecur32 $(filter -leay32 -lssleay32 -lgnutls -lcomerr32 -lkrb5_32, $(LIBS)) endif SHLIB_EXPORTS = exports.txt @@ -106,7 +108,7 @@ backend_src = $(top_srcdir)/src/backend chklocale.c crypt.c erand48.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c strnlen.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/% rm -f $@ && $(LN_S) $< . -ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm.c: % : $(top_srcdir)/src/common/% +ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c sha2_gnutls.c saslprep.c unicode_norm.c: % : $(top_srcdir)/src/common/% rm -f $@ && $(LN_S) $< . encnames.c wchar.c: % : $(backend_src)/utils/mb/% @@ -156,7 +158,7 @@ clean distclean: clean-lib rm -f pg_config_paths.h # Remove files we (may have) symlinked in from src/port and other places rm -f chklocale.c crypt.c erand48.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c strnlen.c thread.c win32error.c win32setlocale.c - rm -f ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm.c + rm -f ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c sha2_gnutls.c saslprep.c unicode_norm.c rm -f encnames.c wchar.c maintainer-clean: distclean maintainer-clean-lib diff --git a/src/interfaces/libpq/fe-secure-gnutls.c b/src/interfaces/libpq/fe-secure-gnutls.c new file mode 100644 index 0000000000..8a82edb6c3 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gnutls.c @@ -0,0 +1,836 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-gnutls.c + * GnuTLS support + * + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/interfaces/libpq/fe-secure-gnutls.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" +#include "fe-secure-common.h" +#include "libpq-int.h" + +#include + +#ifdef ENABLE_THREAD_SAFETY +#ifdef WIN32 +#include "pthread-win32.h" +#else +#include +#endif +#endif + +#include +#include + +static int initialize_SSL(PGconn *conn); +static PostgresPollingStatusType open_client_SSL(PGconn *); + +static ssize_t my_sock_read(gnutls_transport_ptr_t h, void *buf, size_t size); +static ssize_t my_sock_write(gnutls_transport_ptr_t h, const void *buf, size_t size); +static int get_peer_certificate(gnutls_session_t ssl, gnutls_x509_crt_t *peer); +static int verify_cb(gnutls_session_t ssl); + +static bool ssl_lib_initialized = false; + +#ifdef ENABLE_THREAD_SAFETY +#ifndef WIN32 +static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER; +#else +static pthread_mutex_t ssl_config_mutex = NULL; +static long win32_ssl_create_mutex = 0; +#endif +#endif /* ENABLE_THREAD_SAFETY */ + + +/* ------------------------------------------------------------ */ +/* Procedures common to all secure sessions */ +/* ------------------------------------------------------------ */ + +void +pgtls_init_library(bool do_ssl, int do_crypto) +{ + /* not used with GnuTLS */ +} + +PostgresPollingStatusType +pgtls_open_client(PGconn *conn) +{ + /* First time through? */ + if (conn->ssl == NULL) + { + /* + * Create a connection-specific SSL object, and load client + * certificate, private key, and trusted CA certs. + */ + if (initialize_SSL(conn) != 0) + { + /* initialize_SSL already put a message in conn->errorMessage */ + pgtls_close(conn); + return PGRES_POLLING_FAILED; + } + } + + /* Begin or continue the actual handshake */ + return open_client_SSL(conn); +} + +ssize_t +pgtls_read(PGconn *conn, void *ptr, size_t len) +{ + ssize_t n; + int result_errno; + char sebuf[256]; + + n = gnutls_record_recv(conn->ssl, ptr, len); + + if (n > 0) + { + SOCK_ERRNO_SET(0); + return n; + } + + switch (n) + { + case 0: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL connection has been closed unexpectedly\n")); + result_errno = ECONNRESET; + n = -1; + break; + case GNUTLS_E_REHANDSHAKE: + /* Ignore re-handsake requests and have the caller retry */ + case GNUTLS_E_INTERRUPTED: + result_errno = EINTR; + n = -1; + break; + case GNUTLS_E_AGAIN: + result_errno = EAGAIN; + n = -1; + break; +#ifdef GNUTLS_E_PREMATURE_TERMINATION + case GNUTLS_E_PREMATURE_TERMINATION: +#endif + case GNUTLS_E_PUSH_ERROR: + result_errno = SOCK_ERRNO; + n = -1; + if (result_errno == EPIPE || result_errno == ECONNRESET) + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "server closed the connection unexpectedly\n" + "\tThis probably means the server terminated abnormally\n" + "\tbefore or while processing the request.\n")); + else + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL SYSCALL error: %s\n"), + SOCK_STRERROR(result_errno, + sebuf, sizeof(sebuf))); + break; + default: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s\n"), + gnutls_strerror(n)); + /* assume the connection is broken */ + result_errno = ECONNRESET; + n = -1; + break; + } + + /* ensure we return the intended errno to caller */ + SOCK_ERRNO_SET(result_errno); + + return n; +} + +bool +pgtls_read_pending(PGconn *conn) +{ + return gnutls_record_check_pending(conn->ssl); +} + +ssize_t +pgtls_write(PGconn *conn, const void *ptr, size_t len) +{ + ssize_t n; + int result_errno; + char sebuf[256]; + + n = gnutls_record_send(conn->ssl, ptr, len); + + if (n >= 0) + { + SOCK_ERRNO_SET(0); + return n; + } + + switch (n) + { + case GNUTLS_E_INTERRUPTED: + result_errno = EINTR; + n = -1; + break; + case GNUTLS_E_AGAIN: + result_errno = EAGAIN; + n = -1; + break; +#ifdef GNUTLS_E_PREMATURE_TERMINATION + case GNUTLS_E_PREMATURE_TERMINATION: +#endif + case GNUTLS_E_PUSH_ERROR: + result_errno = SOCK_ERRNO; + n = -1; + if (result_errno == EPIPE || result_errno == ECONNRESET) + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "server closed the connection unexpectedly\n" + "\tThis probably means the server terminated abnormally\n" + "\tbefore or while processing the request.\n")); + else + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL SYSCALL error: %s\n"), + SOCK_STRERROR(result_errno, + sebuf, sizeof(sebuf))); + break; + default: + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s\n"), + gnutls_strerror(n)); + /* assume the connection is broken */ + result_errno = ECONNRESET; + n = -1; + break; + } + + /* ensure we return the intended errno to caller */ + SOCK_ERRNO_SET(result_errno); + + return n; +} + +char * +pgtls_get_finished(PGconn *conn, size_t *len) +{ + gnutls_datum_t cb; + int ret; + char *result; + + ret = gnutls_session_channel_binding(conn->ssl, GNUTLS_CB_TLS_UNIQUE, &cb); + + if (ret < 0) + return NULL; + + result = malloc(cb.size); + if (result == NULL) + return NULL; + memcpy(result, cb.data, cb.size); + *len = cb.size; + + return result; +} + +/* + * Stub function for SCRAM channel binding type tls-server-end-point, + * currently not supported with GnuTLS. + */ +char * +pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len) +{ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("channel binding type \"tls-server-end-point\" is not supported by this build\n")); + return NULL; +} + +/* ------------------------------------------------------------ */ +/* GnuTLS specific code */ +/* ------------------------------------------------------------ */ + + + +#define MAX_CN 256 + +/* + * 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 namedata[MAX_CN]; + size_t namelen; + int i; + int ret; + int rc = 0; + + /* + * First, get the Subject Alternative Names (SANs) from the certificate, + * and compare them against the originally given hostname. + */ + for (i = 0;; i++) + { + namelen = sizeof(namedata); + ret = gnutls_x509_crt_get_subject_alt_name(conn->peer, i, + namedata, + &namelen, + NULL); + + if (ret < 0) + break; + + if (ret == GNUTLS_SAN_DNSNAME) + { + char *alt_name = NULL; + + (*names_examined)++; + + rc = pq_verify_peer_name_matches_certificate_name(conn, namedata, namelen, &alt_name); + + if (alt_name) + { + if (!*first_name) + *first_name = alt_name; + else + free(alt_name); + } + } + + if (rc != 0) + break; + } + + /* + * If there is no subjectAltName extension of type dNSName, check the + * Common Name. + * + * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type + * dNSName is present, the CN must be ignored.) + */ + if (*names_examined == 0) + { + namelen = sizeof(namedata); + ret = gnutls_x509_crt_get_dn_by_oid(conn->peer, GNUTLS_OID_X520_COMMON_NAME, 0, 0, namedata, &namelen); + + if (ret >= 0) + { + (*names_examined)++; + rc = pq_verify_peer_name_matches_certificate_name(conn, namedata, namelen, first_name); + } + } + + return rc; +} + +/* + * Initialize SSL library. + * + * In threadsafe mode, this includes setting up libcrypto callback functions + * to do thread locking. + */ +int +pgtls_init(PGconn *conn) +{ +#ifdef ENABLE_THREAD_SAFETY +#ifdef WIN32 + /* Also see similar code in fe-connect.c, default_threadlock() */ + if (ssl_config_mutex == NULL) + { + while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1) + /* loop, another thread own the lock */ ; + if (ssl_config_mutex == NULL) + { + if (pthread_mutex_init(&ssl_config_mutex, NULL)) + return -1; + } + InterlockedExchange(&win32_ssl_create_mutex, 0); + } +#endif + if (pthread_mutex_lock(&ssl_config_mutex)) + return -1; +#endif /* ENABLE_THREAD_SAFETY */ + + if (!ssl_lib_initialized) + { + gnutls_global_init(); + ssl_lib_initialized = true; + } + +#ifdef ENABLE_THREAD_SAFETY + pthread_mutex_unlock(&ssl_config_mutex); +#endif + return 0; +} + +/* + * Create per-connection SSL object, and load the client certificate, + * private key, and trusted CA certs. + * + * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage). + */ +static int +initialize_SSL(PGconn *conn) +{ + gnutls_certificate_credentials_t creds; + int ret; + struct stat buf; + char homedir[MAXPGPATH]; + char fnbuf[MAXPGPATH]; + char keybuf[MAXPGPATH]; + char sebuf[256]; + bool have_homedir; + + /* + * We'll need the home directory if any of the relevant parameters are + * defaulted. If pqGetHomeDirectory fails, act as though none of the + * files could be found. + */ + if (!(conn->sslcert && strlen(conn->sslcert) > 0) || + !(conn->sslkey && strlen(conn->sslkey) > 0) || + !(conn->sslrootcert && strlen(conn->sslrootcert) > 0) || + !(conn->sslcrl && strlen(conn->sslcrl) > 0)) + have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir)); + else /* won't need it */ + have_homedir = false; + + ret = gnutls_certificate_allocate_credentials(&creds); + if (ret < 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not create SSL credentials: %s\n"), + gnutls_strerror(ret)); + return -1; + } + + /* + * If the root cert file exists, load it so we can perform certificate + * verification. If sslmode is "verify-full" we will also do further + * verification after the connection has been completed. + */ + if (conn->sslrootcert && strlen(conn->sslrootcert) > 0) + strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf)); + else if (have_homedir) + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE); + else + fnbuf[0] = '\0'; + + if (fnbuf[0] != '\0' && + stat(fnbuf, &buf) == 0) + { + ret = gnutls_certificate_set_x509_trust_file(creds, fnbuf, GNUTLS_X509_FMT_PEM); + if (ret < 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not read root certificate file \"%s\": %s\n"), + fnbuf, gnutls_strerror(ret)); + gnutls_certificate_free_credentials(creds); + return -1; + } + + gnutls_certificate_set_verify_function(creds, verify_cb); + + if (conn->sslcrl && strlen(conn->sslcrl) > 0) + strlcpy(fnbuf, conn->sslcrl, sizeof(fnbuf)); + else if (have_homedir) + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE); + else + fnbuf[0] = '\0'; + + if (fnbuf[0] != '\0' && stat(fnbuf, &buf) == 0) + { + ret = gnutls_certificate_set_x509_crl_file(creds, fnbuf, GNUTLS_X509_FMT_PEM); + if (ret < 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not read crl file \"%s\": %s\n"), + fnbuf, gnutls_strerror(ret)); + gnutls_certificate_free_credentials(creds); + return -1; + } + } + } + else + { + /* + * stat() failed; assume root file doesn't exist. If sslmode is + * verify-ca or verify-full, this is an error. Otherwise, continue + * without performing any server cert verification. + */ + if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */ + { + /* + * The only way to reach here with an empty filename is if + * pqGetHomeDirectory failed. That's a sufficiently unusual case + * that it seems worth having a specialized error message for it. + */ + if (fnbuf[0] == '\0') + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not get home directory to locate root certificate file\n" + "Either provide the file or change sslmode to disable server certificate verification.\n")); + else + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("root certificate file \"%s\" does not exist\n" + "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf); + gnutls_certificate_free_credentials(creds); + return -1; + } + } + + /* Read the client certificate file */ + if (conn->sslcert && strlen(conn->sslcert) > 0) + strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf)); + else if (have_homedir) + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE); + else + fnbuf[0] = '\0'; + + if (fnbuf[0] == '\0') + { + /* no home directory, proceed without a client cert */ + } + else if (stat(fnbuf, &buf) != 0) + { + /* + * If file is not present, just go on without a client cert; server + * might or might not accept the connection. Any other error, + * however, is grounds for complaint. + */ + if (errno != ENOENT && errno != ENOTDIR) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not open certificate file \"%s\": %s\n"), + fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf))); + gnutls_certificate_free_credentials(creds); + return -1; + } + } + else + { + if (conn->sslkey && strlen(conn->sslkey) > 0) + strlcpy(keybuf, conn->sslkey, sizeof(keybuf)); + else if (have_homedir) + snprintf(keybuf, sizeof(keybuf), "%s/%s", homedir, USER_KEY_FILE); + else + keybuf[0] = '\0'; + + if (keybuf[0] != '\0') + { + if (stat(keybuf, &buf) != 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("certificate present, but not private key file \"%s\"\n"), + keybuf); + return -1; + } +#ifndef WIN32 + if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO)) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"), + keybuf); + return -1; + } +#endif + } + + ret = gnutls_certificate_set_x509_key_file(creds, fnbuf, keybuf, GNUTLS_X509_FMT_PEM); + if (ret < 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not read certificate and key files \"%s\" \"%s\": %s\n"), + fnbuf, keybuf, gnutls_strerror(ret)); + gnutls_certificate_free_credentials(creds); + return -1; + } + } + + ret = gnutls_init(&conn->ssl, GNUTLS_CLIENT); + if (ret < 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not establish SSL connection: %s\n"), + gnutls_strerror(ret)); + gnutls_certificate_free_credentials(creds); + return -1; + } + + gnutls_priority_set_direct(conn->ssl, "NORMAL", NULL); + if (ret < 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not establish SSL connection: %s\n"), + gnutls_strerror(ret)); + gnutls_certificate_free_credentials(creds); + return -1; + } + + ret = gnutls_credentials_set(conn->ssl, GNUTLS_CRD_CERTIFICATE, creds); + if (ret < 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not establish SSL connection: %s\n"), + gnutls_strerror(ret)); + gnutls_deinit(conn->ssl); + gnutls_certificate_free_credentials(creds); + return -1; + } + + gnutls_transport_set_ptr(conn->ssl, conn); + gnutls_transport_set_pull_function(conn->ssl, my_sock_read); + gnutls_transport_set_push_function(conn->ssl, my_sock_write); + + conn->ssl_in_use = true; + + return 0; +} + +/* + * Attempt to negotiate SSL connection. + */ +static PostgresPollingStatusType +open_client_SSL(PGconn *conn) +{ + int ret; + + do + { + ret = gnutls_handshake(conn->ssl); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + if (ret < 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("SSL error: %s\n"), + gnutls_strerror(ret)); + pgtls_close(conn); + return PGRES_POLLING_FAILED; + } + + /* + * We already checked the server certificate in gnutls_handshake() using + * verify_cb(), if root.crt exists. + */ + + /* get server certificate */ + ret = get_peer_certificate(conn->ssl, &conn->peer); + if (conn->peer == NULL) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("certificate could not be obtained: %s\n"), + gnutls_strerror(ret)); + pgtls_close(conn); + return PGRES_POLLING_FAILED; + } + + if (!pq_verify_peer_name_matches_certificate(conn)) + { + pgtls_close(conn); + return PGRES_POLLING_FAILED; + } + + /* SSL handshake is complete */ + return PGRES_POLLING_OK; +} + +void +pgtls_close(PGconn *conn) +{ + if (conn->ssl) + { + gnutls_bye(conn->ssl, GNUTLS_SHUT_RDWR); + gnutls_deinit(conn->ssl); + conn->ssl = NULL; + conn->ssl_in_use = false; + } + + if (conn->peer) + { + gnutls_x509_crt_deinit(conn->peer); + conn->peer = NULL; + } +} + +/* ------------------------------------------------------------ */ +/* SSL information functions */ +/* ------------------------------------------------------------ */ + +/* + * Return pointer to OpenSSL object, which is none for GnuTLS. + */ +void * +PQgetssl(PGconn *conn) +{ + return NULL; +} + +void * +PQsslStruct(PGconn *conn, const char *struct_name) +{ + if (!conn) + return NULL; + if (strcmp(struct_name, "GnuTLS") == 0) + return conn->ssl; + return NULL; +} + +const char *const * +PQsslAttributeNames(PGconn *conn) +{ + static const char *const result[] = { + "library", + "key_bits", + "cipher", + "compression", + "protocol", + NULL + }; + + return result; +} + +const char * +PQsslAttribute(PGconn *conn, const char *attribute_name) +{ + if (!conn) + return NULL; + if (conn->ssl == NULL) + return NULL; + + if (strcmp(attribute_name, "library") == 0) + return "GnuTLS"; + + if (strcmp(attribute_name, "key_bits") == 0) + { + static char sslbits_str[10]; + int sslbytes; + + sslbytes = gnutls_cipher_get_key_size(gnutls_cipher_get(conn->ssl)); + + if (sslbytes == 0) + return NULL; + + snprintf(sslbits_str, sizeof(sslbits_str), "%d", sslbytes * 8); + return sslbits_str; + } + + if (strcmp(attribute_name, "cipher") == 0) + return gnutls_cipher_get_name(gnutls_cipher_get(conn->ssl)); + + if (strcmp(attribute_name, "compression") == 0) + { + gnutls_compression_method_t comp = gnutls_compression_get(conn->ssl); + + if (comp == GNUTLS_COMP_NULL || comp == GNUTLS_COMP_UNKNOWN) + return "off"; + else + return "on"; + } + + if (strcmp(attribute_name, "protocol") == 0) + return gnutls_protocol_get_name(gnutls_protocol_get_version(conn->ssl)); + + return NULL; /* unknown attribute */ +} + +/* + * Private substitute transport layer: this does the sending and receiving using + * pqsecure_raw_write() and pqsecure_raw_read() instead, to allow those + * functions to disable SIGPIPE and give better error messages on I/O errors. + */ + +static ssize_t +my_sock_read(gnutls_transport_ptr_t conn, void *buf, size_t size) +{ + return pqsecure_raw_read((PGconn *) conn, buf, size); +} + +static ssize_t +my_sock_write(gnutls_transport_ptr_t conn, const void *buf, size_t size) +{ + return pqsecure_raw_write((PGconn *) conn, buf, size); +} + +#if !HAVE_DECL_GNUTLS_X509_CRT_LIST_SORT +/* + * GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted + * certificate chains, so we skip doing so in these earlier versions. + */ +#define GNUTLS_X509_CRT_LIST_SORT 0 +#endif + +/* + * Get peer certificate from a session + * + * Returns GNUTLS_E_NO_CERTIFICATE_FOUND when not x509 certifcate was found. + */ +static int +get_peer_certificate(gnutls_session_t ssl, gnutls_x509_crt_t *peer) +{ + if (gnutls_certificate_type_get(ssl) == GNUTLS_CRT_X509) + { + unsigned int n; + int ret; + gnutls_datum_t const *raw_certs; + gnutls_x509_crt_t *certs; + + raw_certs = gnutls_certificate_get_peers(ssl, &n); + + if (n == 0) + return GNUTLS_E_NO_CERTIFICATE_FOUND; + + certs = malloc(n * sizeof(gnutls_x509_crt_t)); + if (!certs) + return GNUTLS_E_NO_CERTIFICATE_FOUND; + + ret = gnutls_x509_crt_list_import(certs, &n, raw_certs, + GNUTLS_X509_FMT_DER, + GNUTLS_X509_CRT_LIST_SORT); + + if (ret >= 1) + { + unsigned int i; + + for (i = 1; i < ret; i++) + gnutls_x509_crt_deinit(certs[i]); + + *peer = certs[0]; + + ret = GNUTLS_E_SUCCESS; + } + else if (ret == 0) + ret = GNUTLS_E_NO_CERTIFICATE_FOUND; + + free(certs); + + return ret; + } + + return GNUTLS_E_NO_CERTIFICATE_FOUND; +} + +/* + * Certificate verification callback + * + * This callback is where we verify the identity of the server. + */ +static int +verify_cb(gnutls_session_t ssl) +{ + unsigned int status; + int ret; + + ret = gnutls_certificate_verify_peers2(ssl, &status); + if (ret < 0) + return ret; + + return status; +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index cfb77f6d85..37bb86044a 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -139,7 +139,7 @@ PQsslInUse(PGconn *conn) /* * Exported function to allow application to tell us it's already - * initialized OpenSSL. + * initialized the SSL library. */ void PQinitSSL(int do_init) diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index ed9c806861..68e4053056 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -339,7 +339,7 @@ extern const char *const *PQsslAttributeNames(PGconn *conn); * unencrypted connections or if any other TLS library is in use. */ extern void *PQgetssl(PGconn *conn); -/* Tell libpq whether it needs to initialize OpenSSL */ +/* Tell libpq whether it needs to initialize the SSL library */ extern void PQinitSSL(int do_init); /* More detailed way to tell libpq whether it needs to initialize OpenSSL */ diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index eba23dcecc..16b7676703 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -71,14 +71,15 @@ typedef struct #endif #endif /* ENABLE_SSPI */ -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) #include #include - #ifndef OPENSSL_NO_ENGINE #define USE_SSL_ENGINE #endif -#endif /* USE_OPENSSL */ +#elif defined(USE_GNUTLS) +#include +#endif /* * POSTGRES backend dependent Constants. @@ -461,7 +462,7 @@ struct pg_conn bool allow_ssl_try; /* Allowed to try SSL negotiation */ bool wait_ssl_try; /* Delay SSL negotiation until after * attempting normal connection */ -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) SSL *ssl; /* SSL status, if have SSL connection */ X509 *peer; /* X509 cert of server */ #ifdef USE_SSL_ENGINE @@ -470,7 +471,10 @@ struct pg_conn void *engine; /* dummy field to keep struct the same if * OpenSSL version changes */ #endif -#endif /* USE_OPENSSL */ +#elif defined(USE_GNUTLS) + gnutls_session_t ssl; /* SSL status, if have SSL connection */ + gnutls_x509_crt_t peer; /* X509 cert of server */ +#endif #endif /* USE_SSL */ #ifdef ENABLE_GSS diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c index bc7a8aacb9..73c02251ad 100644 --- a/src/port/pg_strong_random.c +++ b/src/port/pg_strong_random.c @@ -24,8 +24,11 @@ #include #include -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) #include +#elif defined(USE_GNUTLS) +#include +#include #endif #ifdef WIN32 #include @@ -85,8 +88,9 @@ random_from_file(char *filename, void *buf, size_t len) * We support a number of sources: * * 1. OpenSSL's RAND_bytes() - * 2. Windows' CryptGenRandom() function - * 3. /dev/urandom + * 2. GnuTLS's gnutls_rnd() + * 3. Windows' CryptGenRandom() function + * 4. /dev/urandom * * The configure script will choose which one to use, and set * a USE_*_RANDOM flag accordingly. @@ -107,6 +111,14 @@ pg_strong_random(void *buf, size_t len) return true; return false; + /* + * When built with GnuTLS, use GnuTLS's gnutls_rnd function. + */ +#elif defined(USE_GNUTLS_RANDOM) + if (gnutls_rnd(GNUTLS_RND_RANDOM, buf, len) == 0) + return true; + return false; + /* * Windows has CryptoAPI for strong cryptographic numbers. */ diff --git a/src/test/Makefile b/src/test/Makefile index efb206aa75..a6d41c7e46 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 ($(filter yes,$(with_openssl) $(with_gnutls)),yes) ifneq (,$(filter ssl,$(PG_TEST_EXTRA))) SUBDIRS += ssl endif diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile index 5cd2c5a404..5b166dc433 100644 --- a/src/test/ssl/Makefile +++ b/src/test/ssl/Makefile @@ -13,7 +13,7 @@ subdir = src/test/ssl top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -export with_openssl +export with_openssl with_gnutls CERTIFICATES := server_ca server-cn-and-alt-names \ server-cn-only server-single-alt-name server-multiple-alt-names \ diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl index 34df5e9dbb..1169b5e608 100644 --- a/src/test/ssl/t/001_ssltests.pl +++ b/src/test/ssl/t/001_ssltests.pl @@ -6,15 +6,13 @@ use ServerSetup; use File::Copy; -if ($ENV{with_openssl} eq 'yes') -{ - plan tests => 62; -} -else +if ($ENV{with_openssl} ne 'yes' && $ENV{with_gnutls} ne 'yes') { plan skip_all => 'SSL not supported by this build'; } +my $number_of_tests = 61; + #### Some configuration # This is the hostname used to connect to the server. This cannot be a @@ -49,6 +47,15 @@ $ENV{PGHOST} = $node->host; $ENV{PGPORT} = $node->port; $node->start; + +# Run this before we lock down access below. +my $result = $node->safe_psql('postgres', "SHOW ssl_library"); +my $expected; +if ($ENV{'with_openssl'} eq 'yes') { $expected = 'OpenSSL'; } +elsif ($ENV{'with_gnutls'} eq 'yes') { $expected = 'GnuTLS'; } +else { $expected = ''; } +is($result, $expected, 'ssl_library parameter'); + configure_test_server_for_ssl($node, $SERVERHOSTADDR, 'trust'); switch_server_cert($node, 'server-cn-only'); @@ -94,12 +101,22 @@ 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"); +# Try with just the server CA's cert. This fails with OpenSSL because +# the root file must contain the whole chain up to the root CA. +if ($ENV{'with_openssl'} eq 'yes') +{ + test_connect_fails($common_connstr, + "sslrootcert=ssl/server_ca.crt sslmode=verify-ca", + qr/SSL error/, + "connect with server CA cert, without root CA"); + $number_of_tests++; +} +else +{ + test_connect_ok($common_connstr, + "sslrootcert=ssl/server_ca.crt sslmode=verify-ca", + "connect with server CA cert, without root CA"); +} # And finally, with the correct root cert. test_connect_ok($common_connstr, @@ -128,11 +145,21 @@ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid", "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"); +if ($ENV{'with_openssl'} eq 'yes') +{ + # 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"); + $number_of_tests++; +} +else +{ + test_connect_ok($common_connstr, + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl", + "CRL belonging to a different CA"); +} # With the correct CRL, succeeds (this cert is not revoked) test_connect_ok($common_connstr, @@ -282,10 +309,14 @@ "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/, + ($ENV{'with_openssl'} eq 'yes' ? + qr/SSL error/ : + qr/connection requires a valid client certificate/), "intermediate client certificate is missing"); # clean up unlink("ssl/client_tmp.key", "ssl/client_wrongperms_tmp.key", "ssl/client-revoked_tmp.key"); + +done_testing($number_of_tests); diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl index a805a3196b..89d770a729 100644 --- a/src/test/ssl/t/002_scram.pl +++ b/src/test/ssl/t/002_scram.pl @@ -8,7 +8,7 @@ use ServerSetup; use File::Copy; -if ($ENV{with_openssl} ne 'yes') +if ($ENV{with_openssl} ne 'yes' && $ENV{with_gnutls} ne 'yes') { plan skip_all => 'SSL not supported by this build'; } diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 72976f44d8..29d768c7e7 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -119,6 +119,10 @@ sub mkvcbuild { push(@pgcommonallfiles, 'sha2_openssl.c'); } + elsif ($solution->{options}->{gnutls}) + { + push(@pgcommonallfiles, 'sha2_gnutls.c'); + } else { push(@pgcommonallfiles, 'sha2.c'); @@ -246,6 +250,12 @@ sub mkvcbuild $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); $libpq->RemoveFile('src/common/sha2_openssl.c'); } + elsif (!$solution->{options}->{gnutls}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c'); + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gnutls.c'); + $libpq->RemoveFile('src/common/sha2_gnutls.c'); + } else { $libpq->RemoveFile('src/common/sha2.c'); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index d4765ce3b0..ba2cf56e0c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2729,6 +2729,9 @@ ginxlogVacuumDataLeafPage gistxlogPage gistxlogPageSplit gistxlogPageUpdate +gnutls_datum_t +gnutls_dh_params_t +gnutls_x509_crt_t grouping_sets_data gseg_picksplit_item gss_OID base-commit: 04e7ecadf64d18f67ca1d0632d8ab71f120ca5e3 -- 2.16.2