On 11/20/2017 02:56 AM, Michael Paquier wrote:
On Mon, Nov 20, 2017 at 9:42 AM, Tomas Vondra
<tomas.von...@2ndquadrant.com> wrote:
If I get it right we ignore gnutls and use openssl (as it's the first
checked in #ifdefs). Shouldn't we enforce in configure that only one TLS
implementation is enabled? Either by some elaborate check, or by
switching to something like
--with-ssl=(openssl|gnutls)
WIth potential patches coming to use macos' SSL implementation or
Windows channel, there should really be only one implementation
available at compile time. That's more simple as a first step as well.
So +1 for the --with-ssl switch.
I have now implemented this in the attached patch (plus added support
for channel binding and rebased it) but I ran into one issue which I
have not yet solved. The script for the windows version takes the
--with-openssl=<path> switch so that cannot just be translated to a
single --with-ssl switch. Should to have both --with-openssl and
--with-gnutls or --with-ssl=(openssl|gnutls) and --with-ssl-path=<path>?
I also do not know the Windows build code very well (or really at all).
Andreas
diff --git a/configure b/configure
index 6c4d743b35..76470af626 100755
--- a/configure
+++ b/configure
@@ -707,7 +707,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
-with_openssl
+with_ssl
krb_srvtab
with_python
with_perl
@@ -834,6 +834,7 @@ with_pam
with_bsd_auth
with_ldap
with_bonjour
+with_ssl
with_openssl
with_selinux
with_systemd
@@ -1528,7 +1529,8 @@ Optional Packages:
--with-bsd-auth build with BSD Authentication support
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
- --with-openssl build with OpenSSL support
+ --with-ssl=LIB build with TLS support from LIB (openssl,gnutls)
+ --with-openssl obsolete spelling of --with-ssl=openssl
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -5961,10 +5963,34 @@ $as_echo "$with_bonjour" >&6; }
#
-# OpenSSL
+# TLS library
#
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with OpenSSL support" >&5
-$as_echo_n "checking whether to build with OpenSSL support... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking which SSL library to build with" >&5
+$as_echo_n "checking which SSL library to build with... " >&6; }
+
+
+
+# Check whether --with-ssl was given.
+if test "${with_ssl+set}" = set; then :
+ withval=$with_ssl;
+ case $withval in
+ yes)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ no)
+ as_fn_error $? "argument required for --with-ssl option" "$LINENO" 5
+ ;;
+ *)
+
+ ;;
+ esac
+
+fi
+
+
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
@@ -5973,9 +5999,7 @@ if test "${with_openssl+set}" = set; then :
withval=$with_openssl;
case $withval in
yes)
-
-$as_echo "#define USE_OPENSSL 1" >>confdefs.h
-
+ :
;;
no)
:
@@ -5991,8 +6015,23 @@ else
fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_openssl" >&5
-$as_echo "$with_openssl" >&6; }
+if test "$with_ssl" = openssl ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
+
+$as_echo "#define USE_OPENSSL 1" >>confdefs.h
+
+elif test "$with_ssl" = gnutls ; then
+
+$as_echo "#define USE_GNUTLS 1" >>confdefs.h
+
+elif test "$with_ssl" != no ; then
+ as_fn_error $? "--with-ssl must specify one of openssl or gnutls" "$LINENO" 5
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_ssl" >&5
+$as_echo "$with_ssl" >&6; }
#
@@ -9911,7 +9950,7 @@ fi
fi
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
if test "$PORTNAME" != "win32"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for CRYPTO_new_ex_data in -lcrypto" >&5
$as_echo_n "checking for CRYPTO_new_ex_data in -lcrypto... " >&6; }
@@ -10169,6 +10208,94 @@ done
fi
+if test "$with_ssl" = gnutls ; 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_X509_CRT_LIST_SORT" "ac_cv_have_decl_GNUTLS_X509_CRT_LIST_SORT" "#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+"
+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; }
@@ -10949,7 +11076,7 @@ done
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
@@ -10966,6 +11093,17 @@ else
fi
+fi
+
+if test "$with_ssl" = gnutls ; 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 <gnutls/gnutls.h> is required for GnuTLS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -15623,9 +15761,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 x"$with_openssl" = x"yes" ; 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_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
+ elif test x"$with_ssl" = x"gnutls" ; then
+ USE_GNUTLS_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
else
@@ -15664,6 +15804,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 d9c4a50b4b..9fe0ff9464 100644
--- a/configure.in
+++ b/configure.in
@@ -695,13 +695,27 @@ AC_MSG_RESULT([$with_bonjour])
#
-# OpenSSL
+# TLS library
#
-AC_MSG_CHECKING([whether to build with OpenSSL support])
-PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
- [AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-openssl)])])
-AC_MSG_RESULT([$with_openssl])
-AC_SUBST(with_openssl)
+AC_MSG_CHECKING([which SSL library to build with])
+PGAC_ARG_REQ(with, ssl, [LIB], [build with TLS support from LIB (openssl,gnutls)])
+if test x"$with_ssl" = x"" ; then
+ with_ssl=no
+fi
+PGAC_ARG_BOOL(with, openssl, no, [obsolete spelling of --with-ssl=openssl])
+if test "$with_ssl" = openssl ; then
+ with_ssl=openssl
+fi
+
+if test "$with_ssl" = openssl ; then
+ AC_DEFINE([USE_OPENSSL], 1, [Define to build with OpenSSL support. (--with-ssl=openssl)])
+elif test "$with_ssl" = gnutls ; then
+ AC_DEFINE([USE_GNUTLS], 1, [Define to build with GnuTLS support. (--with-ssl=gnutls)])
+elif test "$with_ssl" != no ; then
+ AC_MSG_ERROR([--with-ssl must specify one of openssl or gnutls])
+fi
+AC_MSG_RESULT([$with_ssl])
+AC_SUBST(with_ssl)
#
# SELinux
@@ -1055,7 +1069,7 @@ if test "$with_gssapi" = yes ; then
fi
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
dnl Order matters!
if test "$PORTNAME" != "win32"; then
AC_CHECK_LIB(crypto, CRYPTO_new_ex_data, [], [AC_MSG_ERROR([library 'crypto' is required for OpenSSL])])
@@ -1077,6 +1091,17 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_ssl" = gnutls ; 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_X509_CRT_LIST_SORT, [], [],
+[#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+])
+ 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
@@ -1219,11 +1244,15 @@ if test "$with_gssapi" = yes ; then
[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
fi
-if test "$with_openssl" = yes ; then
+if test "$with_ssl" = openssl ; then
AC_CHECK_HEADER(openssl/ssl.h, [], [AC_MSG_ERROR([header file <openssl/ssl.h> is required for OpenSSL])])
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
fi
+if test "$with_ssl" = gnutls ; then
+ AC_CHECK_HEADER(gnutls/gnutls.h, [], [AC_MSG_ERROR([header file <gnutls/gnutls.h> 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, [],
@@ -1989,9 +2018,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 x"$with_openssl" = x"yes" ; 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_ssl" = x"openssl" ; then
USE_OPENSSL_RANDOM=1
+ elif test x"$with_ssl" = x"gnutls" ; then
+ USE_GNUTLS_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
else
@@ -2008,6 +2039,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/contrib/Makefile b/contrib/Makefile
index 8046ca4f39..b256aa2c0c 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -50,7 +50,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),openssl)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 573bc6df79..97b4c98df8 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,17 +1,17 @@
# contrib/pgcrypto/Makefile
-INT_SRCS = md5.c sha1.c internal.c internal-sha2.c blf.c rijndael.c \
- pgp-mpi-internal.c imath.c
-INT_TESTS = sha2
-
-OSSL_SRCS = openssl.c pgp-mpi-openssl.c
-OSSL_TESTS = sha2 des 3des cast5
-
ZLIB_TST = pgp-compression
ZLIB_OFF_TST = pgp-zlib-DISABLED
-CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
-CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
+ifeq ($(with_ssl),openssl)
+CF_SRCS = openssl.c pgp-mpi-openssl.c
+CF_TESTS = sha2 des 3des cast5
+else
+CF_SRCS = md5.c sha1.c internal.c internal-sha2.c blf.c rijndael.c \
+ pgp-mpi-internal.c imath.c
+CF_TESTS = sha2
+endif
+
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
SRCS = pgcrypto.c px.c px-hmac.c px-crypt.c \
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index a922819fce..76c9b83e9d 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -790,20 +790,39 @@ su - postgres
</varlistentry>
<varlistentry>
- <term><option>--with-openssl</option>
+ <term><option>--with-ssl=<replaceable>LIBRARY</replaceable></option>
<indexterm>
- <primary>OpenSSL</primary>
- <seealso>SSL</seealso>
+ <primary>SSL</primary>
+ <seealso>OpenSSL</seealso>
+ <seealso>GnuTLS</seealso>
</indexterm>
</term>
<listitem>
<para>
Build with support for <acronym>SSL</acronym> (encrypted)
- connections. This requires the <productname>OpenSSL</productname>
- package to be installed. <filename>configure</filename> will check
- for the required header files and libraries to make sure that
- your <productname>OpenSSL</productname> installation is sufficient
- before proceeding.
+ connections using the specified library.
+ <replaceable>LIBRARY</replaceable> must be one of:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>openssl</option> to use <productname>OpenSSL</productname>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>gnutls</option> to use <productname>GnuTLS</productname>.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--with-openssl</option></term>
+ <listitem>
+ <para>
+ Obsolete equivalent of <literal>--with-ssl=openssl</literal>.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 25341d684c..74f7b7228c 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -1146,7 +1146,7 @@ gen_random_uuid() returns uuid
<filename>pgcrypto</filename> configures itself according to the findings of the
main PostgreSQL <literal>configure</literal> script. The options that
affect it are <literal>--with-zlib</literal> and
- <literal>--with-openssl</literal>.
+ <literal>--with-ssl</literal>.
</para>
<para>
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index cda09aaafd..60205cac80 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -16,7 +16,7 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with <literal>--with-ssl</literal>.
</para>
<sect2>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index d980f81046..8f272bdf36 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,7 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
-with_openssl = @with_openssl@
+with_ssl = @with_ssl@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_libxml = @with_libxml@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 7fa2b02743..6c1720f9ad 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -17,8 +17,10 @@ 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_ssl),openssl)
OBJS += be-secure-openssl.o
+else ifeq ($(with_ssl),gnutls)
+OBJS += be-secure-gnutls.o
endif
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-gnutls.c b/src/backend/libpq/be-secure-gnutls.c
new file mode 100644
index 0000000000..4c55e9985f
--- /dev/null
+++ b/src/backend/libpq/be-secure-gnutls.c
@@ -0,0 +1,946 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+ *
+ * Since the server static private key ($DataDir/server.key)
+ * will normally be stored unencrypted so that the database
+ * backend can restart automatically, it is important that
+ * we select an algorithm that continues to provide confidentiality
+ * even if the attacker has the server's private key. Ephemeral
+ * DH (EDH) keys provide this and more (Perfect Forward Secrecy
+ * aka PFS).
+ *
+ * N.B., the static private key should still be protected to
+ * the largest extent possible, to minimize the risk of
+ * impersonations.
+ *
+ * Another benefit of EDH is that it allows the backend and
+ * clients to use DSA keys. DSA keys can only provide digital
+ * signatures, not encryption, and are often acceptable in
+ * jurisdictions where RSA keys are unacceptable.
+ *
+ * The downside to EDH is that it makes it impossible to
+ * use ssldump(1) if there's a problem establishing an SSL
+ * session. In this case you'll need to temporarily disable
+ * EDH by commenting out the callback.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+#include <gnutls/x509.h>
+#include <gnutls/pkcs11.h>
+
+#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);
+#if HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION
+static int ssl_passwd_cb(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 ssl_passwd_cb_called = false;
+
+/* ------------------------------------------------------------ */
+/* Hardcoded values */
+/* ------------------------------------------------------------ */
+
+/*
+ * Hardcoded DH parameters, used in ephemeral DH keying.
+ * As discussed above, EDH protects the confidentiality of
+ * sessions even if the static private key is compromised,
+ * so we are *highly* motivated to ensure that we can use
+ * EDH even if the DBA has not provided custom DH parameters.
+ *
+ * We could refuse SSL connections unless a good DH parameter
+ * file exists, but some clients may quietly renegotiate an
+ * unsecured connection without fully informing the user.
+ *
+ * Very uncool. Alternatively, the system could refuse to start
+ * if a DH parameters is not specified, but this would tend to
+ * piss off DBAs.
+ *
+ * Alternatively, the backend could attempt to load these files
+ * on startup if SSL is enabled - and refuse to start if any
+ * do not exist - but this would tend to piss off DBAs.
+ *
+ * If you want to create your own hardcoded DH parameters
+ * for fun and profit, review "Assigned Number for SKIP
+ * Protocols" (http://www.skip-vpn.org/spec/numbers.html)
+ * for suggestions.
+ */
+
+static const char file_dh2048[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MIIBCAKCAQEA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV\n\
+89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50\n\
+T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknb\n\
+zSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdX\n\
+Q6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbT\n\
+CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
+-----END DH PARAMETERS-----\n";
+
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * Initialize global SSL credentials.
+ *
+ * If isServerStart is true, report any errors as FATAL (so we don't return).
+ * Otherwise, log errors at LOG level and return -1 to indicate trouble,
+ * preserving the old SSL state if any. Returns 0 if OK.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ gnutls_certificate_credentials_t credentials = NULL;
+ gnutls_priority_t priority = NULL;
+ gnutls_dh_params_t dh_params = NULL;
+ struct stat buf;
+ 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;
+ }
+
+ /*
+ * If reloading, override OpenSSL's default handling of
+ * passphrase-protected files, because we don't want to prompt for a
+ * passphrase in an already-running server. (Not that the default
+ * handling is very desirable during server start either, but some people
+ * insist we need to keep it.)
+ *
+ * We set the callback globally for compatibility with GnuTLS < 3.1.0.
+ */
+#if HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION
+ if (!isServerStart)
+ gnutls_pkcs11_set_pin_function(ssl_passwd_cb, NULL);
+#endif
+
+ /*
+ * Load and verify server's certificate and private key
+ */
+ if (stat(ssl_key_file, &buf) != 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode_for_file_access(),
+ errmsg("could not access private key file \"%s\": %m",
+ ssl_key_file)));
+ goto error;
+ }
+
+ if (!S_ISREG(buf.st_mode))
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" is not a regular file",
+ ssl_key_file)));
+ goto error;
+ }
+
+ /*
+ * Refuse to load key files owned by users other than us or root.
+ *
+ * XXX surely we can check this on Windows somehow, too.
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ if (buf.st_uid != geteuid() && buf.st_uid != 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" must be owned by the database user or root",
+ ssl_key_file)));
+ goto error;
+ }
+#endif
+
+ /*
+ * Require no public access to key file. If the file is owned by us,
+ * require mode 0600 or less. If owned by root, require 0640 or less to
+ * allow read access through our gid, or a supplementary gid that allows
+ * to read system-wide certificates.
+ *
+ * XXX temporarily suppress check when on Windows, because there may not
+ * be proper support for Unix-y file permissions. Need to think of a
+ * reasonable check to apply on Windows. (See also the data directory
+ * permission check in postmaster.c)
+ */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+ if ((buf.st_uid == geteuid() && buf.st_mode & (S_IRWXG | S_IRWXO)) ||
+ (buf.st_uid == 0 && buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)))
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" has group or world access",
+ ssl_key_file),
+ errdetail("File must have permissions u=rw (0600) or less if owned by the database user, or permissions u=rw,g=r (0640) or less if owned by root.")));
+ goto error;
+ }
+#endif
+
+ /*
+ * OK, try to load the private key file.
+ */
+ ssl_passwd_cb_called = false;
+
+ ret = gnutls_certificate_set_x509_key_file(credentials, ssl_cert_file, ssl_key_file, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ if (ssl_passwd_cb_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).
+ * http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,00.html
+ *----------
+ */
+ 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;
+}
+
+/*
+ * Destroy global SSL credentials, if any.
+ */
+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;
+}
+
+/*
+ * Attempt to negotiate SSL connection.
+ */
+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;
+ }
+
+ ereport(DEBUG2,
+ (errmsg("SSL connection from \"%s\"",
+ port->peer_cn ? port->peer_cn : "(anonymous)")));
+
+ return 0;
+}
+
+/*
+ * Close SSL connection.
+ */
+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;
+ }
+}
+
+/*
+ * Read data from a secure connection.
+ */
+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;
+}
+
+/*
+ * Write data to a secure connection.
+ */
+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;
+}
+
+#if HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION
+/*
+ * Passphrase collection callback
+ *
+ * If GnuTLS is told to use a passphrase-protected server key, by default
+ * it will issue a prompt on /dev/tty and try to read a key from there.
+ * That's 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
+ssl_passwd_cb(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 */
+ ssl_passwd_cb_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;
+}
+
+/*
+ * Return information about the SSL connection
+ */
+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;
+}
+
+void
+be_tls_get_version(Port *port, char *ptr, size_t len)
+{
+ if (port->ssl)
+ strlcpy(ptr, gnutls_protocol_get_name(gnutls_protocol_get_version(port->ssl)), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_cipher(Port *port, char *ptr, size_t len)
+{
+ if (port->ssl)
+ strlcpy(ptr, gnutls_cipher_get_name(gnutls_cipher_get(port->ssl)), len);
+ else
+ ptr[0] = '\0';
+}
+
+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';
+}
+
+/*
+ * Routine to get the expected TLS Finished message information from the
+ * client, useful for authorization when doing channel binding.
+ *
+ * Result is a palloc'd copy of the TLS Finished message with its size.
+ */
+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;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 53fefd1b29..303fa783d3 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -59,6 +59,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 ca78a7e0ba..c55077ad45 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-ssl to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6dcd738be6..4effec7768 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3614,6 +3614,21 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"gnutls_priority", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ gettext_noop("Sets the list of allowed GnuTLS algorithms."),
+ NULL,
+ GUC_SUPERUSER_ONLY
+ },
+ &gnutls_priority,
+#ifdef USE_SSL
+ "NORMAL:%SERVER_PRECEDENCE",
+#else
+ "none",
+#endif
+ NULL, NULL, NULL
+ },
+
{
{"ssl_dh_params_file", PGC_SIGHUP, CONN_AUTH_SECURITY,
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 16ffbbeea8..2b876a2544 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -77,14 +77,22 @@
#authentication_timeout = 1min # 1s-600s
#ssl = off
-#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
-#ssl_prefer_server_ciphers = on
-#ssl_ecdh_curve = 'prime256v1'
#ssl_dh_params_file = ''
#ssl_cert_file = 'server.crt'
#ssl_key_file = 'server.key'
#ssl_ca_file = ''
#ssl_crl_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'
+
+# Parameters for GnuTLS. Leave these commented out if not using GnuTLS.
+
+#gnutls_priority = 'NORMAL:%SERVER_PRECEDENCE'
+
#password_encryption = md5 # md5 or scram-sha-256
#db_user_namespace = off
#row_security = on
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..af961a7193 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -45,8 +45,10 @@ 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_ssl),openssl)
OBJS_COMMON += sha2_openssl.o
+else ifeq ($(with_ssl),gnutls)
+OBJS_COMMON += sha2_gnutls.o
else
OBJS_COMMON += sha2.o
endif
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 a31b3979d8..0c311dea2f 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
+#ifdef USE_OPENSSL
#include <openssl/sha.h>
+#elif defined(USE_GNUTLS)
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
#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
+#ifdef 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 856e0439d5..a51d3b5138 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,8 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif defined(USE_GNUTLS)
+#include <gnutls/gnutls.h>
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -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
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 fd2dd5853c..3a8552f124 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -99,5 +99,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 84d59f12b2..5667b7c972 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -134,6 +134,10 @@
don't. */
#undef HAVE_DECL_F_FULLFSYNC
+/* 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 +260,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 <gssapi/gssapi.h> header file. */
#undef HAVE_GSSAPI_GSSAPI_H
@@ -825,6 +832,12 @@
(--enable-float8-byval) */
#undef USE_FLOAT8_BYVAL
+/* Define to build with GnuTLS support. (--with-ssl=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
@@ -841,7 +854,7 @@
/* Define to select named POSIX semaphores. */
#undef USE_NAMED_POSIX_SEMAPHORES
-/* Define to build with OpenSSL support. (--with-openssl) */
+/* Define to build with OpenSSL support. (--with-ssl=openssl) */
#undef USE_OPENSSL
/* Define to use OpenSSL for random number generation */
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 6f2238b330..8e4be41c60 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..656a10b277 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -28,6 +28,7 @@
/scram-common.c
/sha2.c
/sha2_openssl.c
+/sha2_gnutls.c
/saslprep.c
/unicode_norm.c
/encnames.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 94eb84be03..94bb9db98f 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -51,8 +51,10 @@ 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_ssl),openssl)
OBJS += fe-secure-openssl.o sha2_openssl.o
+else ifeq ($(with_ssl),gnutls)
+OBJS += fe-secure-gnutls.o sha2_gnutls.o
else
OBJS += sha2.o
endif
@@ -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..c5352a1e65
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gnutls.c
@@ -0,0 +1,1056 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-gnutls.c
+ * OpenSSL 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
+ *
+ * NOTES
+ *
+ * We don't provide informational callbacks here (like
+ * info_cb() in be-secure.c), since there's no good mechanism to
+ * display such information to the user.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+#include "libpq-int.h"
+
+#include <sys/stat.h>
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+#include "pthread-win32.h"
+#else
+#include <pthread.h>
+#endif
+#endif
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+static bool verify_peer_name_matches_certificate(PGconn *);
+static int verify_peer_name_matches_certificate_name(PGconn *conn,
+ size_t namelen,
+ char *namedata,
+ char **store_name);
+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 pq_init_ssl_lib = true;
+
+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 */
+/* ------------------------------------------------------------ */
+
+/*
+ * Exported function to allow application to tell us it's already
+ * initialized GnuTLS.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ pq_init_ssl_lib = do_ssl;
+}
+
+/*
+ * Begin or continue negotiating a secure session.
+ */
+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);
+}
+
+/*
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return gnutls_record_check_pending(conn->ssl);
+}
+
+/*
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message
+ * into conn->errorMessage. The caller must still inspect errno, but only
+ * to determine whether to continue/retry after error.
+ */
+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;
+}
+
+/*
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message
+ * into conn->errorMessage. The caller must still inspect errno, but only
+ * to determine whether to continue/retry after error.
+ */
+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;
+}
+
+/*
+ * Get the TLS finish message sent during last handshake
+ *
+ * This information is useful for callers doing channel binding during
+ * authentication.
+ */
+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;
+}
+
+/* ------------------------------------------------------------ */
+/* GnuTLS specific code */
+/* ------------------------------------------------------------ */
+
+/*
+ * Check if a wildcard certificate matches the server hostname.
+ *
+ * The rule for this is:
+ * 1. We only match the '*' character as wildcard
+ * 2. We match only wildcards at the start of the string
+ * 3. The '*' character does *not* match '.', meaning that we match only
+ * a single pathname component.
+ * 4. We don't support more than one '*' in a single pattern.
+ *
+ * This is roughly in line with RFC2818, but contrary to what most browsers
+ * appear to be implementing (point 3 being the difference)
+ *
+ * Matching is always case-insensitive, since DNS is case insensitive.
+ */
+static int
+wildcard_certificate_match(const char *pattern, const char *string)
+{
+ int lenpat = strlen(pattern);
+ int lenstr = strlen(string);
+
+ /* If we don't start with a wildcard, it's not a match (rule 1 & 2) */
+ if (lenpat < 3 ||
+ pattern[0] != '*' ||
+ pattern[1] != '.')
+ return 0;
+
+ if (lenpat > lenstr)
+ /* If pattern is longer than the string, we can never match */
+ return 0;
+
+ if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)
+
+ /*
+ * If string does not end in pattern (minus the wildcard), we don't
+ * match
+ */
+ return 0;
+
+ if (strchr(string, '.') < string + lenstr - lenpat)
+
+ /*
+ * If there is a dot left of where the pattern started to match, we
+ * don't match (rule 3)
+ */
+ return 0;
+
+ /* String ended with pattern, and didn't have a dot before, so we match */
+ return 1;
+}
+
+/*
+ * Check if a name from a server's certificate matches the peer's hostname.
+ *
+ * Returns 1 if the name matches, and 0 if it does not. On error, returns
+ * -1, and sets the libpq error message.
+ *
+ * The name extracted from the certificate is returned in *store_name. The
+ * caller is responsible for freeing it.
+ */
+static int
+verify_peer_name_matches_certificate_name(PGconn *conn, size_t len,
+ char *namedata, char **store_name)
+{
+ char *name;
+ int result;
+ char *host = PQhost(conn);
+
+ name = malloc(len + 1);
+ if (name == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return -1;
+ }
+ memcpy(name, namedata, len);
+ name[len] = '\0';
+
+ /*
+ * Reject embedded NULLs in certificate common or alternative name to
+ * prevent attacks like CVE-2009-4034.
+ */
+ if (len != strlen(name))
+ {
+ free(name);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL certificate's name contains embedded null\n"));
+ return -1;
+ }
+
+ if (pg_strcasecmp(name, host) == 0)
+ {
+ /* Exact name match */
+ result = 1;
+ }
+ else if (wildcard_certificate_match(name, host))
+ {
+ /* Matched wildcard name */
+ result = 1;
+ }
+ else
+ {
+ result = 0;
+ }
+
+ *store_name = name;
+ return result;
+}
+
+#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.
+ */
+static bool
+verify_peer_name_matches_certificate(PGconn *conn)
+{
+ char *host = PQhost(conn);
+ char namedata[MAX_CN];
+ size_t namelen;
+ int i;
+ int ret;
+ int rc;
+ char *first_name = NULL;
+ int names_examined = 0;
+ bool found_match = false;
+ bool got_error = false;
+
+ /*
+ * If told not to verify the peer name, don't do it. Return true
+ * indicating that the verification was successful.
+ */
+ if (strcmp(conn->sslmode, "verify-full") != 0)
+ return true;
+
+ /* Check that we have a hostname to compare with. */
+ if (!(host && host[0] != '\0'))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("host name must be specified for a verified SSL connection\n"));
+ return false;
+ }
+
+ /*
+ * 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 = verify_peer_name_matches_certificate_name(conn, namelen, namedata, &alt_name);
+
+ if (rc == -1)
+ got_error = true;
+ if (rc == 1)
+ found_match = true;
+
+ if (alt_name)
+ {
+ if (!first_name)
+ first_name = alt_name;
+ else
+ free(alt_name);
+ }
+ }
+
+ if (found_match || got_error)
+ 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)
+ {
+ rc = verify_peer_name_matches_certificate_name(conn, namelen, namedata, &first_name);
+
+ if (rc == -1)
+ got_error = true;
+ if (rc == 1)
+ found_match = true;
+ }
+ }
+
+ if (!found_match && !got_error)
+ {
+ /*
+ * No match. Include the name from the server certificate in the error
+ * message, to aid debugging broken configurations. If there are
+ * multiple names, only print the first one to avoid an overly long
+ * error message.
+ */
+ if (names_examined > 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_ngettext("server certificate for \"%s\" (and %d other name) does not match host name \"%s\"\n",
+ "server certificate for \"%s\" (and %d other names) does not match host name \"%s\"\n",
+ names_examined - 1),
+ first_name, names_examined - 1, host);
+ }
+ else if (names_examined == 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("server certificate for \"%s\" does not match host name \"%s\"\n"),
+ first_name, host);
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get server's host name from server certificate\n"));
+ }
+ }
+
+ /* clean up */
+ if (first_name)
+ free(first_name);
+
+ return found_match && !got_error;
+}
+
+/*
+ * Initialize SSL library.
+ *
+ * In threadsafe mode, this includes setting up libcrypto callback functions
+ * to do thread locking.
+ *
+ * If the caller has told us (through PQinitOpenSSL) that he's taking care
+ * of libcrypto, we expect that callbacks are already set, and won't try to
+ * override it.
+ *
+ * The conn parameter is only used to be able to pass back an error
+ * message - no connection-local setup is made here.
+ *
+ * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
+ */
+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)
+ {
+ if (pq_init_ssl_lib)
+ {
+ 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 (!verify_peer_name_matches_certificate(conn))
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* SSL handshake is complete */
+ return PGRES_POLLING_OK;
+}
+
+/*
+ * Close SSL connection.
+ */
+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 */
+/* ------------------------------------------------------------ */
+
+int
+PQsslInUse(PGconn *conn)
+{
+ if (!conn)
+ return 0;
+ return conn->ssl_in_use;
+}
+
+/*
+ * 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/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 8412ee8160..9e53d9239a 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -78,7 +78,9 @@ typedef struct
#ifndef OPENSSL_NO_ENGINE
#define USE_SSL_ENGINE
#endif
-#endif /* USE_OPENSSL */
+#elif defined(USE_GNUTLS)
+#include <gnutls/gnutls.h>
+#endif
/*
* POSTGRES backend dependent Constants.
@@ -469,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 c6ee5ea1d4..0591b3d812 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -27,6 +27,10 @@
#ifdef USE_OPENSSL
#include <openssl/rand.h>
#endif
+#ifdef USE_GNUTLS
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#endif
#ifdef WIN32
#include <wincrypt.h>
#endif
@@ -85,8 +89,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 +112,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/ssl/Makefile b/src/test/ssl/Makefile
index e4437d19c3..7035d7eca9 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,6 +13,10 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
+ifneq ($(with_ssl),no)
+export WITH_SSL=$(with_ssl)
+endif
+
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
server-no-names server-revoked server-ss \
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index a0a06825c6..87ad7105d6 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -67,11 +67,15 @@ test_connect_fails($common_connstr,
test_connect_fails($common_connstr,
"sslrootcert=ssl/client_ca.crt 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.
-note "connect with server CA cert, without root CA";
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca");
+SKIP: {
+ skip 'Not compiled with OpenSSL', 1 unless $ENV{'WITH_SSL'} eq 'openssl';
+
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ note "connect with server CA cert, without root CA";
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca");
+}
# And finally, with the correct root cert.
note "connect with correct server CA cert file";
@@ -95,9 +99,13 @@ note "testing sslcrl option with a non-revoked cert";
test_connect_ok($common_connstr,
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid");
-# 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");
+SKIP: {
+ skip 'Not compiled with OpenSSL', 1 unless $ENV{'WITH_SSL'} eq 'openssl';
+
+ # 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");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok($common_connstr,
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4c2e12e228..61b39fb9ba 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -118,6 +118,10 @@ sub mkvcbuild
{
push(@pgcommonallfiles, 'sha2_openssl.c');
}
+ elsif ($solution->{options}->{gnutls})
+ {
+ push(@pgcommonallfiles, 'sha2_gnutls.c');
+ }
else
{
push(@pgcommonallfiles, 'sha2.c');
@@ -244,6 +248,11 @@ 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-gnutls.c');
+ $libpq->RemoveFile('src/common/sha2_gnutls.c');
+ }
else
{
$libpq->RemoveFile('src/common/sha2.c');