Hi,

I have seen discussions from time to time about OpenSSL and its licensing issues so I decided to see how much work it would be to add support for another TLS library, and I went with GnuTLS since it is the library I know best after OpenSSL and it is also a reasonably popular library.

Attached is a work in progress patch which implements the basics. I send it the list because I want feedback on some design questions and to check with the community if this is a feature we want.

= What is implemented

- Backend
- Frontend
- Diffie-Hellmann support
- Using GnuTLS for secure random numbers
- Using GnuTLS for sha2

= Work left to do

- Add GnuTLS support to sslinfo
- Add GnuTLS support to pgcrypto
- Support for GnuTLS's version of engines
- Test code with older versions of GnuTLS
- Update documentation
- Add support for all postgresql.conf options (see design question)
- Fix two failing tests (see design question)

= Design questions

== GnuTLS priority strings vs OpenSSL cipher lists

GnuTLS uses a different format for specifying ciphers. Instead of setting ciphers, protocol versions, and ECDH curves separately GnuTLS instead uses a single priority string[1].

For example the default settings of PostgreSQL (which include disabling SSLv3)

ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
ssl_prefer_server_ciphers = on
ssl_ecdh_curve = 'prime256v1'

is represented with a string similar to

SECURE128:+3DES-CBC:+GROUP-SECP256R1:%SERVER_PRECEDENCE

So the two libraries have decided on different ways to specify things.

One way to solve th issue would be to just let ssl_ciphers be the priority string and then add %SERVER_PRECEDENCE to it if ssl_prefer_server_ciphers is on. Adding the ssl_ecdh_curve is trickier since the curves have different names in GnuTLS (e.g. prime256v1 vs SECP256R1) and I would rather avoid having to add such a mapping to PostgreSQL. Thoughts?

== Potentially OpenSSL-specific  est cases

There are currently two failing SSL tests which at least to me seems more like they test specific OpenSSL behaviors rather than something which need to be true for all SSL libraries.

The two tests:

# 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("sslrootcert=ssl/server_ca.crt sslmode=verify-ca");

# A CRL belonging to a different CA is not accepted, fails
test_connect_fails(
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl");

For the missing root CA case GnuTLS seems to be happy enough with just an intermediate CA and as far as I can tell this behavior is not easy to configure.

And for the CRL belonging to a different CA case GnuTLS can be configured to either just store such a non-validating CRL or to ignore it, but not to return an error.

Personally I think thee two tests should just be removed but maybe I am missing something.

Notes:

1. https://gnutls.org/manual/html_node/Priority-Strings.html

Andreas
diff --git a/configure b/configure
index a2f9a256b4..8dcb26b532 100755
--- a/configure
+++ b/configure
@@ -709,6 +709,7 @@ UUID_EXTRA_OBJS
 with_uuid
 with_systemd
 with_selinux
+with_gnutls
 with_openssl
 krb_srvtab
 with_python
@@ -838,6 +839,7 @@ with_bsd_auth
 with_ldap
 with_bonjour
 with_openssl
+with_gnutls
 with_selinux
 with_systemd
 with_readline
@@ -1534,6 +1536,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 GnuTS 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
@@ -6051,6 +6054,41 @@ 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; }
+
+
 #
 # SELinux
 #
@@ -10218,6 +10256,67 @@ 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
+
+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; }
@@ -11015,6 +11114,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 <gnutls/gnutls.h> is required for GnuTLS" "$LINENO" 5
+fi
+
+
 fi
 
 if test "$with_pam" = yes ; then
@@ -15540,9 +15650,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
@@ -15581,6 +15693,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 e94fba5235..ace83610b2 100644
--- a/configure.in
+++ b/configure.in
@@ -734,6 +734,15 @@ 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 GnuTS support],
+              [AC_DEFINE([USE_GNUTLS], 1, [Define to build with GnuTLS support. (--with-gnutls)])])
+AC_MSG_RESULT([$with_gnutls])
+AC_SUBST(with_gnutls)
+
 #
 # SELinux
 #
@@ -1108,6 +1117,10 @@ 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])])
+fi
+
 if test "$with_pam" = yes ; then
   AC_CHECK_LIB(pam,    pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
 fi
@@ -1255,6 +1268,10 @@ if test "$with_openssl" = yes ; then
   AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
 fi
 
+if test "$with_gnutls" = yes ; 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, [],
@@ -2004,9 +2021,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
@@ -2023,6 +2042,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/src/Makefile.global.in b/src/Makefile.global.in
index e8b3a519cb..6c630e54e2 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl	= @with_perl@
 with_python	= @with_python@
 with_tcl	= @with_tcl@
 with_openssl	= @with_openssl@
+with_gnutls    = @with_gnutls@
 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..9d29037d35 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -19,6 +19,8 @@ OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ifaddr.o pqcomm.o \
 
 ifeq ($(with_openssl),yes)
 OBJS += be-secure-openssl.o
+else ifeq ($(with_gnutls),yes)
+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..d442c6c63f
--- /dev/null
+++ b/src/backend/libpq/be-secure-gnutls.c
@@ -0,0 +1,902 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "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);
+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);
+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.)
+	 */
+	if (!isServerStart)
+		gnutls_certificate_set_pin_function(credentials, ssl_passwd_cb, NULL);
+
+	/*
+	 * 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, SSLCipherSuites, &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);
+}
+
+/*
+ *	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;
+}
+
+/*
+ *	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;
+}
+
+/*
+ *	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';
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 246fea8693..1f6f89a4c8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3584,7 +3584,11 @@ static struct config_string ConfigureNamesString[] =
 		},
 		&SSLCipherSuites,
 #ifdef USE_SSL
+#ifdef USE_GNUTLS
+		"NORMAL",
+#else
 		"HIGH:MEDIUM:+3DES:!aNULL",
+#endif
 #else
 		"none",
 #endif
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..3e26161f87 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -47,6 +47,8 @@ OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
 
 ifeq ($(with_openssl),yes)
 OBJS_COMMON += sha2_openssl.o
+else ifeq ($(with_gnutls),yes)
+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 7bde744d51..6f487a7daa 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/pg_config.h.in b/src/include/pg_config.h.in
index dcb7a1a320..64afec94b9 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -819,6 +819,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 f3b35297d1..7ecb9183e2 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -169,7 +169,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 6c02dc7055..1d1a8db482 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -27,6 +27,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 87f22d242f..014b4fc107 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -53,6 +53,8 @@ OBJS += base64.o ip.o md5.o scram-common.o saslprep.o unicode_norm.o
 
 ifeq ($(with_openssl),yes)
 OBJS += fe-secure-openssl.o sha2_openssl.o
+else ifeq ($(with_gnutls),yes)
+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 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 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..e58c4778a3
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gnutls.c
@@ -0,0 +1,1019 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+		case GNUTLS_E_PREMATURE_TERMINATION:
+		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;
+		case GNUTLS_E_PREMATURE_TERMINATION:
+		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;
+}
+
+/* ------------------------------------------------------------ */
+/*						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;
+
+		/*
+		 * Count IP addresses too even if we do not match them to make sure
+		 * SAN takes precedence over the Common Name.
+		 */
+		names_examined++;
+
+		if (ret == GNUTLS_SAN_DNSNAME)
+		{
+			char	   *alt_name = NULL;
+
+			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);
+}
+
+/*
+ *	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;
+}
+
+/*
+ *	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 42913604e3..af9ca66214 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.
@@ -467,7 +469,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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 686c7369f6..a3c59dd954 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');
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to