GnuTLS support
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
Attachments:
gnutls-v1.patchtext/x-patch; name=gnutls-v1.patchDownload
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');
On Thu, Aug 31, 2017 at 1:52 PM, Andreas Karlsson <andreas@proxel.se> wrote:
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.
Thanks for working on this. I think it's good for PostgreSQL to have
more options in this area.
= 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.
I think that what this shows is that the current set of GUCs is overly
OpenSSL-centric. We created a set of GUCs that are actually specific
to one particular implementation but named them as if they were
generic. My idea about this would be to actually rename the existing
GUCs to start with "openssl" rather than "ssl", and then add new GUCs
as needed for other SSL implementations.
Depending on what we think is best, GUCs for an SSL implementation
other than the one against which we compiled can either not exist at
all, or can exist but be limited to a single value (e.g. "none", as we
currently do when the compile has no SSL support at all). Also, we
could add a read-only GUC with a name like ssl_library that exposes
the name of the underlying SSL implementation - none, openssl, gnutls,
or whatever.
I think if we go the route of insisting that every SSL implementation
has to use the existing GUCs, we're just trying to shove a square peg
into a round hole, and there's no real benefit for users. If the
string that has to be stuffed into ssl_ciphers differs based on which
library was chosen at compile time, then you can't have a uniform
default configuration for all libraries anyway. I think it'll be
easier to explain and document this if there's separate documentation
for openssl_ciphers, gnutls_ciphers, etc. rather than one giant
documentation section that tries to explain every implementation
separately.
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.
I don't know what we should do about these issues.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
On Thu, Aug 31, 2017 at 1:52 PM, Andreas Karlsson <andreas@proxel.se> wrote:
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.
Thanks for working on this. I think it's good for PostgreSQL to have
more options in this area.
+1. We also have a patch in the queue to support macOS' TLS library,
and I suppose that's going to be facing similar issues. It would be
a good plan, probably, to try to push both of these to conclusion in
the same development cycle.
I think that what this shows is that the current set of GUCs is overly
OpenSSL-centric. We created a set of GUCs that are actually specific
to one particular implementation but named them as if they were
generic. My idea about this would be to actually rename the existing
GUCs to start with "openssl" rather than "ssl", and then add new GUCs
as needed for other SSL implementations.
Works for me.
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.
I don't know what we should do about these issues.
Maybe the SSL test suite needs to be implementation-specific as well.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 01 Sep 2017, at 19:10, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Thu, Aug 31, 2017 at 1:52 PM, Andreas Karlsson <andreas@proxel.se> wrote:
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.I don't know what we should do about these issues.
Maybe the SSL test suite needs to be implementation-specific as well.
To properly test the macOS Secure Transport support we will need to use
Keychain files on top of plain PEM files, so I think we have to. That being
said, we should probably define a (as large possible) minimum set which applies
to all to ensure compatability between different frontends and backends.
cheers ./daniel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 1, 2017 at 1:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Thu, Aug 31, 2017 at 1:52 PM, Andreas Karlsson <andreas@proxel.se> wrote:
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.Thanks for working on this. I think it's good for PostgreSQL to have
more options in this area.+1. We also have a patch in the queue to support macOS' TLS library,
and I suppose that's going to be facing similar issues. It would be
a good plan, probably, to try to push both of these to conclusion in
the same development cycle.
The thing which I think would save the most aggravation - at least for
my employer - is a Windows SSL implementation. Relying on OpenSSL
means that every time OpenSSL puts out a critical security fix, we've
got to rewrap all the Windows installers to pick up the new version.
If we were relying on what's built into Windows, it would be
Microsoft's problem. Granted, it's not anybody's job to solve
EnterpriseDB's problems except EnterpriseDB, but users might like it
too -- and anyone else who is building Windows installers for
PostgreSQL.
Depending on macOS TLS instead of OpenSSL has similar advantages, of
course, just for a somewhat less common platform.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 01 Sep 2017, at 20:00, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Sep 1, 2017 at 1:10 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Thu, Aug 31, 2017 at 1:52 PM, Andreas Karlsson <andreas@proxel.se> wrote:
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.Thanks for working on this. I think it's good for PostgreSQL to have
more options in this area.+1. We also have a patch in the queue to support macOS' TLS library,
and I suppose that's going to be facing similar issues. It would be
a good plan, probably, to try to push both of these to conclusion in
the same development cycle.The thing which I think would save the most aggravation - at least for
my employer - is a Windows SSL implementation.
In 53EA546E.6020404@vmware.com, an early version of SChannel support was posted
by Heikki. If anyone is keen to pick up the effort that would most likely be a
good starting point.
Relying on OpenSSL
means that every time OpenSSL puts out a critical security fix, we've
got to rewrap all the Windows installers to pick up the new version.
If we were relying on what's built into Windows, it would be
Microsoft's problem. Granted, it's not anybody's job to solve
EnterpriseDB's problems except EnterpriseDB, but users might like it
too -- and anyone else who is building Windows installers for
PostgreSQL.Depending on macOS TLS instead of OpenSSL has similar advantages, of
course, just for a somewhat less common platform.
I think providing alternatives to OpenSSL on platforms where OpenSSL can’t be
relied on to be already available (Windows and macOS come to mind) would be a
great thing for many users and app developers.
cheers ./daniel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Thu, Aug 31, 2017 at 1:52 PM, Andreas Karlsson <andreas@proxel.se> wrote:
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.I don't know what we should do about these issues.
Maybe the SSL test suite needs to be implementation-specific as well.
If only two tests fail currently, I suggest that the thing to do is to
split it up in generic vs. library-specific test files. It should be
easy to do with the TAP framework -- just move the library-specific
tests to their own file and mark it as skipped at the start of the file
when a different library is detected.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 1, 2017 at 12:09:35PM -0400, Robert Haas wrote:
I think that what this shows is that the current set of GUCs is overly
OpenSSL-centric. We created a set of GUCs that are actually specific
to one particular implementation but named them as if they were
generic. My idea about this would be to actually rename the existing
GUCs to start with "openssl" rather than "ssl", and then add new GUCs
as needed for other SSL implementations.Depending on what we think is best, GUCs for an SSL implementation
other than the one against which we compiled can either not exist at
all, or can exist but be limited to a single value (e.g. "none", as we
currently do when the compile has no SSL support at all). Also, we
could add a read-only GUC with a name like ssl_library that exposes
the name of the underlying SSL implementation - none, openssl, gnutls,
or whatever.I think if we go the route of insisting that every SSL implementation
has to use the existing GUCs, we're just trying to shove a square peg
into a round hole, and there's no real benefit for users. If the
string that has to be stuffed into ssl_ciphers differs based on which
library was chosen at compile time, then you can't have a uniform
default configuration for all libraries anyway. I think it'll be
easier to explain and document this if there's separate documentation
for openssl_ciphers, gnutls_ciphers, etc. rather than one giant
documentation section that tries to explain every implementation
separately.
I am worried about having 3x version of TLS controls in postgresql.conf,
and only one set being active. Perhaps we need to break out the TLS
config to separate files or something. Anyway, this needs more thought.
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ As you are, so once was I. As I am, so you will be. +
+ Ancient Roman grave inscription +
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 01, 2017 at 10:33:37PM +0200, Alvaro Herrera wrote:
Tom Lane wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Thu, Aug 31, 2017 at 1:52 PM, Andreas Karlsson <andreas@proxel.se> wrote:
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.I don't know what we should do about these issues.
Maybe the SSL test suite needs to be implementation-specific as well.
If only two tests fail currently, I suggest that the thing to do is to
split it up in generic vs. library-specific test files. It should be
easy to do with the TAP framework -- just move the library-specific
tests to their own file and mark it as skipped at the start of the file
when a different library is detected.
This seems like a much smarter and more reliable way to test.
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
On 09/04/2017 04:24 PM, Bruce Momjian wrote:
On Fri, Sep 1, 2017 at 12:09:35PM -0400, Robert Haas wrote:
I think that what this shows is that the current set of GUCs is overly
OpenSSL-centric. We created a set of GUCs that are actually specific
to one particular implementation but named them as if they were
generic. My idea about this would be to actually rename the existing
GUCs to start with "openssl" rather than "ssl", and then add new GUCs
as needed for other SSL implementations.Depending on what we think is best, GUCs for an SSL implementation
other than the one against which we compiled can either not exist at
all, or can exist but be limited to a single value (e.g. "none", as we
currently do when the compile has no SSL support at all). Also, we
could add a read-only GUC with a name like ssl_library that exposes
the name of the underlying SSL implementation - none, openssl, gnutls,
or whatever.I think if we go the route of insisting that every SSL implementation
has to use the existing GUCs, we're just trying to shove a square peg
into a round hole, and there's no real benefit for users. If the
string that has to be stuffed into ssl_ciphers differs based on which
library was chosen at compile time, then you can't have a uniform
default configuration for all libraries anyway. I think it'll be
easier to explain and document this if there's separate documentation
for openssl_ciphers, gnutls_ciphers, etc. rather than one giant
documentation section that tries to explain every implementation
separately.I am worried about having 3x version of TLS controls in
postgresql.conf, and only one set being active. Perhaps we need to
break out the TLS config to separate files or something. Anyway, this
needs more thought.
Well, people won't be able to set the inactive options, just like you
can't set ssl=on when you build without OpenSSL support. But perhaps we
could simply not include the inactive options into the config file, no?
I don't see how breaking the TLS config into separate files improves the
situation - instead of dealing with GUCs in a single file you'll be
dealing with the same GUCs in multiple files. No?
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Sep 7, 2017 at 2:34 PM, Tomas Vondra <tomas.vondra@2ndquadrant.com>
wrote:
Hi,
On 09/04/2017 04:24 PM, Bruce Momjian wrote:
On Fri, Sep 1, 2017 at 12:09:35PM -0400, Robert Haas wrote:
I think that what this shows is that the current set of GUCs is overly
OpenSSL-centric. We created a set of GUCs that are actually specific
to one particular implementation but named them as if they were
generic. My idea about this would be to actually rename the existing
GUCs to start with "openssl" rather than "ssl", and then add new GUCs
as needed for other SSL implementations.Depending on what we think is best, GUCs for an SSL implementation
other than the one against which we compiled can either not exist at
all, or can exist but be limited to a single value (e.g. "none", as we
currently do when the compile has no SSL support at all). Also, we
could add a read-only GUC with a name like ssl_library that exposes
the name of the underlying SSL implementation - none, openssl, gnutls,
or whatever.I think if we go the route of insisting that every SSL implementation
has to use the existing GUCs, we're just trying to shove a square peg
into a round hole, and there's no real benefit for users. If the
string that has to be stuffed into ssl_ciphers differs based on which
library was chosen at compile time, then you can't have a uniform
default configuration for all libraries anyway. I think it'll be
easier to explain and document this if there's separate documentation
for openssl_ciphers, gnutls_ciphers, etc. rather than one giant
documentation section that tries to explain every implementation
separately.I am worried about having 3x version of TLS controls in
postgresql.conf, and only one set being active. Perhaps we need to
break out the TLS config to separate files or something. Anyway, this
needs more thought.Well, people won't be able to set the inactive options, just like you
can't set ssl=on when you build without OpenSSL support. But perhaps we
could simply not include the inactive options into the config file, no?
We build the pg_hba.conf file dynamically depending on if we have ipv6
support, IIRC. Maybe we need to implement that type of support into
postgresql.conf as well?
It will still be a mess though -- documentation, and tutorials around and
whatnot, will be dependent on library. But I'm not sure we can really get
around that.
Do we have some examples of how other products that support multiple
libraries do to handle this?
I don't see how breaking the TLS config into separate files improves the
situation - instead of dealing with GUCs in a single file you'll be
dealing with the same GUCs in multiple files. No?
+1. I don't think splitting them up into different files makes it in any
way better -- if anything, it makes it worse.
--
Magnus Hagander
Me: https://www.hagander.net/ <http://www.hagander.net/>
Work: https://www.redpill-linpro.com/ <http://www.redpill-linpro.com/>
On 09/07/2017 11:34 PM, Tomas Vondra wrote:
I am worried about having 3x version of TLS controls in
postgresql.conf, and only one set being active. Perhaps we need to
break out the TLS config to separate files or something. Anyway, this
needs more thought.Well, people won't be able to set the inactive options, just like you
can't set ssl=on when you build without OpenSSL support. But perhaps we
could simply not include the inactive options into the config file, no?
Yeah, I have been thinking about how bad it would be to dynamically
generate the config file. I think I will try this.
Daniel: What options does Secure Transport need for configuring ciphers,
ECDH, and cipher preference? Does it need any extra options (I think I
saw something about the keychain)?
Andreas
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andreas Karlsson <andreas@proxel.se> writes:
On 09/07/2017 11:34 PM, Tomas Vondra wrote:
Well, people won't be able to set the inactive options, just like you
can't set ssl=on when you build without OpenSSL support. But perhaps we
could simply not include the inactive options into the config file, no?
Yeah, I have been thinking about how bad it would be to dynamically
generate the config file. I think I will try this.
I'm not exactly convinced that dynamically inserting some parameters
and not others is a great idea. Remember that people tend to copy
postgresql.conf files forward from one installation to another. Or
they might decide to rebuild the postmaster for an existing installation
with a different SSL library. In any scenario like that, you've not
done them any real favor if the config file they have contains no trace
of the SSL parameters they need.
I think we might be best off just playing it straight and providing
a config file that contains a section along these lines:
# Parameters for OpenSSL. Leave these commented out if not using OpenSSL.
#
#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
#ssl_prefer_server_ciphers = on
#ssl_ecdh_curve = 'prime256v1'
#ssl_dh_params_file = ''
#ssl_cert_file = 'server.crt'
#ssl_key_file = 'server.key'
#ssl_ca_file = ''
#ssl_crl_file = ''
#
# Parameters for GnuTLS. Leave these commented out if not using GnuTLS.
#
#gnufoo=...
#gnubar=...
#
# Parameters for macOS TLS. ... you get the idea.
As previously noted, it'd be a good idea to rename the existing
ssl_xxx parameters to openssl_xxx, except maybe ones that we think
will be universal. (But even if we do think that, it might be
simpler in the long run to just have three or four totally independent
sections of the config file, instead of some common and some library-
specific parameters.)
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Sep 7, 2017 at 10:35 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think we might be best off just playing it straight and providing
a config file that contains a section along these lines:# Parameters for OpenSSL. Leave these commented out if not using OpenSSL.
#
#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
#ssl_prefer_server_ciphers = on
#ssl_ecdh_curve = 'prime256v1'
#ssl_dh_params_file = ''
#ssl_cert_file = 'server.crt'
#ssl_key_file = 'server.key'
#ssl_ca_file = ''
#ssl_crl_file = ''
#
# Parameters for GnuTLS. Leave these commented out if not using GnuTLS.
#
#gnufoo=...
#gnubar=...
#
# Parameters for macOS TLS. ... you get the idea.As previously noted, it'd be a good idea to rename the existing
ssl_xxx parameters to openssl_xxx, except maybe ones that we think
will be universal. (But even if we do think that, it might be
simpler in the long run to just have three or four totally independent
sections of the config file, instead of some common and some library-
specific parameters.)
+1 to all of that.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 31, 2017 at 10:52 AM, Andreas Karlsson <andreas@proxel.se>
wrote:
= Work left to do
- Test code with older versions of GnuTLS
I can't build against gnutls-2.12.23-21.el6.x86_64 from CentOS 6.9
be-secure-gnutls.c: In function 'be_tls_init':
be-secure-gnutls.c:168: warning: implicit declaration of function
'gnutls_certificate_set_pin_function'
be-secure-gnutls.c: In function 'get_peer_certificate':
be-secure-gnutls.c:656: error: 'GNUTLS_X509_CRT_LIST_SORT' undeclared
(first use in this function)
be-secure-gnutls.c:656: error: (Each undeclared identifier is reported only
once
be-secure-gnutls.c:656: error: for each function it appears in.)
But I can build on ubuntu 16.04, using whatever baffling salami of package
versions they turned gnutls into on that system.
I can interoperate in both direction gnutls client to openssl server and
the reverse (and gnutls to gnutls).
Cheers,
Jeff
On 09/15/2017 06:55 PM, Jeff Janes wrote:
I can't build against gnutls-2.12.23-21.el6.x86_64 from CentOS 6.9
Thanks for testing my patch. I have fixed both these issues plus some of
the other feedback. A new version of my patch is attached which should,
at least on theory, support all GnuTLS versions >= 2.11.
I just very quickly fixed the broken SSL tests, as I am no fan of how
the SSL tests currently are written and think they should be cleaned up.
Andreas
Attachments:
gnutls-v2.patchtext/x-patch; name=gnutls-v2.patchDownload
diff --git a/configure b/configure
index 0d76e5ea42..33b1f00bff 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,83 @@ done
fi
+if test "$with_gnutls" = yes ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing gnutls_init" >&5
+$as_echo_n "checking for library containing gnutls_init... " >&6; }
+if ${ac_cv_search_gnutls_init+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char gnutls_init ();
+int
+main ()
+{
+return gnutls_init ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' gnutls; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_gnutls_init=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_gnutls_init+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_gnutls_init+:} false; then :
+
+else
+ ac_cv_search_gnutls_init=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_gnutls_init" >&5
+$as_echo "$ac_cv_search_gnutls_init" >&6; }
+ac_res=$ac_cv_search_gnutls_init
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+else
+ as_fn_error $? "library 'gnutls' is required for GnuTLS" "$LINENO" 5
+fi
+
+ # GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ # certificate chains.
+ ac_fn_c_check_decl "$LINENO" "GNUTLS_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
+
+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 +11130,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
@@ -15522,9 +15648,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
@@ -15563,6 +15691,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 bdc41b071f..62af7525b7 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,16 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_gnutls" = yes ; then
+ AC_SEARCH_LIBS(gnutls_init, gnutls, [], [AC_MSG_ERROR([library 'gnutls' is required for GnuTLS])])
+ # GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ # certificate chains.
+ AC_CHECK_DECLS(GNUTLS_X509_CRT_LIST_SORT, [], [],
+[#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+])
+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 +1274,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, [],
@@ -1992,9 +2015,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
@@ -2011,6 +2036,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 fae8068150..82c0cca57f 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..cb00302971
--- /dev/null
+++ b/src/backend/libpq/be-secure-gnutls.c
@@ -0,0 +1,913 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+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.)
+ *
+ * We set the callback globally for compatibility with GnuTLS < 3.1.0.
+ */
+ if (!isServerStart)
+ gnutls_pkcs11_set_pin_function(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, 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);
+}
+
+#ifndef 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;
+}
+
+/*
+ * 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/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/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index bc9f09a086..df15d111c2 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3607,6 +3607,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 53aa006df5..58ce2ab849 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..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/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 579d195663..3b01116f84 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -131,6 +131,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
@@ -815,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..8a890a1888
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gnutls.c
@@ -0,0 +1,1027 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+}
+
+#ifndef 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;
+}
+
+/*
+ * 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/test/ssl/Makefile b/src/test/ssl/Makefile
index e4437d19c3..a8fe6fc7d4 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
+ifeq ($(with_openssl),yes)
+export WITH_OPENSSL=yes
+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 32df273929..01eaf19cc4 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -99,10 +99,14 @@ test_connect_fails("sslrootcert=ssl/client_ca.crt sslmode=require");
test_connect_fails("sslrootcert=ssl/client_ca.crt sslmode=verify-ca");
test_connect_fails("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("sslrootcert=ssl/server_ca.crt sslmode=verify-ca");
+SKIP: {
+ skip 'Not compiled with OpenSSL', 1 unless $ENV{'WITH_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("sslrootcert=ssl/server_ca.crt sslmode=verify-ca");
+}
# And finally, with the correct root cert.
note "connect with correct server CA cert file";
@@ -121,9 +125,13 @@ note "testing sslcrl option with a non-revoked cert";
test_connect_ok(
"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(
-"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl");
+SKIP: {
+ skip 'Not compiled with OpenSSL', 1 unless $ENV{'WITH_OPENSSL'};
+
+ # 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");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
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');
On Sun, Sep 17, 2017 at 2:17 PM, Andreas Karlsson <andreas@proxel.se> wrote:
On 09/15/2017 06:55 PM, Jeff Janes wrote:
I can't build against gnutls-2.12.23-21.el6.x86_64 from CentOS 6.9
Thanks for testing my patch. I have fixed both these issues plus some of
the other feedback. A new version of my patch is attached which should, at
least on theory, support all GnuTLS versions >= 2.11.
You fixed the first issue, but I still get the second one:
be-secure-gnutls.c: In function 'get_peer_certificate':
be-secure-gnutls.c:667: error: 'GNUTLS_X509_CRT_LIST_SORT' undeclared
(first use in this function)
be-secure-gnutls.c:667: error: (Each undeclared identifier is reported only
once
be-secure-gnutls.c:667: error: for each function it appears in.)
Cheers,
Jeff
On 09/18/2017 07:04 PM, Jeff Janes wrote:> You fixed the first issue,
but I still get the second one:
be-secure-gnutls.c: In function 'get_peer_certificate':
be-secure-gnutls.c:667: error: 'GNUTLS_X509_CRT_LIST_SORT' undeclared
(first use in this function)
be-secure-gnutls.c:667: error: (Each undeclared identifier is reported
only once
be-secure-gnutls.c:667: error: for each function it appears in.)
Thanks again for testing the code. I have now rebased the patch and
fixed the second issue. I tested that it works on CentOS 6.
Work which remains:
- sslinfo
- pgcrypto
- Documentation
- Decide if what I did with the config is a good idea
Andreas
Attachments:
gnutls-v4.patchtext/x-patch; name=gnutls-v4.patchDownload
diff --git a/configure b/configure
index 4ecd2e1922..1ba34dfced 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
@@ -837,6 +838,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_gnutls
with_selinux
with_systemd
with_readline
@@ -1531,6 +1533,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
@@ -5997,6 +6000,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
#
@@ -10164,6 +10202,94 @@ done
fi
+if test "$with_gnutls" = yes ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing gnutls_init" >&5
+$as_echo_n "checking for library containing gnutls_init... " >&6; }
+if ${ac_cv_search_gnutls_init+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char gnutls_init ();
+int
+main ()
+{
+return gnutls_init ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' gnutls; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_gnutls_init=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_gnutls_init+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_gnutls_init+:} false; then :
+
+else
+ ac_cv_search_gnutls_init=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_gnutls_init" >&5
+$as_echo "$ac_cv_search_gnutls_init" >&6; }
+ac_res=$ac_cv_search_gnutls_init
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+else
+ as_fn_error $? "library 'gnutls' is required for GnuTLS" "$LINENO" 5
+fi
+
+ # GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ # certificate chains.
+ ac_fn_c_check_decl "$LINENO" "GNUTLS_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; }
@@ -10961,6 +11087,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
@@ -15580,9 +15717,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
@@ -15621,6 +15760,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 cea7fd0755..4ddb101834 100644
--- a/configure.in
+++ b/configure.in
@@ -703,6 +703,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
#
@@ -1077,6 +1086,17 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_gnutls" = yes ; then
+ AC_SEARCH_LIBS(gnutls_init, gnutls, [], [AC_MSG_ERROR([library 'gnutls' is required for GnuTLS])])
+ # GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ # certificate chains.
+ AC_CHECK_DECLS(GNUTLS_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
@@ -1224,6 +1244,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, [],
@@ -1980,9 +2004,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
@@ -1999,6 +2025,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 27ec54a417..abc7572267 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..2640152353
--- /dev/null
+++ b/src/backend/libpq/be-secure-gnutls.c
@@ -0,0 +1,919 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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';
+}
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/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 65372d7cc5..db912263a1 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3604,6 +3604,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 368b280c8a..147b31772b 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..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/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 cfdcc5ac62..30704c614b 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -131,6 +131,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
@@ -253,6 +257,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
@@ -822,6 +829,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 b048175321..7506915a7e 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -175,7 +175,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..534298e47f 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 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..b4ab70ffa3
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gnutls.c
@@ -0,0 +1,1027 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+}
+
+/* ------------------------------------------------------------ */
+/* 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 = 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/test/ssl/Makefile b/src/test/ssl/Makefile
index e4437d19c3..a8fe6fc7d4 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
+ifeq ($(with_openssl),yes)
+export WITH_OPENSSL=yes
+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 32df273929..bac862a424 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -99,10 +99,14 @@ test_connect_fails("sslrootcert=ssl/client_ca.crt sslmode=require");
test_connect_fails("sslrootcert=ssl/client_ca.crt sslmode=verify-ca");
test_connect_fails("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("sslrootcert=ssl/server_ca.crt sslmode=verify-ca");
+SKIP: {
+ skip 'Not compiled with OpenSSL', 1 unless $ENV{'WITH_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("sslrootcert=ssl/server_ca.crt sslmode=verify-ca");
+}
# And finally, with the correct root cert.
note "connect with correct server CA cert file";
@@ -121,9 +125,13 @@ note "testing sslcrl option with a non-revoked cert";
test_connect_ok(
"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(
-"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl");
+SKIP: {
+ skip 'Not compiled with OpenSSL', 1 unless $ENV{'WITH_OPENSSL'};
+
+ # 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");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
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');
Hi,
On 11/02/2017 11:33 PM, Andreas Karlsson wrote:
On 09/18/2017 07:04 PM, Jeff Janes wrote:> You fixed the first issue,
but I still get the second one:be-secure-gnutls.c: In function 'get_peer_certificate':
be-secure-gnutls.c:667: error: 'GNUTLS_X509_CRT_LIST_SORT' undeclared
(first use in this function)
be-secure-gnutls.c:667: error: (Each undeclared identifier is reported
only once
be-secure-gnutls.c:667: error: for each function it appears in.)Thanks again for testing the code. I have now rebased the patch and
fixed the second issue. I tested that it works on CentOS 6.Work which remains:
- sslinfo
- pgcrypto
- Documentation
- Decide if what I did with the config is a good idea
I don't want to be the annoying guy, but this patch no longer applies
due to 642bafa0c5f9f08d106a14f31429e0e0c718b603 touching the tests :-(
BTW regarding the config, I believe you've kept is static (no hiding of
sections based on configure parameters), and you've separated the
openssl and gnutls options, right? Seems fine to me. The one thing is
that it was proposed to rename the openssl-specific options to start
with openssl_ instead of ssl_.
One question though. What happens when you do
./configure --with-openssl --with-gnutls
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)
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Mon, Nov 20, 2017 at 9:42 AM, Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
I don't want to be the annoying guy, but this patch no longer applies
due to 642bafa0c5f9f08d106a14f31429e0e0c718b603 touching the tests :-(
Sorry about that. Something more specific needs to happen here as well
for channel binding support with SCRAM. CheckSCRAMAuth() now assumes
that the channel binding mechanism SCRAM-SHA-256-PLUS can be published
to the client if SSL is used, because OpenSSL is the only
implementation available. Does gnutls include an API to allow an
application to fetch the bytes from the TLS finished message? I can
see some references by glancing at the tarball of gnutls 3.6.1, but
you would need something similar to OpenSSL's SSL_get_peer_finished().
If that cannot be achieved I think that it will be necessary to tweak
auth.c to not publish the -PLUS mechanism if for example the result of
be_tls_get_peer_finished() is NULL. No need for a new SSL-specific
API. At the end it would prove to be more portable to do so for all
the SSL implementations, MacOS stuff does not document an API to get
the TLS finish message bytes.
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.
--
Michael
On 11/20/2017 02:56 AM, Michael Paquier wrote:
Sorry about that. Something more specific needs to happen here as well
for channel binding support with SCRAM. CheckSCRAMAuth() now assumes
that the channel binding mechanism SCRAM-SHA-256-PLUS can be published
to the client if SSL is used, because OpenSSL is the only
implementation available. Does gnutls include an API to allow an
application to fetch the bytes from the TLS finished message? I can
see some references by glancing at the tarball of gnutls 3.6.1, but
you would need something similar to OpenSSL's SSL_get_peer_finished().
If that cannot be achieved I think that it will be necessary to tweak
auth.c to not publish the -PLUS mechanism if for example the result of
be_tls_get_peer_finished() is NULL. No need for a new SSL-specific
API. At the end it would prove to be more portable to do so for all
the SSL implementations, MacOS stuff does not document an API to get
the TLS finish message bytes.
There is a function called gnutls_session_channel_binding() which seems
to do something very similar to SSL_get*_finished() which has been in
GnuTLS since 2.12.
https://www.gnutls.org/manual/gnutls.html#Channel-Bindings
--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.
Agreed.
Andreas
On Tue, Nov 21, 2017 at 11:45 PM, Andreas Karlsson <andreas@proxel.se> wrote:
There is a function called gnutls_session_channel_binding() which seems to
do something very similar to SSL_get*_finished() which has been in GnuTLS
since 2.12.
Nice. This way you can get tls-unique to work properly.
--
Michael
On 11/20/2017 02:56 AM, Michael Paquier wrote:
On Mon, Nov 20, 2017 at 9:42 AM, Tomas Vondra
<tomas.vondra@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
Attachments:
gnutls-v5.patchtext/x-patch; name=gnutls-v5.patchDownload
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');
On Mon, Nov 27, 2017 at 10:05 AM, Andreas Karlsson <andreas@proxel.se> wrote:
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).
By default --with-openssl does not take a path with ./configure. When
using gnutls, do the name of the libraries and the binaries generated
change compared to openssl? If yes, and I guess that it is the case,
you will need to be able to make the difference between gnutls and
openssl anyway as the set of perl scripts in src/tools/msvc need to
make the difference with deliverables at name-level. I would be
personally fine with just listing gnutls in the list of options and
comment it as --with-ssl=<path>, and change the openssl comment to
match that.
--
Michael
On 11/27/2017 02:20 AM, Michael Paquier wrote:
On Mon, Nov 27, 2017 at 10:05 AM, Andreas Karlsson <andreas@proxel.se> wrote:
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).By default --with-openssl does not take a path with ./configure. When
using gnutls, do the name of the libraries and the binaries generated
change compared to openssl? If yes, and I guess that it is the case,
you will need to be able to make the difference between gnutls and
openssl anyway as the set of perl scripts in src/tools/msvc need to
make the difference with deliverables at name-level. I would be
personally fine with just listing gnutls in the list of options and
comment it as --with-ssl=<path>, and change the openssl comment to
match that.
Hm, after reading more of our MSVC code it seems like building with MSVC
does not really use switch, people rather have to create a config.pl. Is
that correct? The MSVC scripts also seems to only support uuid-ossp
which it just calls $config->{uuid}.
If so we could either have:
$config->{ssl} = "openssl";
$config->{sslpath} = "/path/to/openssl";
or
$config->{ssl} = "openssl";
$config->{openssl} = "/path/to/openssl";
or
$config->{openssl} = "/path/to/openssl";
vs
$config->{gnutls} = "/path/to/gnutls";
Andreas
On Mon, Nov 27, 2017 at 10:28 AM, Andreas Karlsson <andreas@proxel.se> wrote:
Hm, after reading more of our MSVC code it seems like building with MSVC
does not really use switch, people rather have to create a config.pl. Is
that correct? The MSVC scripts also seems to only support uuid-ossp which it
just calls $config->{uuid}.If so we could either have:
$config->{ssl} = "openssl";
$config->{sslpath} = "/path/to/openssl";
Using this one may actually finish by being cleaner as there is no
need to cross-check for both options defined.
--
Michael
On Mon, Nov 27, 2017 at 2:37 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Mon, Nov 27, 2017 at 10:28 AM, Andreas Karlsson <andreas@proxel.se> wrote:
Hm, after reading more of our MSVC code it seems like building with MSVC
does not really use switch, people rather have to create a config.pl. Is
that correct? The MSVC scripts also seems to only support uuid-ossp which it
just calls $config->{uuid}.If so we could either have:
$config->{ssl} = "openssl";
$config->{sslpath} = "/path/to/openssl";Using this one may actually finish by being cleaner as there is no
need to cross-check for both options defined.
Andreas, as I can see that you are still actively working on this
patch, I am bumping it to next CF.
--
Michael
On 11/19/17 20:56, Michael Paquier 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'm not sure whether this is a great improvement. Why upset existing
build and packaging scripts? The usual options style is
--with-nameoflib. We can have separate options and error if conflicting
combinations are specified.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 11/28/2017 04:19 PM, Peter Eisentraut wrote:
On 11/19/17 20:56, Michael Paquier wrote:
--with-ssl=(openssl|gnutls)
I'm not sure whether this is a great improvement. Why upset existing
build and packaging scripts? The usual options style is
--with-nameoflib. We can have separate options and error if conflicting
combinations are specified.
We already have a precedent in --with-uuid=LIB which has the backwards
compatibility alias --with-ossp-uuid, so I think adding --with-ssl=LIB
while keeping --with-openssl as an alias is consistent with that. I also
think the code and the interface ended up pretty clean when I added
--with-ssl in my latest patch.
The only issue I see with --with-ssl is Window's config.pl which might
end up a bit ugly to support '$config->{openssl} = "path";' as a legacy
alias.
Andreas
On 11/26/17 20:05, Andreas Karlsson wrote:
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).
This patch appears to work well.
As I had mentioned previously, I'm not fond of changing the existing
configure flags, and given the above issue, I'd just leave everything as
is and add --with-gnutls.
The patch contains a purported GUC variable gnutls_priority, but it is
not documented or used anywhere.
There are some test cases that are marked to be skipped. We should
document why that is.
I see a potential problem with the SCRAM channel binding support.
GnuTLS will not support tls-server-endpoint, so we'll need to check what
happens when a client requests that. (That's not the problem of this
patch, however.)
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Tue, Jan 02, 2018 at 10:35:16AM -0500, Peter Eisentraut wrote:
I see a potential problem with the SCRAM channel binding support.
GnuTLS will not support tls-server-endpoint, so we'll need to check what
happens when a client requests that. (That's not the problem of this
patch, however.)
Doesn't it depend on the first patch merged into HEAD? At the end we'll
need to make be_tls_get_certificate_hash() generate an ereport() with
ERRCODE_FEATURE_NOT_SUPPORTED and have pgtls_get_peer_certificate_hash()
return NULL with conn->errorMessage properly filled.
--
Michael
On 1/2/18 18:35, Michael Paquier wrote:
On Tue, Jan 02, 2018 at 10:35:16AM -0500, Peter Eisentraut wrote:
I see a potential problem with the SCRAM channel binding support.
GnuTLS will not support tls-server-endpoint, so we'll need to check what
happens when a client requests that. (That's not the problem of this
patch, however.)Doesn't it depend on the first patch merged into HEAD? At the end we'll
need to make be_tls_get_certificate_hash() generate an ereport() with
ERRCODE_FEATURE_NOT_SUPPORTED and have pgtls_get_peer_certificate_hash()
return NULL with conn->errorMessage properly filled.
The problem I see is that SCRAM doesn't really support the negotiation
of the channel binding type. The client picks one, and the server
either accepts it or the whole thing dies.
One scenario is that if GnuTLS goes in, it's quite plausible that the
PG11 packages for Debian and Ubuntu will use it by default. But if it
doesn't support tls-server-endpoint, then a JDBC client (assuming
channel binding support is added) can't connect to such a server with
SCRAM authentication over SSL (which we hope will be a popular
configuration), unless they manually disable channel binding altogether
using the new scramchannelbinding connection option. That would be a
very poor experience.
I think the solution is that we need to require that all SSL server-side
implementations support all channel binding types.
I think that can probably be done with GnuTLS, but we'd need to check
that out a bit.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Tue, Jan 02, 2018 at 10:54:29PM -0500, Peter Eisentraut wrote:
I think the solution is that we need to require that all SSL server-side
implementations support all channel binding types.
That could be a stop for Windows and macos SSL implementations then. I
would think that we would benefit by being softer here, say with the
following guidelines:
- Have the server publish the -PLUS mechanism only if an SSL
implementation supports tls-unique.
- The RFC makes tls-unique mandatory, so requiring only tls-unique to be
present looks like a good default for me.
It is true that JDBC makes this whole thing harder, tls-server-end-point
patch has been done mainly for them. Even for OpenSSL, I had to dig
within their code tree to figure out the APIs to use to get the hash
algorithm. I would not be surprised that the same investigation is
necessary for gnutls.
--
Michael
On 1/3/18 04:59, Michael Paquier wrote:
On Tue, Jan 02, 2018 at 10:54:29PM -0500, Peter Eisentraut wrote:
I think the solution is that we need to require that all SSL server-side
implementations support all channel binding types.That could be a stop for Windows and macos SSL implementations then.
I'm surprised by that. I thought tls-server-endpoint is basically
always possible to implement, because all you need is to obtain the peer
certificate and hash it. It seems to me that any SSL implementation
should be able to do that.
- Have the server publish the -PLUS mechanism only if an SSL
implementation supports tls-unique.
But then a conforming client will never pick -PLUS.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 1/2/18 10:35, Peter Eisentraut wrote:
On 11/26/17 20:05, Andreas Karlsson wrote:
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).This patch appears to work well.
Seeing that Andres is apparently currently not available, I have started
to dig through this patch myself and made some adjustments.
Question for the group: We currently have a number of config settings
named ssl_*. Some of these are specific to OpenSSL, some are not, namely:
# general
ssl
ssl_dh_params_file
ssl_cert_file
ssl_key_file
ssl_ca_file
ssl_crl_file
# OpenSSL
ssl_ciphers
ssl_prefer_server_ciphers
ssl_ecdh_curve
# GnuTLS (proposed)
gnutls_priorities
(effectively a combination of ssl_ciphers and ssl_prefer_server_ciphers)
Should we rename the OpenSSL-specific settings to openssl_*?
It think it would be better for clarity, and they are not set very
commonly, so the user impact would be low.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
Question for the group: We currently have a number of config settings
named ssl_*. Some of these are specific to OpenSSL, some are not, namely:
# general
ssl
ssl_dh_params_file
ssl_cert_file
ssl_key_file
ssl_ca_file
ssl_crl_file
# OpenSSL
ssl_ciphers
ssl_prefer_server_ciphers
ssl_ecdh_curve
# GnuTLS (proposed)
gnutls_priorities
(effectively a combination of ssl_ciphers and ssl_prefer_server_ciphers)
Should we rename the OpenSSL-specific settings to openssl_*?
It think it would be better for clarity, and they are not set very
commonly, so the user impact would be low.
Yeah, I think only the "general" parameters would be set by very
many people. +1 for renaming the OpenSSL-only parameters.
I don't know too much about the internals here, so looking at your
list, I wonder whether "ssl_dh_params_file" ought to be treated as
implementation-specific too. The other four files seem essential
to any feature-complete implementation, but is that one?
regards, tom lane
On 1/17/18 12:39, Tom Lane wrote:
I don't know too much about the internals here, so looking at your
list, I wonder whether "ssl_dh_params_file" ought to be treated as
implementation-specific too. The other four files seem essential
to any feature-complete implementation, but is that one?
The proposed GnuTLS patch does make use of ssl_dh_params_file.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
On 1/17/18 12:39, Tom Lane wrote:
I don't know too much about the internals here, so looking at your
list, I wonder whether "ssl_dh_params_file" ought to be treated as
implementation-specific too. The other four files seem essential
to any feature-complete implementation, but is that one?
The proposed GnuTLS patch does make use of ssl_dh_params_file.
Right, but what happens if say macTLS doesn't?
There are basically two approaches we can take here:
1. All the relevant parameters are named "ssl_something", and we have
to flag in the documentation any that are supported only by some
implementations.
2. Parameters that might be supported only by some implementations
are named with implementation-specific names, and we have to accept
that there might sometimes be both "foossl_xyz" and "barssl_xyz".
What I don't want to end up with is an unholy mixture of both approaches.
Therefore, if we are going to use method #2, we must be certain that
the basic "ssl_" parameters are supported by every implementation,
to the point where we'd reject an implementation that didn't have one.
I can see that we'd reject an implementation lacking CRL support
for instance, but I'm less clear that lack of configurable DH parameters
should be a disqualifying feature omission. I'm prepared to be educated
either way, but that's the core question here.
regards, tom lane
On Wed, Jan 17, 2018 at 8:18 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
On 1/17/18 12:39, Tom Lane wrote:
I don't know too much about the internals here, so looking at your
list, I wonder whether "ssl_dh_params_file" ought to be treated as
implementation-specific too. The other four files seem essential
to any feature-complete implementation, but is that one?The proposed GnuTLS patch does make use of ssl_dh_params_file.
Right, but what happens if say macTLS doesn't?
There are basically two approaches we can take here:
1. All the relevant parameters are named "ssl_something", and we have
to flag in the documentation any that are supported only by some
implementations.2. Parameters that might be supported only by some implementations
are named with implementation-specific names, and we have to accept
that there might sometimes be both "foossl_xyz" and "barssl_xyz".What I don't want to end up with is an unholy mixture of both approaches.
Therefore, if we are going to use method #2, we must be certain that
the basic "ssl_" parameters are supported by every implementation,
to the point where we'd reject an implementation that didn't have one.
I can see that we'd reject an implementation lacking CRL support
for instance, but I'm less clear that lack of configurable DH parameters
should be a disqualifying feature omission. I'm prepared to be educated
either way, but that's the core question here.
So in this particular case, does it mean that to do #2, we sould actually
have an openssl_dh_params_file and a gnutls_dh_params_file, but only one at
any given time?
Thinking on that there is also the case of file formats. What if one
provider takes a cert file, but not in the same format -- should that still
be ssl_cert_file, or should it be a different parameter name? Given that
you can't use it to point to the same file.
--
Magnus Hagander
Me: https://www.hagander.net/ <http://www.hagander.net/>
Work: https://www.redpill-linpro.com/ <http://www.redpill-linpro.com/>
Magnus Hagander <magnus@hagander.net> writes:
On Wed, Jan 17, 2018 at 8:18 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
What I don't want to end up with is an unholy mixture of both approaches.
Therefore, if we are going to use method #2, we must be certain that
the basic "ssl_" parameters are supported by every implementation,
to the point where we'd reject an implementation that didn't have one.
I can see that we'd reject an implementation lacking CRL support
for instance, but I'm less clear that lack of configurable DH parameters
should be a disqualifying feature omission. I'm prepared to be educated
either way, but that's the core question here.
So in this particular case, does it mean that to do #2, we sould actually
have an openssl_dh_params_file and a gnutls_dh_params_file, but only one at
any given time?
That's where I think we end up if we don't want to assume that every
implementation has dh_params_file support.
Thinking on that there is also the case of file formats. What if one
provider takes a cert file, but not in the same format -- should that still
be ssl_cert_file, or should it be a different parameter name? Given that
you can't use it to point to the same file.
Meh --- as long as any given build supports only one SSL implementation,
I think insisting on that would be overly pedantic. Seems like as long
as what you put into the config file is the same, it's okay to consider
a given parameter to be shared by two different implementations.
Although these corner cases are starting to make me feel like changing
my original vote. Maybe we should forget the prefixes, in particular
renaming gnutls_priorities to ssl_priorities, and just accept the need
to document some parameters as only relevant to some implementations.
That certainly requires less betting on our ability to predict the
future.
regards, tom lane
On 1/17/18 13:18, Tom Lane wrote:
The proposed GnuTLS patch does make use of ssl_dh_params_file.
Right, but what happens if say macTLS doesn't?
The previously proposed patch for that also makes use of ssl_dh_params_file.
So while we can't guarantee that this will be the case for all TLS
implementations ever, this is a pretty good indicator to me that it is
an implementation-independent concept.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 1/17/18 14:05, Tom Lane wrote:
Although these corner cases are starting to make me feel like changing
my original vote. Maybe we should forget the prefixes, in particular
renaming gnutls_priorities to ssl_priorities, and just accept the need
to document some parameters as only relevant to some implementations.
We could go the route of normalizing all implementation-specific
settings to some set of atomic concepts and create separate settings for
those, and then map them back to the actual APIs in code.
So we could take ssl_ciphers, ssl_prefer_server_ciphers, ssl_ecdh_curve
and assemble internally something that we can pass to
gnutls_priority_init().
But I think it would be more helpful in practice if the naming of the
implementation-specific settings match with something you can look up in
the documentation of that implementation. "GnuTLS priority string" is
easy to look up and well documented. If instead we chop it up into
something that is more like the OpenSSL settings, I think we are not
helping anyone.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 01/17/2018 11:14 PM, Peter Eisentraut wrote:
On 1/17/18 14:05, Tom Lane wrote:
Although these corner cases are starting to make me feel like
changing my original vote. Maybe we should forget the prefixes, in
particular renaming gnutls_priorities to ssl_priorities, and just
accept the need to document some parameters as only relevant to
some implementations.We could go the route of normalizing all implementation-specific
settings to some set of atomic concepts and create separate settings
for those, and then map them back to the actual APIs in code.
I think it's reasonable to expect that other SSL libraries implementing
the same crypto algorithms will have APIs with the same (or very
similar) concepts and parameters, so +1 to doing this.
So we could take ssl_ciphers, ssl_prefer_server_ciphers,
ssl_ecdh_curve and assemble internally something that we can pass to
gnutls_priority_init().
I think the question here is how far we want to go. For example, the
various libraries are likely using different formats for the lists of
preferred cipher suites. Are we fine with that? The meaning of the GUC
will be the same, but the format (or cipher suite naming) will depend on
the library.
(I don't think we want to get into the business of inventing our custom
universal syntax, nor accepting OpenSSL syntax/naming as the right one
and translating everything to/from it.)
But I think it would be more helpful in practice if the naming of
the implementation-specific settings match with something you can
look up in the documentation of that implementation. "GnuTLS priority
string" is easy to look up and well documented. If instead we chop it
up into something that is more like the OpenSSL settings, I think we
are not helping anyone.
Meh. As long as there's something like "priority string" for that
particular library, I think we're fine. We can just say "This is a
priority string, the format depends on the SSL library used," and
perhaps even link to additional docs for each supported library.
What would be much worse is if a particular GUC did not have a matching
concept in the library. Say if an SSL library did not have a concept of
priority strings and instead used some other concept affecting cipher
suite choice (not sure how that would like). That would make our GUC
useless or confusing, possibly forcing us to translate the strings in
some strange way.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Wed, Jan 17, 2018 at 6:48 PM, Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
What would be much worse is if a particular GUC did not have a matching
concept in the library. Say if an SSL library did not have a concept of
priority strings and instead used some other concept affecting cipher
suite choice (not sure how that would like). That would make our GUC
useless or confusing, possibly forcing us to translate the strings in
some strange way.
I think that's pretty likely to happen, which is why I favor renaming
all of the SSL stuff to openssl_* and then having gnutls_* and
similarly for other implementations. It's not going to be fun to
document that there's this single GUC which, depending on some
compiler flag which you don't know anything about, takes a
differently-formatted value and maybe does different stuff. That's
what we'll end up with even for absolutely simple things like
ssl_ciphers, because it's extremely unlikely that every SSL library on
earth uses the same format that OpenSSL does. Worse yet, users are
not going to intrinsically know which SSL implementation was compiled
into the server they have.
Now, if we can tell them something like this, then things will be better:
PostgreSQL can be compiled against any of several SSL implementations.
Currently, PostgreSQL supports OpenSSL, GnuTLS, AwesomeSSL, and
TLSBlah. Each of these implementations is controlled by a different
group of settings; only settings for the SSL implementation against
which the server is compiled will exist. For OpenSSL, the controlling
settings are openssl_thingy, openssl_thang, and openssl_thunk. For
GnuTLS, the controlling settings are .... etc. etc.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
... Worse yet, users are
not going to intrinsically know which SSL implementation was compiled
into the server they have.
That is a really good point. For precedent, note that darn near nobody
seems to know whether their psql contains readline or libedit. If we
force the issue by giving the settings different names, then they'll be
forced to figure out which SSL implementation they have.
On the other hand, you could argue that there are more user-friendly
ways to expose that information than demanding that users play twenty
questions with their config files. I'd at least want us to recognize
when somebody tries to set "openssl_foo" in a gnutls implementation,
and respond with "you need to twiddle the gnutls_xxx variables instead"
rather than just "unrecognized configuration parameter". Maybe that'd
be good enough, though.
Also, this isn't really a good argument against using uniform names
for parameters that every implementation is certain to have, like
ssl_key_file.
regards, tom lane
Comparing the existing {be,fe}-secure-openssl.c with the proposed
{be,fe}-secure-gnutls.c, and with half an eye on the previously proposed
Apple Secure Transport implementation, I have identified a few more
areas of refactoring that should be done in order to avoid excessive
copy-and-pasting in the new implementations:
0001-Add-installcheck-support-to-more-test-suites.patch
This will help with interoperability testing, because you can then
create an installation with mixed SSL implementations and run the test
suite against it.
0002-Split-out-documentation-of-SSL-parameters-into-their.patch
Prepares and cleans up the documentation a bit before the addition of
new things, as discussed elsewhere.
0003-Move-EDH-support-to-common-files.patch
To avoid copy-and-paste, and also because the EDH explanation doesn't
really belong in a file header comment. Maybe the whole thing is known
well enough nowadays that we can just remove the explanation.
0004-Move-SSL-API-comments-to-header-files.patch
0005-Extract-common-bits-from-OpenSSL-implementation.patch
Move copy-and-paste avoidance.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
0001-Add-installcheck-support-to-more-test-suites.patchtext/plain; charset=UTF-8; name=0001-Add-installcheck-support-to-more-test-suites.patch; x-mac-creator=0; x-mac-type=0Download
From 5b5f568bf05b2424ceb522c0a6d2f94487fea3b7 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 19 Jan 2018 12:17:35 -0500
Subject: [PATCH 1/5] Add installcheck support to more test suites
---
src/test/authentication/Makefile | 3 +++
src/test/ldap/Makefile | 3 +++
src/test/recovery/Makefile | 3 +++
src/test/ssl/Makefile | 3 +++
src/test/subscription/Makefile | 3 +++
5 files changed, 15 insertions(+)
diff --git a/src/test/authentication/Makefile b/src/test/authentication/Makefile
index a435b13057..218452ec76 100644
--- a/src/test/authentication/Makefile
+++ b/src/test/authentication/Makefile
@@ -16,5 +16,8 @@ include $(top_builddir)/src/Makefile.global
check:
$(prove_check)
+installcheck:
+ $(prove_installcheck)
+
clean distclean maintainer-clean:
rm -rf tmp_check
diff --git a/src/test/ldap/Makefile b/src/test/ldap/Makefile
index 50e3c17e95..fef5742b82 100644
--- a/src/test/ldap/Makefile
+++ b/src/test/ldap/Makefile
@@ -16,5 +16,8 @@ include $(top_builddir)/src/Makefile.global
check:
$(prove_check)
+installcheck:
+ $(prove_installcheck)
+
clean distclean maintainer-clean:
rm -rf tmp_check
diff --git a/src/test/recovery/Makefile b/src/test/recovery/Makefile
index aecf37d89a..daf79a0b1f 100644
--- a/src/test/recovery/Makefile
+++ b/src/test/recovery/Makefile
@@ -18,5 +18,8 @@ include $(top_builddir)/src/Makefile.global
check:
$(prove_check)
+installcheck:
+ $(prove_installcheck)
+
clean distclean maintainer-clean:
rm -rf tmp_check
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 4886e901d0..4e9095529a 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -132,3 +132,6 @@ clean distclean maintainer-clean:
check:
$(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/test/subscription/Makefile b/src/test/subscription/Makefile
index 25c48e470d..0f3d2098ad 100644
--- a/src/test/subscription/Makefile
+++ b/src/test/subscription/Makefile
@@ -18,5 +18,8 @@ EXTRA_INSTALL = contrib/hstore
check:
$(prove_check)
+installcheck:
+ $(prove_installcheck)
+
clean distclean maintainer-clean:
rm -rf tmp_check
base-commit: 4e54dd2e0a750352ce2a5c45d1cc9183e887eec3
--
2.15.1
0002-Split-out-documentation-of-SSL-parameters-into-their.patchtext/plain; charset=UTF-8; name=0002-Split-out-documentation-of-SSL-parameters-into-their.patch; x-mac-creator=0; x-mac-type=0Download
From d9160fc3cfa0e9725c857469fc1f156452a77922 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 18 Jan 2018 19:12:05 -0500
Subject: [PATCH 2/5] Split out documentation of SSL parameters into their own
section
Split the "Authentication and Security" section into two separate
sections "Authentication" and "SSL". The latter part has gotten much
longer over time, and doesn't primarily have to do with authentication.
---
doc/src/sgml/config.sgml | 233 +++++++++++++++++++++--------------------
src/backend/utils/misc/guc.c | 38 +++----
src/include/utils/guc_tables.h | 3 +-
3 files changed, 143 insertions(+), 131 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 37a61a13c8..907bf1471d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -924,8 +924,9 @@ <title>Connection Settings</title>
</variablelist>
</sect2>
- <sect2 id="runtime-config-connection-security">
- <title>Security and Authentication</title>
+
+ <sect2 id="runtime-config-connection-authentication">
+ <title>Authentication</title>
<variablelist>
<varlistentry id="guc-authentication-timeout" xreflabel="authentication_timeout">
@@ -950,6 +951,123 @@ <title>Security and Authentication</title>
</listitem>
</varlistentry>
+ <varlistentry id="guc-password-encryption" xreflabel="password_encryption">
+ <term><varname>password_encryption</varname> (<type>enum</type>)
+ <indexterm>
+ <primary><varname>password_encryption</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ When a password is specified in <xref linkend="sql-createrole"/> or
+ <xref linkend="sql-alterrole"/>, this parameter determines the algorithm
+ to use to encrypt the password. The default value is <literal>md5</literal>,
+ which stores the password as an MD5 hash (<literal>on</literal> is also
+ accepted, as alias for <literal>md5</literal>). Setting this parameter to
+ <literal>scram-sha-256</literal> will encrypt the password with SCRAM-SHA-256.
+ </para>
+ <para>
+ Note that older clients might lack support for the SCRAM authentication
+ mechanism, and hence not work with passwords encrypted with
+ SCRAM-SHA-256. See <xref linkend="auth-password"/> for more details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-krb-server-keyfile" xreflabel="krb_server_keyfile">
+ <term><varname>krb_server_keyfile</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>krb_server_keyfile</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Sets the location of the Kerberos server key file. See
+ <xref linkend="gssapi-auth"/>
+ for details. This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-krb-caseins-users" xreflabel="krb_caseins_users">
+ <term><varname>krb_caseins_users</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>krb_caseins_users</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Sets whether GSSAPI user names should be treated
+ case-insensitively.
+ The default is <literal>off</literal> (case sensitive). This parameter can only be
+ set in the <filename>postgresql.conf</filename> file or on the server command line.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-db-user-namespace" xreflabel="db_user_namespace">
+ <term><varname>db_user_namespace</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>db_user_namespace</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ This parameter enables per-database user names. It is off by default.
+ This parameter can only be set in the <filename>postgresql.conf</filename>
+ file or on the server command line.
+ </para>
+
+ <para>
+ If this is on, you should create users as <replaceable>username@dbname</replaceable>.
+ When <replaceable>username</replaceable> is passed by a connecting client,
+ <literal>@</literal> and the database name are appended to the user
+ name and that database-specific user name is looked up by the
+ server. Note that when you create users with names containing
+ <literal>@</literal> within the SQL environment, you will need to
+ quote the user name.
+ </para>
+
+ <para>
+ With this parameter enabled, you can still create ordinary global
+ users. Simply append <literal>@</literal> when specifying the user
+ name in the client, e.g. <literal>joe@</literal>. The <literal>@</literal>
+ will be stripped off before the user name is looked up by the
+ server.
+ </para>
+
+ <para>
+ <varname>db_user_namespace</varname> causes the client's and
+ server's user name representation to differ.
+ Authentication checks are always done with the server's user name
+ so authentication methods must be configured for the
+ server's user name, not the client's. Because
+ <literal>md5</literal> uses the user name as salt on both the
+ client and server, <literal>md5</literal> cannot be used with
+ <varname>db_user_namespace</varname>.
+ </para>
+
+ <note>
+ <para>
+ This feature is intended as a temporary measure until a
+ complete solution is found. At that time, this option will
+ be removed.
+ </para>
+ </note>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </sect2>
+
+ <sect2 id="runtime-config-connection-ssl">
+ <title>SSL</title>
+
+ <para>
+ See <xref linkend="ssl-tcp"/> for more information about setting up SSL.
+ </para>
+
+ <variablelist>
<varlistentry id="guc-ssl" xreflabel="ssl">
<term><varname>ssl</varname> (<type>boolean</type>)
<indexterm>
@@ -958,8 +1076,7 @@ <title>Security and Authentication</title>
</term>
<listitem>
<para>
- Enables <acronym>SSL</acronym> connections. Please read
- <xref linkend="ssl-tcp"/> before using this.
+ Enables <acronym>SSL</acronym> connections.
This parameter can only be set in the <filename>postgresql.conf</filename>
file or on the server command line.
The default is <literal>off</literal>.
@@ -1172,29 +1289,6 @@ <title>Security and Authentication</title>
</listitem>
</varlistentry>
- <varlistentry id="guc-password-encryption" xreflabel="password_encryption">
- <term><varname>password_encryption</varname> (<type>enum</type>)
- <indexterm>
- <primary><varname>password_encryption</varname> configuration parameter</primary>
- </indexterm>
- </term>
- <listitem>
- <para>
- When a password is specified in <xref linkend="sql-createrole"/> or
- <xref linkend="sql-alterrole"/>, this parameter determines the algorithm
- to use to encrypt the password. The default value is <literal>md5</literal>,
- which stores the password as an MD5 hash (<literal>on</literal> is also
- accepted, as alias for <literal>md5</literal>). Setting this parameter to
- <literal>scram-sha-256</literal> will encrypt the password with SCRAM-SHA-256.
- </para>
- <para>
- Note that older clients might lack support for the SCRAM authentication
- mechanism, and hence not work with passwords encrypted with
- SCRAM-SHA-256. See <xref linkend="auth-password"/> for more details.
- </para>
- </listitem>
- </varlistentry>
-
<varlistentry id="guc-ssl-dh-params-file" xreflabel="ssl_dh_params_file">
<term><varname>ssl_dh_params_file</varname> (<type>string</type>)
<indexterm>
@@ -1218,91 +1312,6 @@ <title>Security and Authentication</title>
</para>
</listitem>
</varlistentry>
-
- <varlistentry id="guc-krb-server-keyfile" xreflabel="krb_server_keyfile">
- <term><varname>krb_server_keyfile</varname> (<type>string</type>)
- <indexterm>
- <primary><varname>krb_server_keyfile</varname> configuration parameter</primary>
- </indexterm>
- </term>
- <listitem>
- <para>
- Sets the location of the Kerberos server key file. See
- <xref linkend="gssapi-auth"/>
- for details. This parameter can only be set in the
- <filename>postgresql.conf</filename> file or on the server command line.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry id="guc-krb-caseins-users" xreflabel="krb_caseins_users">
- <term><varname>krb_caseins_users</varname> (<type>boolean</type>)
- <indexterm>
- <primary><varname>krb_caseins_users</varname> configuration parameter</primary>
- </indexterm>
- </term>
- <listitem>
- <para>
- Sets whether GSSAPI user names should be treated
- case-insensitively.
- The default is <literal>off</literal> (case sensitive). This parameter can only be
- set in the <filename>postgresql.conf</filename> file or on the server command line.
- </para>
- </listitem>
- </varlistentry>
-
- <varlistentry id="guc-db-user-namespace" xreflabel="db_user_namespace">
- <term><varname>db_user_namespace</varname> (<type>boolean</type>)
- <indexterm>
- <primary><varname>db_user_namespace</varname> configuration parameter</primary>
- </indexterm>
- </term>
- <listitem>
- <para>
- This parameter enables per-database user names. It is off by default.
- This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
- </para>
-
- <para>
- If this is on, you should create users as <replaceable>username@dbname</replaceable>.
- When <replaceable>username</replaceable> is passed by a connecting client,
- <literal>@</literal> and the database name are appended to the user
- name and that database-specific user name is looked up by the
- server. Note that when you create users with names containing
- <literal>@</literal> within the SQL environment, you will need to
- quote the user name.
- </para>
-
- <para>
- With this parameter enabled, you can still create ordinary global
- users. Simply append <literal>@</literal> when specifying the user
- name in the client, e.g. <literal>joe@</literal>. The <literal>@</literal>
- will be stripped off before the user name is looked up by the
- server.
- </para>
-
- <para>
- <varname>db_user_namespace</varname> causes the client's and
- server's user name representation to differ.
- Authentication checks are always done with the server's user name
- so authentication methods must be configured for the
- server's user name, not the client's. Because
- <literal>md5</literal> uses the user name as salt on both the
- client and server, <literal>md5</literal> cannot be used with
- <varname>db_user_namespace</varname>.
- </para>
-
- <note>
- <para>
- This feature is intended as a temporary measure until a
- complete solution is found. At that time, this option will
- be removed.
- </para>
- </note>
- </listitem>
- </varlistentry>
-
</variablelist>
</sect2>
</sect1>
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 72f6be329e..28f17e3e5f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -573,8 +573,10 @@ const char *const config_group_names[] =
gettext_noop("Connections and Authentication"),
/* CONN_AUTH_SETTINGS */
gettext_noop("Connections and Authentication / Connection Settings"),
- /* CONN_AUTH_SECURITY */
- gettext_noop("Connections and Authentication / Security and Authentication"),
+ /* CONN_AUTH_AUTH */
+ gettext_noop("Connections and Authentication / Authentication"),
+ /* CONN_AUTH_SSL */
+ gettext_noop("Connections and Authentication / SSL"),
/* RESOURCES */
gettext_noop("Resource Usage"),
/* RESOURCES_MEM */
@@ -978,7 +980,7 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
- {"ssl", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"ssl", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Enables SSL connections."),
NULL
},
@@ -987,7 +989,7 @@ static struct config_bool ConfigureNamesBool[] =
check_ssl, NULL, NULL
},
{
- {"ssl_prefer_server_ciphers", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"ssl_prefer_server_ciphers", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Give priority to server ciphersuite order."),
NULL
},
@@ -1378,7 +1380,7 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
- {"db_user_namespace", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"db_user_namespace", PGC_SIGHUP, CONN_AUTH_AUTH,
gettext_noop("Enables per-database user names."),
NULL
},
@@ -1425,7 +1427,7 @@ static struct config_bool ConfigureNamesBool[] =
check_transaction_deferrable, NULL, NULL
},
{
- {"row_security", PGC_USERSET, CONN_AUTH_SECURITY,
+ {"row_security", PGC_USERSET, CLIENT_CONN_STATEMENT,
gettext_noop("Enable row security."),
gettext_noop("When enabled, row security will be applied to all users.")
},
@@ -1548,7 +1550,7 @@ static struct config_bool ConfigureNamesBool[] =
},
{
- {"krb_caseins_users", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"krb_caseins_users", PGC_SIGHUP, CONN_AUTH_AUTH,
gettext_noop("Sets whether Kerberos and GSSAPI user names should be treated as case-insensitive."),
NULL
},
@@ -2247,7 +2249,7 @@ static struct config_int ConfigureNamesInt[] =
},
{
- {"authentication_timeout", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"authentication_timeout", PGC_SIGHUP, CONN_AUTH_AUTH,
gettext_noop("Sets the maximum allowed time to complete client authentication."),
NULL,
GUC_UNIT_S
@@ -2797,7 +2799,7 @@ static struct config_int ConfigureNamesInt[] =
},
{
- {"ssl_renegotiation_limit", PGC_USERSET, CONN_AUTH_SECURITY,
+ {"ssl_renegotiation_limit", PGC_USERSET, CONN_AUTH_SSL,
gettext_noop("SSL renegotiation is no longer supported; this can only be 0."),
NULL,
GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE,
@@ -3170,7 +3172,7 @@ static struct config_string ConfigureNamesString[] =
},
{
- {"krb_server_keyfile", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"krb_server_keyfile", PGC_SIGHUP, CONN_AUTH_AUTH,
gettext_noop("Sets the location of the Kerberos server key file."),
NULL,
GUC_SUPERUSER_ONLY
@@ -3530,7 +3532,7 @@ static struct config_string ConfigureNamesString[] =
},
{
- {"ssl_cert_file", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"ssl_cert_file", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Location of the SSL server certificate file."),
NULL
},
@@ -3540,7 +3542,7 @@ static struct config_string ConfigureNamesString[] =
},
{
- {"ssl_key_file", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"ssl_key_file", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Location of the SSL server private key file."),
NULL
},
@@ -3550,7 +3552,7 @@ static struct config_string ConfigureNamesString[] =
},
{
- {"ssl_ca_file", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"ssl_ca_file", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Location of the SSL certificate authority file."),
NULL
},
@@ -3560,7 +3562,7 @@ static struct config_string ConfigureNamesString[] =
},
{
- {"ssl_crl_file", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"ssl_crl_file", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Location of the SSL certificate revocation list file."),
NULL
},
@@ -3602,7 +3604,7 @@ static struct config_string ConfigureNamesString[] =
},
{
- {"ssl_ciphers", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"ssl_ciphers", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Sets the list of allowed SSL ciphers."),
NULL,
GUC_SUPERUSER_ONLY
@@ -3617,7 +3619,7 @@ static struct config_string ConfigureNamesString[] =
},
{
- {"ssl_ecdh_curve", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"ssl_ecdh_curve", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Sets the curve to use for ECDH."),
NULL,
GUC_SUPERUSER_ONLY
@@ -3632,7 +3634,7 @@ static struct config_string ConfigureNamesString[] =
},
{
- {"ssl_dh_params_file", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ {"ssl_dh_params_file", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Location of the SSL DH parameters file."),
NULL,
GUC_SUPERUSER_ONLY
@@ -3932,7 +3934,7 @@ static struct config_enum ConfigureNamesEnum[] =
},
{
- {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
+ {"password_encryption", PGC_USERSET, CONN_AUTH_AUTH,
gettext_noop("Encrypt passwords."),
gettext_noop("When a password is specified in CREATE USER or "
"ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index 04de6a383a..668d9efd35 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -56,7 +56,8 @@ enum config_group
FILE_LOCATIONS,
CONN_AUTH,
CONN_AUTH_SETTINGS,
- CONN_AUTH_SECURITY,
+ CONN_AUTH_AUTH,
+ CONN_AUTH_SSL,
RESOURCES,
RESOURCES_MEM,
RESOURCES_DISK,
--
2.15.1
0003-Move-EDH-support-to-common-files.patchtext/plain; charset=UTF-8; name=0003-Move-EDH-support-to-common-files.patch; x-mac-creator=0; x-mac-type=0Download
From e69297ca31c532077d6c7f0e34338e8d1432cb78 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 19 Jan 2018 12:18:42 -0500
Subject: [PATCH 3/5] Move EDH support to common files
The EDH support is not really specific to the OpenSSL implementation, so
move the support and documentation comments to common files.
---
src/backend/libpq/README.SSL | 22 +++++++++++++
src/backend/libpq/be-secure-openssl.c | 58 +----------------------------------
src/include/libpq/libpq-be.h | 19 ++++++++++++
3 files changed, 42 insertions(+), 57 deletions(-)
diff --git a/src/backend/libpq/README.SSL b/src/backend/libpq/README.SSL
index 53dc9dd005..d84a434a6e 100644
--- a/src/backend/libpq/README.SSL
+++ b/src/backend/libpq/README.SSL
@@ -58,3 +58,25 @@ SSL
Fail with unknown
---------------------------------------------------------------------------
+
+Ephemeral DH
+============
+
+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 (see initialize_dh()).
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index fc6e8a0a88..450a2f614c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -11,28 +11,6 @@
* IDENTIFICATION
* src/backend/libpq/be-secure-openssl.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 (see initialize_dh()).
- *
*-------------------------------------------------------------------------
*/
@@ -87,40 +65,6 @@ static SSL_CTX *SSL_context = NULL;
static bool SSL_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.
- *
- * 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 */
@@ -1080,7 +1024,7 @@ initialize_dh(SSL_CTX *context, bool isServerStart)
if (ssl_dh_params_file[0])
dh = load_dh_file(ssl_dh_params_file, isServerStart);
if (!dh)
- dh = load_dh_buffer(file_dh2048, sizeof file_dh2048);
+ dh = load_dh_buffer(FILE_DH2048, sizeof(FILE_DH2048));
if (!dh)
{
ereport(isServerStart ? FATAL : LOG,
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 49cb263110..a38849b0d0 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -193,6 +193,25 @@ typedef struct Port
} Port;
#ifdef USE_SSL
+/*
+ * Hardcoded DH parameters, used in ephemeral DH keying. (See also
+ * README.SSL for more details on EDH.)
+ *
+ * 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.
+ */
+#define 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"
+
/*
* These functions are implemented by the glue code specific to each
* SSL implementation (e.g. be-secure-openssl.c)
--
2.15.1
0004-Move-SSL-API-comments-to-header-files.patchtext/plain; charset=UTF-8; name=0004-Move-SSL-API-comments-to-header-files.patch; x-mac-creator=0; x-mac-type=0Download
From 7255b557fd14713574cf646718eacdaffb7328ed Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 18 Jan 2018 19:53:22 -0500
Subject: [PATCH 4/5] Move SSL API comments to header files
Move the documentation of the SSL API calls are supposed to do into the
headers files, instead of keeping them in the files for the OpenSSL
implementation. That way, they don't have to be duplicated or be
inconsistent when other implementations are added.
---
src/backend/libpq/be-secure-openssl.c | 38 --------------------
src/include/libpq/libpq-be.h | 46 ++++++++++++++++++++++++
src/interfaces/libpq/fe-secure-openssl.c | 57 ++++-------------------------
src/interfaces/libpq/libpq-int.h | 62 +++++++++++++++++++++++++++++++-
4 files changed, 113 insertions(+), 90 deletions(-)
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 450a2f614c..f550ec82a9 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -70,13 +70,6 @@ static bool ssl_passwd_cb_called = false;
/* Public interface */
/* ------------------------------------------------------------ */
-/*
- * Initialize global SSL context.
- *
- * 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)
{
@@ -356,9 +349,6 @@ be_tls_init(bool isServerStart)
return -1;
}
-/*
- * Destroy global SSL context, if any.
- */
void
be_tls_destroy(void)
{
@@ -368,9 +358,6 @@ be_tls_destroy(void)
ssl_loaded_verify_locations = false;
}
-/*
- * Attempt to negotiate SSL connection.
- */
int
be_tls_open_server(Port *port)
{
@@ -539,9 +526,6 @@ be_tls_open_server(Port *port)
return 0;
}
-/*
- * Close SSL connection.
- */
void
be_tls_close(Port *port)
{
@@ -566,9 +550,6 @@ be_tls_close(Port *port)
}
}
-/*
- * Read data from a secure connection.
- */
ssize_t
be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
{
@@ -628,9 +609,6 @@ be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
return n;
}
-/*
- * Write data to a secure connection.
- */
ssize_t
be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
{
@@ -1106,9 +1084,6 @@ SSLerrmessage(unsigned long ecode)
return errbuf;
}
-/*
- * Return information about the SSL connection
- */
int
be_tls_get_cipher_bits(Port *port)
{
@@ -1159,12 +1134,6 @@ be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
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)
{
@@ -1183,13 +1152,6 @@ be_tls_get_peer_finished(Port *port, size_t *len)
return result;
}
-/*
- * Get the server certificate hash for SCRAM channel binding type
- * tls-server-end-point.
- *
- * The result is a palloc'd hash of the server certificate with its
- * size, and NULL if there is no certificate available.
- */
char *
be_tls_get_certificate_hash(Port *port, size_t *len)
{
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index a38849b0d0..584f794b9e 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -216,19 +216,65 @@ CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
* These functions are implemented by the glue code specific to each
* SSL implementation (e.g. be-secure-openssl.c)
*/
+
+/*
+ * Initialize global SSL context.
+ *
+ * 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.
+ */
extern int be_tls_init(bool isServerStart);
+
+/*
+ * Destroy global SSL context, if any.
+ */
extern void be_tls_destroy(void);
+
+/*
+ * Attempt to negotiate SSL connection.
+ */
extern int be_tls_open_server(Port *port);
+
+/*
+ * Close SSL connection.
+ */
extern void be_tls_close(Port *port);
+
+/*
+ * Read data from a secure connection.
+ */
extern ssize_t be_tls_read(Port *port, void *ptr, size_t len, int *waitfor);
+
+/*
+ * Write data to a secure connection.
+ */
extern ssize_t be_tls_write(Port *port, void *ptr, size_t len, int *waitfor);
+/*
+ * Return information about the SSL connection.
+ */
extern int be_tls_get_cipher_bits(Port *port);
extern bool be_tls_get_compression(Port *port);
extern void be_tls_get_version(Port *port, char *ptr, size_t len);
extern void be_tls_get_cipher(Port *port, char *ptr, size_t len);
extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);
+
+/*
+ * 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.
+ */
extern char *be_tls_get_peer_finished(Port *port, size_t *len);
+
+/*
+ * Get the server certificate hash for SCRAM channel binding type
+ * tls-server-end-point.
+ *
+ * The result is a palloc'd hash of the server certificate with its
+ * size, and NULL if there is no certificate available.
+ */
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index b50bfd144a..eb13120941 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -98,10 +98,6 @@ static long win32_ssl_create_mutex = 0;
/* Procedures common to all secure sessions */
/* ------------------------------------------------------------ */
-/*
- * Exported function to allow application to tell us it's already
- * initialized OpenSSL and/or libcrypto.
- */
void
pgtls_init_library(bool do_ssl, int do_crypto)
{
@@ -119,9 +115,6 @@ pgtls_init_library(bool do_ssl, int do_crypto)
pq_init_crypto_lib = do_crypto;
}
-/*
- * Begin or continue negotiating a secure session.
- */
PostgresPollingStatusType
pgtls_open_client(PGconn *conn)
{
@@ -144,22 +137,6 @@ pgtls_open_client(PGconn *conn)
return open_client_SSL(conn);
}
-/*
- * Is there unread data waiting in the SSL read buffer?
- */
-bool
-pgtls_read_pending(PGconn *conn)
-{
- return SSL_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)
{
@@ -284,13 +261,12 @@ pgtls_read(PGconn *conn, void *ptr, size_t len)
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.
- */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return SSL_pending(conn->ssl);
+}
+
ssize_t
pgtls_write(PGconn *conn, const void *ptr, size_t len)
{
@@ -393,12 +369,6 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
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)
{
@@ -419,13 +389,6 @@ pgtls_get_finished(PGconn *conn, size_t *len)
return result;
}
-/*
- * Get the hash of the server certificate, for SCRAM channel binding type
- * tls-server-end-point.
- *
- * NULL is sent back to the caller in the event of an error, with an
- * error message for the caller to consume.
- */
char *
pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
{
@@ -854,11 +817,6 @@ pq_lockingcallback(int mode, int n, const char *file, int line)
* 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)
@@ -1493,9 +1451,6 @@ open_client_SSL(PGconn *conn)
return PGRES_POLLING_OK;
}
-/*
- * Close SSL connection.
- */
void
pgtls_close(PGconn *conn)
{
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4e354098b3..b3492b033a 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -661,19 +661,79 @@ extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending,
bool got_epipe);
#endif
+/* === SSL === */
+
/*
- * The SSL implementation provides these functions (fe-secure-openssl.c)
+ * The SSL implementation provides these functions.
+ */
+
+/*
+ * Implementation of PQinitSSL().
*/
extern void pgtls_init_library(bool do_ssl, int do_crypto);
+
+/*
+ * Initialize SSL library.
+ *
+ * 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).
+ */
extern int pgtls_init(PGconn *conn);
+
+/*
+ * Begin or continue negotiating a secure session.
+ */
extern PostgresPollingStatusType pgtls_open_client(PGconn *conn);
+
+/*
+ * Close SSL connection.
+ */
extern void pgtls_close(PGconn *conn);
+
+/*
+ * 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.
+ */
extern ssize_t pgtls_read(PGconn *conn, void *ptr, size_t len);
+
+/*
+ * Is there unread data waiting in the SSL read buffer?
+ */
extern bool pgtls_read_pending(PGconn *conn);
+
+/*
+ * 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.
+ */
extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
+
+/*
+ * Get the TLS finish message sent during last handshake.
+ *
+ * This information is useful for callers doing channel binding during
+ * authentication.
+ */
extern char *pgtls_get_finished(PGconn *conn, size_t *len);
+
+/*
+ * Get the hash of the server certificate, for SCRAM channel binding type
+ * tls-server-end-point.
+ *
+ * NULL is sent back to the caller in the event of an error, with an
+ * error message for the caller to consume.
+ */
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
+/* === miscellaneous macros === */
+
/*
* this is so that we can check if a connection is non-blocking internally
* without the overhead of a function call
--
2.15.1
0005-Extract-common-bits-from-OpenSSL-implementation.patchtext/plain; charset=UTF-8; name=0005-Extract-common-bits-from-OpenSSL-implementation.patch; x-mac-creator=0; x-mac-type=0Download
From 4ce82a167b3a7bc4b0f00f8794441d4d7bcb9edd Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 19 Jan 2018 10:17:56 -0500
Subject: [PATCH 5/5] Extract common bits from OpenSSL implementation
Some things in be-secure-openssl.c and fe-secure-openssl.c were not
actually specific to OpenSSL but could also be used by other
implementations. In order to avoid copy-and-pasting, move some of that
code to common files.
---
src/backend/libpq/be-secure-openssl.c | 62 +---------------------------
src/backend/libpq/be-secure.c | 71 ++++++++++++++++++++++++++++++++
src/include/libpq/libpq.h | 1 +
src/interfaces/libpq/fe-secure-openssl.c | 8 ----
src/interfaces/libpq/fe-secure.c | 14 ++++---
5 files changed, 81 insertions(+), 75 deletions(-)
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index f550ec82a9..02601da6c8 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -75,7 +75,6 @@ be_tls_init(bool isServerStart)
{
STACK_OF(X509_NAME) *root_cert_list = NULL;
SSL_CTX *context;
- struct stat buf;
/* This stuff need be done only once. */
if (!SSL_initialized)
@@ -133,63 +132,8 @@ be_tls_init(bool isServerStart)
goto error;
}
- 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)));
+ if (!check_ssl_key_file_permissions(ssl_key_file, isServerStart))
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.
@@ -516,10 +460,6 @@ be_tls_open_server(Port *port)
port->peer_cert_valid = true;
}
- ereport(DEBUG2,
- (errmsg("SSL connection from \"%s\"",
- port->peer_cn ? port->peer_cn : "(anonymous)")));
-
/* set up debugging/info callback */
SSL_CTX_set_info_callback(SSL_context, info_cb);
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index eb42ea1a1e..76c0a9e39b 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -114,6 +114,10 @@ secure_open_server(Port *port)
#ifdef USE_SSL
r = be_tls_open_server(port);
+
+ ereport(DEBUG2,
+ (errmsg("SSL connection from \"%s\"",
+ port->peer_cn ? port->peer_cn : "(anonymous)")));
#endif
return r;
@@ -314,3 +318,70 @@ secure_raw_write(Port *port, const void *ptr, size_t len)
return n;
}
+
+bool
+check_ssl_key_file_permissions(const char *ssl_key_file, bool isServerStart)
+{
+ int loglevel = isServerStart ? FATAL : LOG;
+ struct stat buf;
+
+ if (stat(ssl_key_file, &buf) != 0)
+ {
+ ereport(loglevel,
+ (errcode_for_file_access(),
+ errmsg("could not access private key file \"%s\": %m",
+ ssl_key_file)));
+ return false;
+ }
+
+ if (!S_ISREG(buf.st_mode))
+ {
+ ereport(loglevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" is not a regular file",
+ ssl_key_file)));
+ return false;
+ }
+
+ /*
+ * 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(loglevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" must be owned by the database user or root",
+ ssl_key_file)));
+ return false;
+ }
+#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(loglevel,
+ (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.")));
+ return false;
+ }
+#endif
+
+ return true;
+}
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 2e7725db21..255222acd7 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -90,6 +90,7 @@ extern ssize_t secure_read(Port *port, void *ptr, size_t len);
extern ssize_t secure_write(Port *port, void *ptr, size_t len);
extern ssize_t secure_raw_read(Port *port, void *ptr, size_t len);
extern ssize_t secure_raw_write(Port *port, const void *ptr, size_t len);
+extern bool check_ssl_key_file_permissions(const char *ssl_key_file, bool isServerStart);
extern bool ssl_loaded_verify_locations;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index eb13120941..9ab317320a 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -1547,14 +1547,6 @@ SSLerrfree(char *buf)
/* SSL information functions */
/* ------------------------------------------------------------ */
-int
-PQsslInUse(PGconn *conn)
-{
- if (!conn)
- return 0;
- return conn->ssl_in_use;
-}
-
/*
* Return pointer to OpenSSL object.
*/
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index ec6c65a4b4..cfb77f6d85 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -129,6 +129,14 @@ struct sigpipe_info
/* ------------------------------------------------------------ */
+int
+PQsslInUse(PGconn *conn)
+{
+ if (!conn)
+ return 0;
+ return conn->ssl_in_use;
+}
+
/*
* Exported function to allow application to tell us it's already
* initialized OpenSSL.
@@ -384,12 +392,6 @@ pqsecure_raw_write(PGconn *conn, const void *ptr, size_t len)
/* Dummy versions of SSL info functions, when built without SSL support */
#ifndef USE_SSL
-int
-PQsslInUse(PGconn *conn)
-{
- return 0;
-}
-
void *
PQgetssl(PGconn *conn)
{
--
2.15.1
On Wed, Jan 17, 2018 at 10:02 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Also, this isn't really a good argument against using uniform names
for parameters that every implementation is certain to have, like
ssl_key_file.
Even then, it's not that hard to imagine minor variations between what
different implementations will accept. The most obvious difference is
probably that they might expect different file formats, but it's also
possible that a Windows-specific implementation might allow omitting
the file extension while some other implementation does not, for
example. I agree that it would probably be fairly low-risk to use one
parameter for the key file for every implementation, but I suggest
that it would be cleaner and less prone to confusion if we enforce a
full separation of parameters. That also spares us having to make a
judgement call about which parameters have semantics close enough that
we need not separate them.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 19 Jan 2018, at 19:43, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
0003-Move-EDH-support-to-common-files.patch
To avoid copy-and-paste, and also because the EDH explanation doesn't
really belong in a file header comment. Maybe the whole thing is known
well enough nowadays that we can just remove the explanation.
Perhaps, I still think it’s a good idea to keep it in README.SSL
0004-Move-SSL-API-comments-to-header-files.patch
0005-Extract-common-bits-from-OpenSSL-implementation.patchMove copy-and-paste avoidance.
+1 on doing this type of refactoring before anything else lands, when
implementing ST the excessive copy-paste was annoying (but avoided for
scope-creep).
cheers ./daniel
On 1/19/18 13:43, Peter Eisentraut wrote:
Comparing the existing {be,fe}-secure-openssl.c with the proposed
{be,fe}-secure-gnutls.c, and with half an eye on the previously proposed
Apple Secure Transport implementation, I have identified a few more
areas of refactoring that should be done in order to avoid excessive
copy-and-pasting in the new implementations:
And here is another place that needs cleaning up, where the OpenSSL API
was used directly.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
0001-Use-abstracted-SSL-API-in-server-connection-log-mess.patchtext/plain; charset=UTF-8; name=0001-Use-abstracted-SSL-API-in-server-connection-log-mess.patch; x-mac-creator=0; x-mac-type=0Download
From 5d8066201dd1152edd2bbe8ba62ea58f378d1dd0 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 25 Jan 2018 08:58:00 -0500
Subject: [PATCH] Use abstracted SSL API in server connection log messages
The existing "connection authorized" server log messages used OpenSSL
API calls directly, even though similar abstracted API calls exist.
Change to use the latter instead.
Change the function prototype for the functions that return the TLS
version and the cipher to return const char * directly instead of
copying into a buffer. That makes them slightly easier to use.
Add bits= to the message. psql shows that, so we might as well show the
same information on the client and server.
---
src/backend/libpq/be-secure-openssl.c | 16 ++++++++--------
src/backend/postmaster/pgstat.c | 4 ++--
src/backend/utils/init/postinit.c | 22 ++++++++++++++--------
src/include/libpq/libpq-be.h | 4 ++--
4 files changed, 26 insertions(+), 20 deletions(-)
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 02601da6c8..e1ddfb3c16 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1047,22 +1047,22 @@ be_tls_get_compression(Port *port)
return false;
}
-void
-be_tls_get_version(Port *port, char *ptr, size_t len)
+const char *
+be_tls_get_version(Port *port)
{
if (port->ssl)
- strlcpy(ptr, SSL_get_version(port->ssl), len);
+ return SSL_get_version(port->ssl);
else
- ptr[0] = '\0';
+ return NULL;
}
-void
-be_tls_get_cipher(Port *port, char *ptr, size_t len)
+const char *
+be_tls_get_cipher(Port *port)
{
if (port->ssl)
- strlcpy(ptr, SSL_get_cipher(port->ssl), len);
+ return SSL_get_cipher(port->ssl);
else
- ptr[0] = '\0';
+ return NULL;
}
void
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index d13011454c..605b1832be 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2909,8 +2909,8 @@ pgstat_bestart(void)
beentry->st_ssl = true;
beentry->st_sslstatus->ssl_bits = be_tls_get_cipher_bits(MyProcPort);
beentry->st_sslstatus->ssl_compression = be_tls_get_compression(MyProcPort);
- be_tls_get_version(MyProcPort, beentry->st_sslstatus->ssl_version, NAMEDATALEN);
- be_tls_get_cipher(MyProcPort, beentry->st_sslstatus->ssl_cipher, NAMEDATALEN);
+ strlcpy(beentry->st_sslstatus->ssl_version, be_tls_get_version(MyProcPort), NAMEDATALEN);
+ strlcpy(beentry->st_sslstatus->ssl_cipher, be_tls_get_cipher(MyProcPort), NAMEDATALEN);
be_tls_get_peerdn_name(MyProcPort, beentry->st_sslstatus->ssl_clientdn, NAMEDATALEN);
}
else
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index f9b330998d..484628987f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -246,12 +246,15 @@ PerformAuthentication(Port *port)
{
if (am_walsender)
{
-#ifdef USE_OPENSSL
+#ifdef USE_SSL
if (port->ssl_in_use)
ereport(LOG,
- (errmsg("replication connection authorized: user=%s SSL enabled (protocol=%s, cipher=%s, compression=%s)",
- port->user_name, SSL_get_version(port->ssl), SSL_get_cipher(port->ssl),
- SSL_get_current_compression(port->ssl) ? _("on") : _("off"))));
+ (errmsg("replication connection authorized: user=%s SSL enabled (protocol=%s, cipher=%s, bits=%d, compression=%s)",
+ port->user_name,
+ be_tls_get_version(port),
+ be_tls_get_cipher(port),
+ be_tls_get_cipher_bits(port),
+ be_tls_get_compression(port) ? _("on") : _("off"))));
else
#endif
ereport(LOG,
@@ -260,12 +263,15 @@ PerformAuthentication(Port *port)
}
else
{
-#ifdef USE_OPENSSL
+#ifdef USE_SSL
if (port->ssl_in_use)
ereport(LOG,
- (errmsg("connection authorized: user=%s database=%s SSL enabled (protocol=%s, cipher=%s, compression=%s)",
- port->user_name, port->database_name, SSL_get_version(port->ssl), SSL_get_cipher(port->ssl),
- SSL_get_current_compression(port->ssl) ? _("on") : _("off"))));
+ (errmsg("connection authorized: user=%s database=%s SSL enabled (protocol=%s, cipher=%s, bits=%d, compression=%s)",
+ port->user_name, port->database_name,
+ be_tls_get_version(port),
+ be_tls_get_cipher(port),
+ be_tls_get_cipher_bits(port),
+ be_tls_get_compression(port) ? _("on") : _("off"))));
else
#endif
ereport(LOG,
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 584f794b9e..7698cd1f88 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -256,8 +256,8 @@ extern ssize_t be_tls_write(Port *port, void *ptr, size_t len, int *waitfor);
*/
extern int be_tls_get_cipher_bits(Port *port);
extern bool be_tls_get_compression(Port *port);
-extern void be_tls_get_version(Port *port, char *ptr, size_t len);
-extern void be_tls_get_cipher(Port *port, char *ptr, size_t len);
+extern const char *be_tls_get_version(Port *port);
+extern const char *be_tls_get_cipher(Port *port);
extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);
/*
--
2.16.1
On 25 Jan 2018, at 15:07, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
On 1/19/18 13:43, Peter Eisentraut wrote:
Comparing the existing {be,fe}-secure-openssl.c with the proposed
{be,fe}-secure-gnutls.c, and with half an eye on the previously proposed
Apple Secure Transport implementation, I have identified a few more
areas of refactoring that should be done in order to avoid excessive
copy-and-pasting in the new implementations:And here is another place that needs cleaning up, where the OpenSSL API
was used directly.
+1 on these cleanups.
Regarding this hunk:
extern int be_tls_get_cipher_bits(Port *port);
extern bool be_tls_get_compression(Port *port);
-extern void be_tls_get_version(Port *port, char *ptr, size_t len);
-extern void be_tls_get_cipher(Port *port, char *ptr, size_t len);
+extern const char *be_tls_get_version(Port *port);
+extern const char *be_tls_get_cipher(Port *port);
extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);
While only tangentially related to the issue this patch solves, converting
be_tls_get_peerdn_name() to return const char * seems reasonable too to keep
the API consistent.
cheers ./daniel
On Fri, Jan 19, 2018 at 01:55:30PM -0500, Robert Haas wrote:
On Wed, Jan 17, 2018 at 10:02 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Also, this isn't really a good argument against using uniform names
for parameters that every implementation is certain to have, like
ssl_key_file.Even then, it's not that hard to imagine minor variations between what
different implementations will accept. The most obvious difference is
probably that they might expect different file formats, but it's also
possible that a Windows-specific implementation might allow omitting
the file extension while some other implementation does not, for
example. I agree that it would probably be fairly low-risk to use one
parameter for the key file for every implementation, but I suggest
that it would be cleaner and less prone to confusion if we enforce a
full separation of parameters. That also spares us having to make a
judgement call about which parameters have semantics close enough that
we need not separate them.
Having to debate about the most correct default values for one common
parameter depending on N SSL implemententations will likely prove at the
end that the most correct answer is just to split all parameters from
the start. Some implementations are platform-specific, the format file
is one thing that matters. There are also other things like ssl_ciphers
which could depend on what kind of cipher types an SSL implementation is
able to support... Another thing is that we cannot be 100% sure that one
parameter can be set to say GUC_SIGHUP for an SSL implementation and
needs to have other modes with another implementations. Of course this
can be solved if some ifdefs, but splitting brings clarity.
At the end, I agree with the position of Robert to split everything and
rename the existing ssl_* parameters to openssl_* if v11 gains a new SSL
implementation.
--
Michael
On Fri, Jan 26, 2018 at 12:27:16AM +0100, Daniel Gustafsson wrote:
On 25 Jan 2018, at 15:07, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
On 1/19/18 13:43, Peter Eisentraut wrote:
Comparing the existing {be,fe}-secure-openssl.c with the proposed
{be,fe}-secure-gnutls.c, and with half an eye on the previously proposed
Apple Secure Transport implementation, I have identified a few more
areas of refactoring that should be done in order to avoid excessive
copy-and-pasting in the new implementations:And here is another place that needs cleaning up, where the OpenSSL API
was used directly.+1 on these cleanups.
Peter, could you change ssl_version() and ssl_cipher() in sslinfo at the
same time please? I think that those should use the generic backend-side
APIs as well. sslinfo depends heavily on OpenSSL, OK, but if possible
getting this code more generic will help users of sslinfo to get
something partially working with other SSL implementations natively.
Regarding this hunk:
extern int be_tls_get_cipher_bits(Port *port); extern bool be_tls_get_compression(Port *port); -extern void be_tls_get_version(Port *port, char *ptr, size_t len); -extern void be_tls_get_cipher(Port *port, char *ptr, size_t len); +extern const char *be_tls_get_version(Port *port); +extern const char *be_tls_get_cipher(Port *port); extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);While only tangentially related to the issue this patch solves, converting
be_tls_get_peerdn_name() to return const char * seems reasonable too to keep
the API consistent.
Why? This is not used for error message generation yet. We could always
change the API as needed later on.
--
Michael
On 1/25/18 20:10, Michael Paquier wrote:
Peter, could you change ssl_version() and ssl_cipher() in sslinfo at the
same time please? I think that those should use the generic backend-side
APIs as well. sslinfo depends heavily on OpenSSL, OK, but if possible
getting this code more generic will help users of sslinfo to get
something partially working with other SSL implementations natively.
sslinfo is currently entirely dependent on OpenSSL, so I don't think
it's useful to throw in one or two isolated API changes.
I'm thinking maybe we should get rid of sslinfo and fold everything into
pg_stat_ssl.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 26 Jan 2018, at 02:10, Michael Paquier <michael.paquier@gmail.com> wrote:
On Fri, Jan 26, 2018 at 12:27:16AM +0100, Daniel Gustafsson wrote:
While only tangentially related to the issue this patch solves, converting
be_tls_get_peerdn_name() to return const char * seems reasonable too to keep
the API consistent.Why? This is not used for error message generation yet. We could always
change the API as needed later on.
Mainly to keep the be_tls_* interface consistent in how the routines return
data. I don’t have any strong opinions though, it just seemed like a good time
to do it.
cheers ./daniel
On 1/25/18 09:07, Peter Eisentraut wrote:
On 1/19/18 13:43, Peter Eisentraut wrote:
Comparing the existing {be,fe}-secure-openssl.c with the proposed
{be,fe}-secure-gnutls.c, and with half an eye on the previously proposed
Apple Secure Transport implementation, I have identified a few more
areas of refactoring that should be done in order to avoid excessive
copy-and-pasting in the new implementations:And here is another place that needs cleaning up, where the OpenSSL API
was used directly.
And here is another one. The last one for now I think.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
0001-Refactor-client-side-SSL-certificate-checking-code.patchtext/plain; charset=UTF-8; name=0001-Refactor-client-side-SSL-certificate-checking-code.patch; x-mac-creator=0; x-mac-type=0Download
From ab0bd8f655a5c75c8040b462a9d2c0111fbf323a Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Sat, 27 Jan 2018 13:47:52 -0500
Subject: [PATCH] Refactor client-side SSL certificate checking code
Separate the parts specific to the SSL library from the general logic.
The previous code structure was
open_client_SSL()
calls verify_peer_name_matches_certificate()
calls verify_peer_name_matches_certificate_name()
calls wildcard_certificate_match()
and was completely in fe-secure-openssl.c. The new structure is
open_client_SSL() [openssl]
calls pq_verify_peer_name_matches_certificate() [generic]
calls pgtls_verify_peer_name_matches_certificate_guts() [openssl]
calls openssl_verify_peer_name_matches_certificate_name() [openssl]
calls pq_verify_peer_name_matches_certificate_name() [generic]
calls wildcard_certificate_match() [generic]
---
src/interfaces/libpq/fe-secure-openssl.c | 209 ++++---------------------------
src/interfaces/libpq/fe-secure.c | 185 ++++++++++++++++++++++++++-
src/interfaces/libpq/libpq-int.h | 20 +++
3 files changed, 228 insertions(+), 186 deletions(-)
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 9ab317320a..228ea5897a 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -60,9 +60,8 @@
#endif
#include <openssl/x509v3.h>
-static bool verify_peer_name_matches_certificate(PGconn *);
static int verify_cb(int ok, X509_STORE_CTX *ctx);
-static int verify_peer_name_matches_certificate_name(PGconn *conn,
+static int openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
ASN1_STRING *name,
char **store_name);
static void destroy_ssl_system(void);
@@ -492,76 +491,16 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
/*
- * 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.
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_name(), converting the ASN1_STRING
+ * into a plain C string.
*/
static int
-verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
- char **store_name)
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+ char **store_name)
{
int len;
- char *name;
const unsigned char *namedata;
- int result;
- char *host = PQhost(conn);
-
- *store_name = NULL;
/* Should not happen... */
if (name_entry == NULL)
@@ -573,9 +512,6 @@ verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
/*
* GEN_DNS can be only IA5String, equivalent to US ASCII.
- *
- * There is no guarantee the string returned from the certificate is
- * NULL-terminated, so make a copy that is.
*/
#ifdef HAVE_ASN1_STRING_GET0_DATA
namedata = ASN1_STRING_get0_data(name_entry);
@@ -583,45 +519,9 @@ verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
namedata = ASN1_STRING_data(name_entry);
#endif
len = ASN1_STRING_length(name_entry);
- 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;
+ /* OK to cast from unsigned to plain char, since it's all ASCII. */
+ return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
}
/*
@@ -629,33 +529,14 @@ verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
*
* The certificate's Common Name and Subject Alternative Names are considered.
*/
-static bool
-verify_peer_name_matches_certificate(PGconn *conn)
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
{
- int names_examined = 0;
- bool found_match = false;
- bool got_error = false;
- char *first_name = NULL;
-
STACK_OF(GENERAL_NAME) *peer_san;
int i;
- int rc;
- char *host = PQhost(conn);
-
- /*
- * 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;
- }
+ int rc = 0;
/*
* First, get the Subject Alternative Names (SANs) from the certificate,
@@ -676,24 +557,20 @@ verify_peer_name_matches_certificate(PGconn *conn)
{
char *alt_name;
- names_examined++;
- rc = verify_peer_name_matches_certificate_name(conn,
+ (*names_examined)++;
+ rc = openssl_verify_peer_name_matches_certificate_name(conn,
name->d.dNSName,
&alt_name);
- if (rc == -1)
- got_error = true;
- if (rc == 1)
- found_match = true;
if (alt_name)
{
- if (!first_name)
- first_name = alt_name;
+ if (!*first_name)
+ *first_name = alt_name;
else
free(alt_name);
}
}
- if (found_match || got_error)
+ if (rc != 0)
break;
}
sk_GENERAL_NAME_free(peer_san);
@@ -706,7 +583,7 @@ verify_peer_name_matches_certificate(PGconn *conn)
* (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
* dNSName is present, the CN must be ignored.)
*/
- if (names_examined == 0)
+ if (*names_examined == 0)
{
X509_NAME *subject_name;
@@ -719,55 +596,17 @@ verify_peer_name_matches_certificate(PGconn *conn)
NID_commonName, -1);
if (cn_index >= 0)
{
- names_examined++;
- rc = verify_peer_name_matches_certificate_name(
+ (*names_examined)++;
+ rc = openssl_verify_peer_name_matches_certificate_name(
conn,
X509_NAME_ENTRY_get_data(
X509_NAME_get_entry(subject_name, cn_index)),
- &first_name);
-
- if (rc == -1)
- got_error = true;
- else if (rc == 1)
- found_match = true;
+ first_name);
}
}
}
- 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;
+ return rc;
}
#if defined(ENABLE_THREAD_SAFETY) && defined(HAVE_CRYPTO_LOCK)
@@ -1441,7 +1280,7 @@ open_client_SSL(PGconn *conn)
return PGRES_POLLING_FAILED;
}
- if (!verify_peer_name_matches_certificate(conn))
+ if (!pq_verify_peer_name_matches_certificate(conn))
{
pgtls_close(conn);
return PGRES_POLLING_FAILED;
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index cfb77f6d85..72bb8ba01c 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -389,7 +389,190 @@ pqsecure_raw_write(PGconn *conn, const void *ptr, size_t len)
return n;
}
-/* Dummy versions of SSL info functions, when built without SSL support */
+/*
+ * Common helper functions
+ */
+#ifdef USE_SSL
+/*
+ * 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 bool
+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 false;
+
+ /* If pattern is longer than the string, we can never match */
+ if (lenpat > lenstr)
+ return false;
+
+ /* If string does not end in pattern (minus the wildcard), we don't
+ * match */
+ if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)
+ return false;
+
+ /* If there is a dot left of where the pattern started to match, we
+ * don't match (rule 3) */
+ if (strchr(string, '.') < string + lenstr - lenpat)
+ return false;
+
+ /* String ended with pattern, and didn't have a dot before, so we match */
+ return true;
+}
+
+/*
+ * 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.
+ */
+int
+pq_verify_peer_name_matches_certificate_name(PGconn *conn,
+ const char *namedata, size_t namelen,
+ char **store_name)
+{
+ char *name;
+ int result;
+ char *host = PQhost(conn);
+
+ *store_name = NULL;
+
+ /*
+ * There is no guarantee the string returned from the certificate is
+ * NULL-terminated, so make a copy that is.
+ */
+ name = malloc(namelen + 1);
+ if (name == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return -1;
+ }
+ memcpy(name, namedata, namelen);
+ name[namelen] = '\0';
+
+ /*
+ * Reject embedded NULLs in certificate common or alternative name to
+ * prevent attacks like CVE-2009-4034.
+ */
+ if (namelen != 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;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+bool
+pq_verify_peer_name_matches_certificate(PGconn *conn)
+{
+ char *host = PQhost(conn);
+ int rc;
+ int names_examined = 0;
+ char *first_name = NULL;
+
+ /*
+ * 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;
+ }
+
+ rc = pgtls_verify_peer_name_matches_certificate_guts(conn, &names_examined, &first_name);
+
+ if (rc == 0)
+ {
+ /*
+ * 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 (rc == 1);
+}
+#endif /* USE_SSL */
+
+/*
+ * Dummy versions of SSL info functions, when built without SSL support
+ */
#ifndef USE_SSL
void *
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index b3492b033a..d511203c4e 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -661,6 +661,13 @@ extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending,
bool got_epipe);
#endif
+#ifdef USE_SSL
+extern int pq_verify_peer_name_matches_certificate_name(PGconn *conn,
+ const char *namedata, size_t namelen,
+ char **store_name);
+extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
+#endif
+
/* === SSL === */
/*
@@ -732,6 +739,19 @@ extern char *pgtls_get_finished(PGconn *conn, size_t *len);
*/
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
+/*
+ * Verify that the server certificate matches the host name we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ *
+ * Returns 1 if the name matches, and 0 if it does not. On error, returns
+ * -1, and sets the libpq error message.
+ *
+ */
+extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name);
+
/* === miscellaneous macros === */
/*
--
2.16.1
On Wed, Jan 17, 2018 at 10:02:35PM -0500, Tom Lane wrote:
That is a really good point. For precedent, note that darn near nobody
seems to know whether their psql contains readline or libedit. If we
force the issue by giving the settings different names, then they'll be
forced to figure out which SSL implementation they have.On the other hand, you could argue that there are more user-friendly
ways to expose that information than demanding that users play twenty
questions with their config files. I'd at least want us to recognize
when somebody tries to set "openssl_foo" in a gnutls implementation,
and respond with "you need to twiddle the gnutls_xxx variables instead"
rather than just "unrecognized configuration parameter". Maybe that'd
be good enough, though.
To open another can of worms, are we ever going to rename "ssl"
parameters to "tls" since TLS is the protocol used by all modern secure
communication libraries. SSL was deprecated in 2015:
https://www.globalsign.com/en/blog/ssl-vs-tls-difference/
Both SSL 2.0 and 3.0 have been deprecated by the IETF (in 2011
and 2015, respectively).
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ As you are, so once was I. As I am, so you will be. +
+ Ancient Roman grave inscription +
On Sat, Jan 27, 2018 at 05:00:17PM -0500, Peter Eisentraut wrote:
On 1/25/18 09:07, Peter Eisentraut wrote:
On 1/19/18 13:43, Peter Eisentraut wrote:
Comparing the existing {be,fe}-secure-openssl.c with the proposed
{be,fe}-secure-gnutls.c, and with half an eye on the previously proposed
Apple Secure Transport implementation, I have identified a few more
areas of refactoring that should be done in order to avoid excessive
copy-and-pasting in the new implementations:And here is another place that needs cleaning up, where the OpenSSL API
was used directly.And here is another one. The last one for now I think.
The comment at the top of PQinitSSL mentions OpenSSL, you may want to
get rid of it as well.
In short, this patch:
- moves wildcard_certificate_match from fe-secure-openssl.c to
fe-secure.c.
- splits verify_peer_name_matches_certificate into two, with one common
part as pq_verify_peer_name_matches_certificate and one part which is
SSL-specific as pgtls_verify_peer_name_matches_certificate_guts.
- splits verify_peer_name_matches_certificate_name into two, with one
common part as verify_peer_name_matches_certificate_name and one part
which is SSL-specific as openssl_verify_peer_name_matches_certificate_name().
To be honest, I find this refactoring confusing. As things stand on
HEAD, fe-secure.c depends on the contents of fe-secure-openssl.c, and
the dependency goes only in one direction. With your patch, you also
make fe-secure-openssl.c call things within fe-secure.c. It seems to me
that a cleaner split would be to introduce a common file for all the
low-level routines like the two ones you are introducing here, say
fe-secure-common.c. aND pq_verify_peer_name_matches_certificate_name and
pq_verify_peer_name_matches_certificate should be moved to that.
--
Michael
On 1/27/18 19:49, Bruce Momjian wrote:
To open another can of worms, are we ever going to rename "ssl"
parameters to "tls"
I had been thinking about that, too, but it doesn't seem worth it.
While renaming server-side settings might be feasible with some
annoyance, this would also include renaming libpq connection parameters
and APIs, which would be much more problematic.
I think most users actually still think about the whole topic as "SSL",
and the leading library is called "OpenSSL", so I think we're fine.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 1/28/18 23:43, Michael Paquier wrote:
The comment at the top of PQinitSSL mentions OpenSSL, you may want to
get rid of it as well.
I think that whole business is actually obsolete as of OpenSSL 1.1.0 and
not applicable to GnuTLS, so we might just leave it to die with some
appropriate documentation updates.
To be honest, I find this refactoring confusing. As things stand on
HEAD, fe-secure.c depends on the contents of fe-secure-openssl.c, and
the dependency goes only in one direction. With your patch, you also
make fe-secure-openssl.c call things within fe-secure.c. It seems to me
that a cleaner split would be to introduce a common file for all the
low-level routines like the two ones you are introducing here, say
fe-secure-common.c. aND pq_verify_peer_name_matches_certificate_name and
pq_verify_peer_name_matches_certificate should be moved to that.
I like that. Updated patch attached.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v2-0001-Refactor-client-side-SSL-certificate-checking-cod.patchtext/plain; charset=UTF-8; name=v2-0001-Refactor-client-side-SSL-certificate-checking-cod.patch; x-mac-creator=0; x-mac-type=0Download
From ac2310337e4749044ee4d65fe2b762295e3ba7af Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Sat, 27 Jan 2018 13:47:52 -0500
Subject: [PATCH v2] Refactor client-side SSL certificate checking code
Separate the parts specific to the SSL library from the general logic.
The previous code structure was
open_client_SSL()
calls verify_peer_name_matches_certificate()
calls verify_peer_name_matches_certificate_name()
calls wildcard_certificate_match()
and was completely in fe-secure-openssl.c. The new structure is
open_client_SSL() [openssl]
calls pq_verify_peer_name_matches_certificate() [generic]
calls pgtls_verify_peer_name_matches_certificate_guts() [openssl]
calls openssl_verify_peer_name_matches_certificate_name() [openssl]
calls pq_verify_peer_name_matches_certificate_name() [generic]
calls wildcard_certificate_match() [generic]
Move the generic functions into a new file fe-secure-common.c, so the
calls generally go fe-connect.c -> fe-secure.c -> fe-secure-${impl}.c ->
fe-secure-common.c, although there is a bit of back-and-forth between
the last two.
---
src/interfaces/libpq/Makefile | 2 +-
src/interfaces/libpq/fe-secure-common.c | 201 +++++++++++++++++++++++++++++
src/interfaces/libpq/fe-secure-common.h | 26 ++++
src/interfaces/libpq/fe-secure-openssl.c | 210 ++++---------------------------
src/interfaces/libpq/libpq-int.h | 13 ++
5 files changed, 266 insertions(+), 186 deletions(-)
create mode 100644 src/interfaces/libpq/fe-secure-common.c
create mode 100644 src/interfaces/libpq/fe-secure-common.h
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0bf1e7ef04..abe0a50e98 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -52,7 +52,7 @@ OBJS += encnames.o wchar.o
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
+OBJS += fe-secure-openssl.o fe-secure-common.o sha2_openssl.o
else
OBJS += sha2.o
endif
diff --git a/src/interfaces/libpq/fe-secure-common.c b/src/interfaces/libpq/fe-secure-common.c
new file mode 100644
index 0000000000..35c2eb23de
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-common.c
@@ -0,0 +1,201 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-common.c
+ *
+ * common implementation-independent SSL support code
+ *
+ * While fe-secure.c contains the interfaces that the rest of libpq call, this
+ * file contains support routines that are used by the library-specific
+ * implementations such as fe-secure-openssl.c.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "fe-secure-common.h"
+
+#include "libpq-int.h"
+#include "pqexpbuffer.h"
+
+/*
+ * 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 bool
+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 false;
+
+ /* If pattern is longer than the string, we can never match */
+ if (lenpat > lenstr)
+ return false;
+
+ /* If string does not end in pattern (minus the wildcard), we don't
+ * match */
+ if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)
+ return false;
+
+ /* If there is a dot left of where the pattern started to match, we
+ * don't match (rule 3) */
+ if (strchr(string, '.') < string + lenstr - lenpat)
+ return false;
+
+ /* String ended with pattern, and didn't have a dot before, so we match */
+ return true;
+}
+
+/*
+ * 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.
+ */
+int
+pq_verify_peer_name_matches_certificate_name(PGconn *conn,
+ const char *namedata, size_t namelen,
+ char **store_name)
+{
+ char *name;
+ int result;
+ char *host = PQhost(conn);
+
+ *store_name = NULL;
+
+ /*
+ * There is no guarantee the string returned from the certificate is
+ * NULL-terminated, so make a copy that is.
+ */
+ name = malloc(namelen + 1);
+ if (name == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return -1;
+ }
+ memcpy(name, namedata, namelen);
+ name[namelen] = '\0';
+
+ /*
+ * Reject embedded NULLs in certificate common or alternative name to
+ * prevent attacks like CVE-2009-4034.
+ */
+ if (namelen != 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;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+bool
+pq_verify_peer_name_matches_certificate(PGconn *conn)
+{
+ char *host = PQhost(conn);
+ int rc;
+ int names_examined = 0;
+ char *first_name = NULL;
+
+ /*
+ * 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;
+ }
+
+ rc = pgtls_verify_peer_name_matches_certificate_guts(conn, &names_examined, &first_name);
+
+ if (rc == 0)
+ {
+ /*
+ * 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 (rc == 1);
+}
diff --git a/src/interfaces/libpq/fe-secure-common.h b/src/interfaces/libpq/fe-secure-common.h
new file mode 100644
index 0000000000..836880fbfd
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-common.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-common.h
+ *
+ * common implementation-independent SSL support code
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FE_SECURE_COMMON_H
+#define FE_SECURE_COMMON_H
+
+#include "libpq-fe.h"
+
+extern int pq_verify_peer_name_matches_certificate_name(PGconn *conn,
+ const char *namedata, size_t namelen,
+ char **store_name);
+extern bool pq_verify_peer_name_matches_certificate(PGconn *conn);
+
+#endif /* FE_SECURE_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 9ab317320a..cade4e157c 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -28,6 +28,7 @@
#include "libpq-fe.h"
#include "fe-auth.h"
+#include "fe-secure-common.h"
#include "libpq-int.h"
#ifdef WIN32
@@ -60,9 +61,8 @@
#endif
#include <openssl/x509v3.h>
-static bool verify_peer_name_matches_certificate(PGconn *);
static int verify_cb(int ok, X509_STORE_CTX *ctx);
-static int verify_peer_name_matches_certificate_name(PGconn *conn,
+static int openssl_verify_peer_name_matches_certificate_name(PGconn *conn,
ASN1_STRING *name,
char **store_name);
static void destroy_ssl_system(void);
@@ -492,76 +492,16 @@ verify_cb(int ok, X509_STORE_CTX *ctx)
/*
- * 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.
+ * OpenSSL-specific wrapper around
+ * pq_verify_peer_name_matches_certificate_name(), converting the ASN1_STRING
+ * into a plain C string.
*/
static int
-verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
- char **store_name)
+openssl_verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
+ char **store_name)
{
int len;
- char *name;
const unsigned char *namedata;
- int result;
- char *host = PQhost(conn);
-
- *store_name = NULL;
/* Should not happen... */
if (name_entry == NULL)
@@ -573,9 +513,6 @@ verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
/*
* GEN_DNS can be only IA5String, equivalent to US ASCII.
- *
- * There is no guarantee the string returned from the certificate is
- * NULL-terminated, so make a copy that is.
*/
#ifdef HAVE_ASN1_STRING_GET0_DATA
namedata = ASN1_STRING_get0_data(name_entry);
@@ -583,45 +520,9 @@ verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
namedata = ASN1_STRING_data(name_entry);
#endif
len = ASN1_STRING_length(name_entry);
- 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;
+ /* OK to cast from unsigned to plain char, since it's all ASCII. */
+ return pq_verify_peer_name_matches_certificate_name(conn, (const char *) namedata, len, store_name);
}
/*
@@ -629,33 +530,14 @@ verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
*
* The certificate's Common Name and Subject Alternative Names are considered.
*/
-static bool
-verify_peer_name_matches_certificate(PGconn *conn)
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
{
- int names_examined = 0;
- bool found_match = false;
- bool got_error = false;
- char *first_name = NULL;
-
STACK_OF(GENERAL_NAME) *peer_san;
int i;
- int rc;
- char *host = PQhost(conn);
-
- /*
- * 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;
- }
+ int rc = 0;
/*
* First, get the Subject Alternative Names (SANs) from the certificate,
@@ -676,24 +558,20 @@ verify_peer_name_matches_certificate(PGconn *conn)
{
char *alt_name;
- names_examined++;
- rc = verify_peer_name_matches_certificate_name(conn,
+ (*names_examined)++;
+ rc = openssl_verify_peer_name_matches_certificate_name(conn,
name->d.dNSName,
&alt_name);
- if (rc == -1)
- got_error = true;
- if (rc == 1)
- found_match = true;
if (alt_name)
{
- if (!first_name)
- first_name = alt_name;
+ if (!*first_name)
+ *first_name = alt_name;
else
free(alt_name);
}
}
- if (found_match || got_error)
+ if (rc != 0)
break;
}
sk_GENERAL_NAME_free(peer_san);
@@ -706,7 +584,7 @@ verify_peer_name_matches_certificate(PGconn *conn)
* (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
* dNSName is present, the CN must be ignored.)
*/
- if (names_examined == 0)
+ if (*names_examined == 0)
{
X509_NAME *subject_name;
@@ -719,55 +597,17 @@ verify_peer_name_matches_certificate(PGconn *conn)
NID_commonName, -1);
if (cn_index >= 0)
{
- names_examined++;
- rc = verify_peer_name_matches_certificate_name(
+ (*names_examined)++;
+ rc = openssl_verify_peer_name_matches_certificate_name(
conn,
X509_NAME_ENTRY_get_data(
X509_NAME_get_entry(subject_name, cn_index)),
- &first_name);
-
- if (rc == -1)
- got_error = true;
- else if (rc == 1)
- found_match = true;
+ first_name);
}
}
}
- 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;
+ return rc;
}
#if defined(ENABLE_THREAD_SAFETY) && defined(HAVE_CRYPTO_LOCK)
@@ -1441,7 +1281,7 @@ open_client_SSL(PGconn *conn)
return PGRES_POLLING_FAILED;
}
- if (!verify_peer_name_matches_certificate(conn))
+ if (!pq_verify_peer_name_matches_certificate(conn))
{
pgtls_close(conn);
return PGRES_POLLING_FAILED;
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index b3492b033a..eba23dcecc 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -732,6 +732,19 @@ extern char *pgtls_get_finished(PGconn *conn, size_t *len);
*/
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
+/*
+ * Verify that the server certificate matches the host name we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ *
+ * Returns 1 if the name matches, and 0 if it does not. On error, returns
+ * -1, and sets the libpq error message.
+ *
+ */
+extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name);
+
/* === miscellaneous macros === */
/*
base-commit: c12693d8f3bbbffcb79f6af476cc647402e1145e
--
2.16.1
On 2018-01-17 12:30:16 -0500, Peter Eisentraut wrote:
On 1/2/18 10:35, Peter Eisentraut wrote:
On 11/26/17 20:05, Andreas Karlsson wrote:
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).This patch appears to work well.
Seeing that Andres is apparently currently not available, I have started
to dig through this patch myself and made some adjustments.
I presume you meant Andreas, right?
FWIW, I'm -0.5 on adding gnutls support. I've not seen any non-vague
arguments for it, and having debugged gnutls using applications in the
past, I'm not convinced we're not primarily increasing our workload by
adding support. If gnutls would improve our windows or OSX situation,
I'd think differently, but afaics it doesn't.
Greetings,
Andres Freund
On Mon, Jan 29, 2018 at 06:24:18PM -0800, Andres Freund wrote:
FWIW, I'm -0.5 on adding gnutls support. I've not seen any non-vague
arguments for it, and having debugged gnutls using applications in the
past, I'm not convinced we're not primarily increasing our workload by
adding support. If gnutls would improve our windows or OSX situation,
I'd think differently, but afaics it doesn't.
That's an interesting point. The last patch set presented by Peter
improves the pluggability situation for Windows and OSX as well, so
those are a good addition anyway.
--
Michael
On Mon, Jan 29, 2018 at 07:39:33PM -0500, Peter Eisentraut wrote:
I think most users actually still think about the whole topic as "SSL",
and the leading library is called "OpenSSL", so I think we're fine.
Yes, that's my impression on the matter as well. While renaming the
client-side parameters sounds not really plausible, the server-side
parameters could be renamed with an implementation-related prefix if
another implementation than OpenSSL is used. Until that happens, any
server-side renaming does not justify the breakage in my opinion.
--
Michael
Andres Freund <andres@anarazel.de> writes:
FWIW, I'm -0.5 on adding gnutls support. I've not seen any non-vague
arguments for it, and having debugged gnutls using applications in the
past, I'm not convinced we're not primarily increasing our workload by
adding support. If gnutls would improve our windows or OSX situation,
I'd think differently, but afaics it doesn't.
That's a fair point. But I think a big part of the value here is to
verify that we've cleaned up our internal APIs to the point where a
different SSL/TLS implementation *could* be rolled underneath. As part
of that, we certainly want to look at gnutls. There might be more
practical value (at least in the short term) in porting to the macOS or
Windows native TLS stacks. But the more different libraries we look at
in the process, the less likely we are to paint ourselves into a corner.
regards, tom lane
Hi,
On 2018-01-29 22:41:53 -0500, Tom Lane wrote:
But I think a big part of the value here is to verify that we've
cleaned up our internal APIs to the point where a different SSL/TLS
implementation *could* be rolled underneath.
Yea, I completely agree with that.
As part of that, we certainly want to look at gnutls. There might be
more practical value (at least in the short term) in porting to the
macOS or Windows native TLS stacks. But the more different libraries
we look at in the process, the less likely we are to paint ourselves
into a corner.
That's true. But any further development in the area is already going to
be painful with three libraries (openssl, native windows, native osx),
adding support for a fourth that doesn't buy as anything just seems to
make the situation worse.
Anyway, I'm only -0.5 on it, and I've said my spiel...
Greetings,
Andres Freund
On Mon, Jan 29, 2018 at 09:16:56PM -0500, Peter Eisentraut wrote:
On 1/28/18 23:43, Michael Paquier wrote:
The comment at the top of PQinitSSL mentions OpenSSL, you may want to
get rid of it as well.I think that whole business is actually obsolete as of OpenSSL 1.1.0 and
not applicable to GnuTLS, so we might just leave it to die with some
appropriate documentation updates.
Yes, that would be fine as well.
To be honest, I find this refactoring confusing. As things stand on
HEAD, fe-secure.c depends on the contents of fe-secure-openssl.c, and
the dependency goes only in one direction. With your patch, you also
make fe-secure-openssl.c call things within fe-secure.c. It seems to me
that a cleaner split would be to introduce a common file for all the
low-level routines like the two ones you are introducing here, say
fe-secure-common.c. aND pq_verify_peer_name_matches_certificate_name and
pq_verify_peer_name_matches_certificate should be moved to that.I like that. Updated patch attached.
Thanks for the new version. This looks sane to me.
ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o sha2_openssl.o
+OBJS += fe-secure-openssl.o fe-secure-common.o sha2_openssl.o
else
As fe-secure-common.c will only be built with SSL builds, you need also
to tweak Mkvcbuild.pm and remove it from libpq's compilation.
+ /* If string does not end in pattern (minus the wildcard), we don't
+ * match */
Such comment blocks are not Postgres-like.
src/interfaces/libpq/nls.mk is updated automatically with translations
by the way?
--
Michael
On 01/26/2018 03:54 AM, Peter Eisentraut wrote:
On 1/25/18 20:10, Michael Paquier wrote:
Peter, could you change ssl_version() and ssl_cipher() in sslinfo at the
same time please? I think that those should use the generic backend-side
APIs as well. sslinfo depends heavily on OpenSSL, OK, but if possible
getting this code more generic will help users of sslinfo to get
something partially working with other SSL implementations natively.sslinfo is currently entirely dependent on OpenSSL, so I don't think
it's useful to throw in one or two isolated API changes.I'm thinking maybe we should get rid of sslinfo and fold everything into
pg_stat_ssl.
I think sslinfo should either use the pg_tls_get_* functions or be
removed. I do not like having an OpenSSL specific extension. One issue
though is that pg_tls_get_* truncates strings to a given length while
sslinfo allocates a copy and is therefore only limited by the maximum
size of text, but this may not be an issue in practice.
Andreas
Re: Peter Eisentraut 2018-01-03 <99680dba-cf63-8151-1de2-46ca93897e56@2ndquadrant.com>
One scenario is that if GnuTLS goes in, it's quite plausible that the
PG11 packages for Debian and Ubuntu will use it by default. But if it
doesn't support tls-server-endpoint, then a JDBC client (assuming
channel binding support is added) can't connect to such a server with
SCRAM authentication over SSL (which we hope will be a popular
configuration), unless they manually disable channel binding altogether
using the new scramchannelbinding connection option. That would be a
very poor experience.
GnuTLS support would mean that Debian could finally link psql against
libreadline (instead of just LD_PRELOADing it at runtime) because
there's not OpenSSL license conflict anymore. But I'm only going to do
that switch if there's no visible incompatibilities for clients, and
even any server-side GUC name changes would need a damn good
justification because they make upgrades harder. The LD_PRELOAD hack
in psql works, there's no pressing urgency to remove it.
Christoph
On Thu, Feb 1, 2018 at 5:08 AM, Christoph Berg <myon@debian.org> wrote:
Re: Peter Eisentraut 2018-01-03 <99680dba-cf63-8151-1de2-46ca93897e56@2ndquadrant.com>
One scenario is that if GnuTLS goes in, it's quite plausible that the
PG11 packages for Debian and Ubuntu will use it by default. But if it
doesn't support tls-server-endpoint, then a JDBC client (assuming
channel binding support is added) can't connect to such a server with
SCRAM authentication over SSL (which we hope will be a popular
configuration), unless they manually disable channel binding altogether
using the new scramchannelbinding connection option. That would be a
very poor experience.GnuTLS support would mean that Debian could finally link psql against
libreadline (instead of just LD_PRELOADing it at runtime) because
there's not OpenSSL license conflict anymore. But I'm only going to do
that switch if there's no visible incompatibilities for clients, and
even any server-side GUC name changes would need a damn good
justification because they make upgrades harder. The LD_PRELOAD hack
in psql works, there's no pressing urgency to remove it.
Yeah. The original justification at top-of-thread for supporting
GnuTLS was licensing. I am not a lawyer and I don't have an opinion
on how much of a licensing problem there is around OpenSSL, but if
somebody things (as they evidently do, 'cuz this patch exists) that
there's a problem there and is willing to do the work to get it fixed,
I think that's great. Even the perception of a legal problem can
hinder adoption, and our goal is to get people to use PostgreSQL. Or
at least, I think that should be our goal.
I don't expect that to generate a lot of work for us because, just as
we insist that people who want obscure operating systems supported
should help by arranging for buildfarm members, so too we should
insist as part of supporting GnuTLS that someone provide buildfarm
members for it. If those buildfarm members break and don't get fixed,
then we'll have to consider removing GnuTLS support. Of course, this
arrangement doesn't guarantee that it's going to be zero work for us,
just as people who primarily work on UNIX-like systems still have to
worry somewhat about Microsoft Windows. But it keeps the effort
pretty manageable. I think that's likely to be even more true for
GnuTLS, because there's a huge amount of code that can depend on
pointer width and endian-ness, whereas it seems unlikely that anything
other than SSL patches will need to care about GnuTLS. And there's
just not that many of those. The biggest risk seems to be that new
SSL-related features someone wants to add in the future would need to
be made to work with every SSL implementation, and I think that could
indeed be an annoyance, but I don't think we'll really know how much
of an annoyance until we try it. It's not like we can't rip support
out again if it proves to be a huge problem (in contrast to a feature
like multixacts or partitioning, which you can't remove without
breaking upgrades).
Also, I have to admit that my experiences with OpenSSL have been
mostly negative. The basic stuff works and is clearly documented, but
more complicated things, at least at the time I looked at it, were not
clearly documented or not documented at all, and I ended up reading
uncommented code to try to figure out how it was supposed to work. I
don't think it's a bad thing if we do our bit to contribute to a
little competition among implementations. I'm not sure that gcc was
worrying too much about their error message quality until llvm came
along, but I bet they're thinking about it now.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
In the thread about Secure Transport we agreed to move the consideration
of new SSL libraries to PG12.
Here is my current patch, after all the refactorings.
The status is that it works fine and could be used.
There are two failures in the SSL tests that I cannot explain. The
tests are for some rather obscure configurations, so the changed
behaviors are not obviously wrong, perhaps legitimate implementation
differences. But someone wrote those tests with a purpose (probably),
so we should have some kind of explanation for the regressions.
Other non-critical, nice-to-have issues:
- Do something about sslinfo, perhaps fold into pg_stat_ssl view.
- Do something about pgcrypto.
- Add tests for load_dh_file().
- Implement channel binding tls-server-end-point.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v6-0001-GnuTLS-support.patchtext/plain; charset=UTF-8; name=v6-0001-GnuTLS-support.patch; x-mac-creator=0; x-mac-type=0Download
From c76b6efca0f52fe4deb6003009f4f8730201f041 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 8 Mar 2018 14:05:33 -0500
Subject: [PATCH v6] GnuTLS support
---
configure | 258 ++++++--
configure.in | 37 +-
doc/src/sgml/client-auth.sgml | 2 +-
doc/src/sgml/config.sgml | 81 ++-
doc/src/sgml/installation.sgml | 47 +-
doc/src/sgml/libpq.sgml | 54 +-
doc/src/sgml/runtime.sgml | 13 +-
doc/src/sgml/sslinfo.sgml | 1 +
src/Makefile.global.in | 1 +
src/backend/libpq/Makefile | 4 +-
src/backend/libpq/be-secure-gnutls.c | 820 +++++++++++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 4 +-
src/backend/libpq/be-secure.c | 4 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 38 ++
src/backend/utils/misc/postgresql.conf.sample | 7 +-
src/common/Makefile | 4 +-
src/common/sha2_gnutls.c | 99 +++
src/include/common/sha2.h | 14 +-
src/include/libpq/libpq-be.h | 13 +-
src/include/libpq/libpq.h | 2 +
src/include/pg_config.h.in | 17 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/.gitignore | 1 +
src/interfaces/libpq/Makefile | 14 +-
src/interfaces/libpq/fe-secure-gnutls.c | 836 ++++++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 2 +-
src/interfaces/libpq/libpq-fe.h | 2 +-
src/interfaces/libpq/libpq-int.h | 14 +-
src/port/pg_strong_random.c | 18 +-
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 2 +-
src/test/ssl/t/001_ssltests.pl | 65 +-
src/test/ssl/t/002_scram.pl | 2 +-
src/tools/msvc/Mkvcbuild.pm | 10 +
src/tools/pgindent/typedefs.list | 3 +
36 files changed, 2360 insertions(+), 135 deletions(-)
create mode 100644 src/backend/libpq/be-secure-gnutls.c
create mode 100644 src/common/sha2_gnutls.c
create mode 100644 src/interfaces/libpq/fe-secure-gnutls.c
diff --git a/configure b/configure
index 3943711283..f2d4aa502a 100755
--- a/configure
+++ b/configure
@@ -707,6 +707,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_gnutls
with_openssl
with_ldap
with_krb_srvnam
@@ -838,6 +839,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_gnutls
with_selinux
with_systemd
with_readline
@@ -1532,6 +1534,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-gnutls build with GnuTLS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -1999,6 +2002,52 @@ $as_echo "$ac_res" >&6; }
} # ac_fn_c_check_func
+# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES
+# ---------------------------------------------
+# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR
+# accordingly.
+ac_fn_c_check_decl ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ as_decl_name=`echo $2|sed 's/ *(.*//'`
+ as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'`
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5
+$as_echo_n "checking whether $as_decl_name is declared... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+int
+main ()
+{
+#ifndef $as_decl_name
+#ifdef __cplusplus
+ (void) $as_decl_use;
+#else
+ (void) $as_decl_name;
+#endif
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ eval "$3=yes"
+else
+ eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_decl
+
# ac_fn_c_check_member LINENO AGGR MEMBER VAR INCLUDES
# ----------------------------------------------------
# Tries to find if the field MEMBER exists in type AGGR, after including
@@ -2292,52 +2341,6 @@ rm -f conftest.val
as_fn_set_status $ac_retval
} # ac_fn_c_compute_int
-
-# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES
-# ---------------------------------------------
-# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR
-# accordingly.
-ac_fn_c_check_decl ()
-{
- as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
- as_decl_name=`echo $2|sed 's/ *(.*//'`
- as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'`
- { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5
-$as_echo_n "checking whether $as_decl_name is declared... " >&6; }
-if eval \${$3+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-$4
-int
-main ()
-{
-#ifndef $as_decl_name
-#ifdef __cplusplus
- (void) $as_decl_use;
-#else
- (void) $as_decl_name;
-#endif
-#endif
-
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- eval "$3=yes"
-else
- eval "$3=no"
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-eval ac_res=\$$3
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
- eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
-
-} # ac_fn_c_check_decl
cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
@@ -6001,6 +6004,47 @@ fi
$as_echo "$with_openssl" >&6; }
+
+#
+# GnuTLS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with GnuTLS support" >&5
+$as_echo_n "checking whether to build with GnuTLS support... " >&6; }
+
+
+
+# Check whether --with-gnutls was given.
+if test "${with_gnutls+set}" = set; then :
+ withval=$with_gnutls;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_GNUTLS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-gnutls option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_gnutls=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_gnutls" >&5
+$as_echo "$with_gnutls" >&6; }
+
+
+if test "$with_openssl" = yes && test "$with_gnutls" = yes; then
+ as_fn_error $? "cannot specify both --with-openssl and --with-gnutls" "$LINENO" 5
+fi
+
+
#
# SELinux
#
@@ -10176,6 +10220,107 @@ done
fi
+if test "$with_gnutls" = yes ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing gnutls_init" >&5
+$as_echo_n "checking for library containing gnutls_init... " >&6; }
+if ${ac_cv_search_gnutls_init+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char gnutls_init ();
+int
+main ()
+{
+return gnutls_init ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' gnutls; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_gnutls_init=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_gnutls_init+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_gnutls_init+:} false; then :
+
+else
+ ac_cv_search_gnutls_init=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_gnutls_init" >&5
+$as_echo "$ac_cv_search_gnutls_init" >&6; }
+ac_res=$ac_cv_search_gnutls_init
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+else
+ as_fn_error $? "library 'gnutls' is required for GnuTLS" "$LINENO" 5
+fi
+
+ # GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ # certificate chains.
+ ac_fn_c_check_decl "$LINENO" "GNUTLS_ALPN_SERVER_PRECEDENCE" "ac_cv_have_decl_GNUTLS_ALPN_SERVER_PRECEDENCE" "#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+"
+if test "x$ac_cv_have_decl_GNUTLS_ALPN_SERVER_PRECEDENCE" = xyes; then :
+ ac_have_decl=1
+else
+ ac_have_decl=0
+fi
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_DECL_GNUTLS_ALPN_SERVER_PRECEDENCE $ac_have_decl
+_ACEOF
+ac_fn_c_check_decl "$LINENO" "GNUTLS_X509_CRT_LIST_SORT" "ac_cv_have_decl_GNUTLS_X509_CRT_LIST_SORT" "#include <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; }
@@ -10984,6 +11129,17 @@ else
fi
+fi
+
+if test "$with_gnutls" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "gnutls/gnutls.h" "ac_cv_header_gnutls_gnutls_h" "$ac_includes_default"
+if test "x$ac_cv_header_gnutls_gnutls_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <gnutls/gnutls.h> is required for GnuTLS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
@@ -15741,9 +15897,11 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test "$enable_strong_random" = "yes" && test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
+if test "$enable_strong_random" = "yes" && test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_GNUTLS_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
if test x"$with_openssl" = x"yes" ; then
USE_OPENSSL_RANDOM=1
+ elif test x"$with_gnutls" = x"yes" ; then
+ USE_GNUTLS_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
else
@@ -15782,6 +15940,12 @@ $as_echo "#define USE_OPENSSL_RANDOM 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL" >&5
$as_echo "OpenSSL" >&6; }
+ elif test x"$USE_GNUTLS_RANDOM" = x"1" ; then
+
+$as_echo "#define USE_GNUTLS_RANDOM 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: GnuTLS" >&5
+$as_echo "GnuTLS" >&6; }
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
$as_echo "#define USE_WIN32_RANDOM 1" >>confdefs.h
diff --git a/configure.in b/configure.in
index 1babdbb755..2aa132b7d1 100644
--- a/configure.in
+++ b/configure.in
@@ -706,6 +706,21 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+
+#
+# GnuTLS
+#
+AC_MSG_CHECKING([whether to build with GnuTLS support])
+PGAC_ARG_BOOL(with, gnutls, no, [build with GnuTLS support],
+ [AC_DEFINE([USE_GNUTLS], 1, [Define to build with GnuTLS support. (--with-gnutls)])])
+AC_MSG_RESULT([$with_gnutls])
+AC_SUBST(with_gnutls)
+
+if test "$with_openssl" = yes && test "$with_gnutls" = yes; then
+ AC_MSG_ERROR([cannot specify both --with-openssl and --with-gnutls])
+fi
+
+
#
# SELinux
#
@@ -1080,6 +1095,17 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_gnutls" = yes ; then
+ AC_SEARCH_LIBS(gnutls_init, gnutls, [], [AC_MSG_ERROR([library 'gnutls' is required for GnuTLS])])
+ # GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ # certificate chains.
+ AC_CHECK_DECLS([GNUTLS_ALPN_SERVER_PRECEDENCE, GNUTLS_X509_CRT_LIST_SORT], [], [],
+[#include <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
@@ -1228,6 +1254,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, [],
@@ -1997,9 +2027,11 @@ fi
# in the template or configure command line.
# If not selected manually, try to select a source automatically.
-if test "$enable_strong_random" = "yes" && test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
+if test "$enable_strong_random" = "yes" && test x"$USE_OPENSSL_RANDOM" = x"" && test x"$USE_GNUTLS_RANDOM" = x"" && test x"$USE_WIN32_RANDOM" = x"" && test x"$USE_DEV_URANDOM" = x"" ; then
if test x"$with_openssl" = x"yes" ; then
USE_OPENSSL_RANDOM=1
+ elif test x"$with_gnutls" = x"yes" ; then
+ USE_GNUTLS_RANDOM=1
elif test "$PORTNAME" = "win32" ; then
USE_WIN32_RANDOM=1
else
@@ -2016,6 +2048,9 @@ if test "$enable_strong_random" = yes ; then
if test x"$USE_OPENSSL_RANDOM" = x"1" ; then
AC_DEFINE(USE_OPENSSL_RANDOM, 1, [Define to use OpenSSL for random number generation])
AC_MSG_RESULT([OpenSSL])
+ elif test x"$USE_GNUTLS_RANDOM" = x"1" ; then
+ AC_DEFINE(USE_GNUTLS_RANDOM, 1, [Define to use GnuTLS for random number generation])
+ AC_MSG_RESULT([GnuTLS])
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation])
AC_MSG_RESULT([Windows native])
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 53832d08e2..27f31102d7 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -1789,7 +1789,7 @@ <title>RADIUS Authentication</title>
<para>
The encryption vector used will only be cryptographically
strong if <productname>PostgreSQL</productname> is built with support for
- <productname>OpenSSL</productname>. In other cases, the transmission to the
+ an SSL library (e.g., <productname>OpenSSL</productname>). In other cases, the transmission to the
RADIUS server should only be considered obfuscated, not secured, and
external security measures should be applied if necessary.
</para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 3a8fc7d803..1770ed5025 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1156,6 +1156,37 @@ <title>SSL</title>
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-dh-params-file" xreflabel="ssl_dh_params_file">
+ <term><varname>ssl_dh_params_file</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_dh_params_file</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing Diffie-Hellman parameters
+ used for so-called ephemeral DH family of SSL ciphers. The default is
+ empty, in which case compiled-in default DH parameters used. Using
+ custom DH parameters reduces the exposure if an attacker manages to
+ crack the well-known compiled-in DH parameters. You can create your own
+ DH parameters file with the command
+ <command>openssl dhparam -out dhparams.pem 2048</command>.
+ </para>
+
+ <para>
+ This parameter can only be set in the <filename>postgresql.conf</filename>
+ file or on the server command line.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>
+ The following settings are only applicable if the
+ <productname>OpenSSL</productname> library is used.
+ </para>
+
+ <variablelist>
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1289,31 +1320,41 @@ <title>SSL</title>
</para>
</listitem>
</varlistentry>
+ </variablelist>
- <varlistentry id="guc-ssl-dh-params-file" xreflabel="ssl_dh_params_file">
- <term><varname>ssl_dh_params_file</varname> (<type>string</type>)
+ <para>
+ The following settings are only applicable if the
+ <productname>GnuTLS</productname> library is used.
+ </para>
+
+ <variablelist>
+ <varlistentry id="guc-gnutls-priority" xreflabel="gnutls_priority">
+ <term><varname>gnutls_priority</varname> (<type>string</type>)
<indexterm>
- <primary><varname>ssl_dh_params_file</varname> configuration parameter</primary>
+ <primary><varname>gnutls_priority</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
- Specifies the name of the file containing Diffie-Hellman parameters
- used for so-called ephemeral DH family of SSL ciphers. The default is
- empty, in which case compiled-in default DH parameters used. Using
- custom DH parameters reduces the exposure if an attacker manages to
- crack the well-known compiled-in DH parameters. You can create your own
- DH parameters file with the command
- <command>openssl dhparam -out dhparams.pem 2048</command>.
+ Sets the priorities for the cipher suites supported by GnuTLS. This
+ can be used to specify which SSL cipher suites are allowed to be
+ used on secure connections and related settings. See the
+ documentation on GnuTLS priority strings for details.
+ This parameter can only be set in the <filename>postgresql.conf</filename>
+ file or on the server command line.
</para>
<para>
- This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ The default value is <literal>NORMAL:%SERVER_PRECEDENCE</literal> (or
+ just <literal>NORMAL</literal> with older
+ <productname>GnuTLS</productname> versions that don't support the
+ server precedence setting). The default is usually a reasonable
+ choice unless you have specific security requirements.
</para>
</listitem>
</varlistentry>
</variablelist>
+
</sect2>
</sect1>
@@ -8120,6 +8161,22 @@ <title>Preset Options</title>
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-library" xreflabel="ssl_library">
+ <term><varname>ssl_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Reports the name of the SSL library that this PostgreSQL server was
+ built with (even if SSL is not currently configured or in use on this
+ instance), for example <literal>OpenSSL</literal> or
+ <literal>GnuTLS</literal>, or an empty string if none.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-wal-block-size" xreflabel="wal_block_size">
<term><varname>wal_block_size</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 141494c651..9d10844ea8 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -244,9 +244,9 @@ <title>Requirements</title>
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
- encrypted client connections. The minimum required version is
- 0.9.8.
+ If you want to support encrypted client connections (SSL/TLS), you need
+ <productname>OpenSSL</productname> (at least version 0.9.8) or
+ <productname>GnuTLS</productname>.
</para>
</listitem>
@@ -727,6 +727,29 @@ <title>Configuration</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-gnutls</option>
+ <indexterm>
+ <primary>GnuTLS</primary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>TLS</acronym>/<acronym>SSL</acronym>
+ (encrypted) connections using the <productname>GnuTLS</productname>
+ package. <filename>configure</filename> will check for the required
+ header files and libraries to make sure that your
+ <productname>GnuTLS</productname> installation is sufficient before
+ proceeding.
+ </para>
+
+ <para>
+ See also <option>--with-openssl</option> for an alternative.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-gssapi</option></term>
<listitem>
@@ -798,12 +821,16 @@ <title>Configuration</title>
</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.
+ Build with support for <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ (encrypted) connections using the <productname>OpenSSL</productname>
+ package. <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.
+ </para>
+
+ <para>
+ See also <option>--with-gnutls</option> for an alternative.
</para>
</listitem>
</varlistentry>
@@ -2284,7 +2311,7 @@ <title>Cygwin</title>
<listitem>
<para>
- OpenSSL is not supported.
+ SSL is not supported.
</para>
</listitem>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index da9421486b..9d3ee7229b 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1442,8 +1442,9 @@ <title>Parameter Key Words</title>
compressed.
If set to 0, compression will be disabled (this requires
<productname>OpenSSL</productname> 1.0.0 or later).
+ <productname>GnuTLS</productname> does not support SSL compression.
This parameter is ignored if a connection without SSL is made,
- or if the version of <productname>OpenSSL</productname> used does not support
+ or if the SSL library used does not support
it.
</para>
<para>
@@ -2141,8 +2142,8 @@ <title>Connection Status Functions</title>
<term><literal>library</literal></term>
<listitem>
<para>
- Name of the SSL implementation in use. (Currently, only
- <literal>"OpenSSL"</literal> is implemented)
+ Name of the SSL implementation in use, e.g.,
+ <literal>OpenSSL</literal> or <literal>GnuTLS</literal>
</para>
</listitem>
</varlistentry>
@@ -2211,11 +2212,11 @@ <title>Connection Status Functions</title>
<synopsis>
void *PQsslStruct(const PGconn *conn, const char *struct_name);
</synopsis>
+ The struct(s) available depend on the SSL implementation in use.
</para>
<para>
- The struct(s) available depend on the SSL implementation in use.
- For OpenSSL, there is one struct, available under the name "OpenSSL",
- and it returns a pointer to the OpenSSL <literal>SSL</literal> struct.
+ For OpenSSL, there is one struct, available under the name <literal>"OpenSSL"</literal>,
+ and it returns a pointer to the OpenSSL <type>SSL</type> struct.
To use this function, code along the following lines could be used:
<programlisting><![CDATA[
#include <libpq-fe.h>
@@ -2234,12 +2235,31 @@ <title>Connection Status Functions</title>
/* use OpenSSL functions to access ssl */
}
]]></programlisting>
- </para>
- <para>
This structure can be used to verify encryption levels, check server
certificates, and more. Refer to the <productname>OpenSSL</productname>
documentation for information about this structure.
</para>
+ <para>
+ Similarly, for GnuTLS, the <type>gnutls_session_t</type> can be
+ obtained under the name <literal>"GnuTLS"</literal>. For example:
+<programlisting><![CDATA[
+#include <libpq-fe.h>
+#include <gnutls/gnutls.h>
+
+...
+
+ gnutls_session_t ssl;
+
+ dbconn = PQconnectdb(...);
+ ...
+
+ ssl = PQsslStruct(dbconn, "GnuTLS");
+ if (ssl)
+ {
+ /* use GnuTLS functions to access ssl */
+ }
+ ]]></programlisting>
+ </para>
</listitem>
</varlistentry>
@@ -7632,6 +7652,7 @@ <title>SSL Support</title>
</para>
<para>
+ When <productname>OpenSSL</productname> is used, then
<application>libpq</application> reads the system-wide
<productname>OpenSSL</productname> configuration file. By default, this
file is named <filename>openssl.cnf</filename> and is located in the
@@ -8006,6 +8027,13 @@ <title>SSL Library Initialization</title>
for details on the SSL API.
</para>
+ <para>
+ If you are using <productname>OpenSSL</productname> version 1.1.0 or later
+ and use initialization function <function>OPENSSL_init_ssl()</function> or
+ the implicit initialization, then you don't need to do this. Similarly,
+ this is not necessary with <productname>GnuTLS</productname>.
+ </para>
+
<para>
<variablelist>
<varlistentry id="libpq-pqinitopenssl">
@@ -8041,6 +8069,10 @@ <title>SSL Library Initialization</title>
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ With <productname>GnuTLS</productname>, this function does nothing.
+ </para>
</listitem>
</varlistentry>
@@ -8061,12 +8093,16 @@ <title>SSL Library Initialization</title>
</para>
<para>
- This function is equivalent to
+ With <productname>OpenSSL</productname>, this function is equivalent to
<literal>PQinitOpenSSL(do_ssl, do_ssl)</literal>.
It is sufficient for applications that initialize both or neither
of <application>OpenSSL</application> and <literal>libcrypto</literal>.
</para>
+ <para>
+ With <productname>GnuTLS</productname>, this function does nothing.
+ </para>
+
<para>
<function>PQinitSSL</function> has been present since
<productname>PostgreSQL</productname> 8.0, while <function>PQinitOpenSSL</function>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..f18ce4c9ac 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2152,10 +2152,14 @@ <title>Secure TCP/IP Connections with SSL</title>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
- for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
- server systems and that support in <productname>PostgreSQL</productname> is
- enabled at build time (see <xref linkend="installation"/>).
+ for increased security. This requires that support in
+ <productname>PostgreSQL</productname> is enabled at build time (see <xref
+ linkend="installation"/>). <productname>PostgreSQL</productname> supports
+ both <productname>OpenSSL</productname> and
+ <productname>GnuTLS</productname> as the SSL implementation. This is
+ chosen at build time. Third-party clients may also use other SSL
+ implementations. Clients and servers using different SSL implementations
+ are compatible.
</para>
<sect2 id="ssl-setup">
@@ -2225,6 +2229,7 @@ <title>Basic Setup</title>
<title>OpenSSL Configuration</title>
<para>
+ When using <productname>OpenSSL</productname>,
<productname>PostgreSQL</productname> reads the system-wide
<productname>OpenSSL</productname> configuration file. By default, this
file is named <filename>openssl.cnf</filename> and is located in the
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index cda09aaafd..782d63fd40 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -17,6 +17,7 @@ <title>sslinfo</title>
<para>
This extension won't build at all unless the installation was
configured with <literal>--with-openssl</literal>.
+ (GnuTLS is currently not supported.)
</para>
<sect2>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 15c14951e8..a4c2761424 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,6 +183,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
+with_gnutls = @with_gnutls@
with_openssl = @with_openssl@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 7fa2b02743..7af6e22744 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -17,7 +17,9 @@ include $(top_builddir)/src/Makefile.global
OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ifaddr.o pqcomm.o \
pqformat.o pqmq.o pqsignal.o auth-scram.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_gnutls),yes)
+OBJS += be-secure-gnutls.o
+else ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
diff --git a/src/backend/libpq/be-secure-gnutls.c b/src/backend/libpq/be-secure-gnutls.c
new file mode 100644
index 0000000000..cc8b86331d
--- /dev/null
+++ b/src/backend/libpq/be-secure-gnutls.c
@@ -0,0 +1,820 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-gnutls.c
+ * functions for GnuTLS support in the backend.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-gnutls.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <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);
+#ifdef HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION
+static int pin_function(void *userdata, int attempt,
+ const char *token_url,
+ const char *token_label,
+ unsigned int flags,
+ char *pin, size_t pin_max);
+static int dummy_pin_function(void *userdata, int attempt,
+ const char *token_url,
+ const char *token_label,
+ unsigned int flags,
+ char *pin, size_t pin_max);
+#endif
+static int verify_cb(gnutls_session_t ssl);
+static bool initialize_dh(gnutls_dh_params_t *dh_params, bool isServerStart);
+
+static gnutls_certificate_credentials_t tls_credentials = NULL;
+static gnutls_dh_params_t tls_dh_params = NULL;
+static gnutls_priority_t tls_priority = NULL;
+static bool tls_initialized = false;
+static bool pin_function_called = false;
+
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+int
+be_tls_init(bool isServerStart)
+{
+ gnutls_certificate_credentials_t credentials = NULL;
+ gnutls_priority_t priority = NULL;
+ gnutls_dh_params_t dh_params = NULL;
+ int ret;
+ const char *err_pos;
+
+ /* This stuff need be done only once. */
+ if (!tls_initialized)
+ {
+ gnutls_global_init();
+ tls_initialized = true;
+ }
+
+ ret = gnutls_certificate_allocate_credentials(&credentials);
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not create SSL credentials: %s",
+ gnutls_strerror(ret))));
+ goto error;
+ }
+
+ /*
+ * Set PIN (passphrase) callback. Note that there is no default for this
+ * in GnuTLS.
+ *
+ * If reloading, substitute a dummy callback, because we don't want to
+ * prompt for a passphrase in an already-running server.
+ */
+#ifdef HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION
+ if (isServerStart)
+ gnutls_pkcs11_set_pin_function(pin_function, NULL);
+ else
+ gnutls_pkcs11_set_pin_function(dummy_pin_function, NULL);
+#endif
+
+ /*
+ * Load and verify server's certificate and private key
+ */
+ if (!check_ssl_key_file_permissions(ssl_key_file, isServerStart))
+ goto error;
+
+ pin_function_called = false;
+
+ ret = gnutls_certificate_set_x509_key_file(credentials, ssl_cert_file, ssl_key_file, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ if (pin_function_called)
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase",
+ ssl_key_file)));
+ else
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load server certificate \"%s\" or key file \"%s\": %s",
+ ssl_cert_file, ssl_key_file, gnutls_strerror(ret))));
+ goto error;
+ }
+
+ /* set up ephemeral DH keys */
+ if (!initialize_dh(&dh_params, isServerStart))
+ goto error;
+
+ gnutls_certificate_set_dh_params(credentials, dh_params);
+
+ /* set up the allowed cipher list */
+ ret = gnutls_priority_init(&priority, gnutls_priority, &err_pos);
+ if (ret < 0)
+ {
+ if (ret == GNUTLS_E_INVALID_REQUEST)
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not set the cipher list: syntax error at %s", err_pos)));
+ else
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not set the cipher list: %s", gnutls_strerror(ret))));
+ goto error;
+ }
+
+ /*
+ * Load CA store, so we can verify client certificates if needed.
+ */
+ if (ssl_ca_file[0])
+ {
+ ret = gnutls_certificate_set_x509_trust_file(credentials, ssl_ca_file, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load root certificate file \"%s\": %s",
+ ssl_ca_file, gnutls_strerror(ret))));
+ goto error;
+ }
+
+ gnutls_certificate_set_verify_function(credentials, verify_cb);
+ }
+
+ /*
+ * Load the Certificate Revocation List (CRL).
+ */
+ if (ssl_crl_file[0])
+ {
+ ret = gnutls_certificate_set_x509_crl_file(credentials, ssl_crl_file, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load SSL certificate revocation list file \"%s\": %s",
+ ssl_crl_file, gnutls_strerror(ret))));
+ goto error;
+ }
+ }
+
+ /*
+ * Success! Replace any existing credentials.
+ */
+ if (tls_credentials)
+ gnutls_certificate_free_credentials(tls_credentials);
+ if (tls_priority)
+ gnutls_priority_deinit(tls_priority);
+ if (tls_dh_params)
+ gnutls_dh_params_deinit(tls_dh_params);
+
+ tls_credentials = credentials;
+ tls_priority = priority;
+ tls_dh_params = dh_params;
+
+ /*
+ * Set flag to remember whether CA store has been loaded.
+ */
+ if (ssl_ca_file[0])
+ ssl_loaded_verify_locations = true;
+ else
+ ssl_loaded_verify_locations = false;
+
+ return 0;
+
+error:
+ if (credentials)
+ gnutls_certificate_free_credentials(credentials);
+ if (priority)
+ gnutls_priority_deinit(priority);
+ if (dh_params)
+ gnutls_dh_params_deinit(dh_params);
+ return -1;
+}
+
+void
+be_tls_destroy(void)
+{
+ if (tls_credentials)
+ gnutls_certificate_free_credentials(tls_credentials);
+ if (tls_priority)
+ gnutls_priority_deinit(tls_priority);
+ if (tls_dh_params)
+ gnutls_dh_params_deinit(tls_dh_params);
+ tls_credentials = NULL;
+ tls_priority = NULL;
+ tls_dh_params = NULL;
+ ssl_loaded_verify_locations = false;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ int ret;
+
+ Assert(!port->ssl);
+ Assert(!port->peer);
+
+ if (!tls_credentials || !tls_priority)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not initialize SSL connection: SSL context not set up")));
+ return -1;
+ }
+
+ ret = gnutls_init(&port->ssl, GNUTLS_SERVER);
+ if (ret < 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not initialize SSL connection: %s",
+ gnutls_strerror(ret))));
+ return -1;
+ }
+
+ gnutls_transport_set_ptr(port->ssl, port);
+ gnutls_transport_set_pull_function(port->ssl, my_sock_read);
+ gnutls_transport_set_push_function(port->ssl, my_sock_write);
+
+ ret = gnutls_priority_set(port->ssl, tls_priority);
+ if (ret < 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not initialize SSL connection: %s",
+ gnutls_strerror(ret))));
+ return -1;
+ }
+
+ ret = gnutls_credentials_set(port->ssl, GNUTLS_CRD_CERTIFICATE, tls_credentials);
+ if (ret < 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not initialize SSL connection: %s",
+ gnutls_strerror(ret))));
+ return -1;
+ }
+
+ if (ssl_loaded_verify_locations)
+ gnutls_certificate_server_set_request(port->ssl, GNUTLS_CERT_REQUEST);
+
+ port->ssl_in_use = true;
+
+ do
+ {
+ ret = gnutls_handshake(port->ssl);
+ }
+ while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+
+ if (ret < 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not accept SSL connection: %s",
+ gnutls_strerror(ret))));
+ return -1;
+ }
+
+ /* Get client certificate, if available. */
+ ret = get_peer_certificate(port->ssl, &port->peer);
+ if (ret < 0 && ret != GNUTLS_E_NO_CERTIFICATE_FOUND)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not load peer certificates: %s",
+ gnutls_strerror(ret))));
+ }
+
+ /* and extract the Common Name from it. */
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+ if (port->peer != NULL)
+ {
+ size_t len = 0;
+
+ gnutls_x509_crt_get_dn_by_oid(port->peer,
+ GNUTLS_OID_X520_COMMON_NAME,
+ 0, 0, NULL, &len);
+
+ if (len > 0)
+ {
+ char *peer_cn;
+
+ peer_cn = MemoryContextAlloc(TopMemoryContext, len);
+
+ ret = gnutls_x509_crt_get_dn_by_oid(port->peer,
+ GNUTLS_OID_X520_COMMON_NAME,
+ 0, 0, peer_cn, &len);
+
+ if (ret != 0)
+ {
+ /* shouldn't happen */
+ pfree(peer_cn);
+ return -1;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent
+ * attacks like CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+ pfree(peer_cn);
+ return -1;
+ }
+
+
+ if (ret == 0)
+ port->peer_cn = peer_cn;
+ else
+ pfree(peer_cn);
+
+ }
+
+ port->peer_cert_valid = true;
+ }
+
+ return 0;
+}
+
+void
+be_tls_close(Port *port)
+{
+ if (port->ssl)
+ {
+ gnutls_bye(port->ssl, GNUTLS_SHUT_RDWR);
+ gnutls_deinit(port->ssl);
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+ }
+
+ if (port->peer)
+ {
+ gnutls_x509_crt_deinit(port->peer);
+ port->peer = NULL;
+ }
+
+ if (port->peer_cn)
+ {
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n;
+
+ n = gnutls_record_recv(port->ssl, ptr, len);
+
+ if (n > 0)
+ return n;
+
+ switch (n)
+ {
+ case 0:
+
+ /*
+ * the SSL connnection was closed, leave it to the caller to
+ * ereport it
+ */
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_INTERRUPTED:
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ n = -1;
+ break;
+ default:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ gnutls_strerror(n))));
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+
+ return n;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n;
+
+ n = gnutls_record_send(port->ssl, ptr, len);
+
+ if (n >= 0)
+ return n;
+
+ switch (n)
+ {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_INTERRUPTED:
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ n = -1;
+ break;
+ default:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ gnutls_strerror(n))));
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+
+ return n;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Private substitute transport layer: this does the sending and receiving
+ * using send() and recv() instead. This is so that we can enable and disable
+ * interrupts just while calling recv(). We cannot have interrupts occurring
+ * while the bulk of GnuTLS runs, because it uses malloc() and possibly other
+ * non-reentrant libc facilities. We also need to call send() and recv()
+ * directly so it gets passed through the socket/signals layer on Win32.
+ */
+
+static ssize_t
+my_sock_read(gnutls_transport_ptr_t port, void *buf, size_t size)
+{
+ return secure_raw_read((Port *) port, buf, size);
+}
+
+static ssize_t
+my_sock_write(gnutls_transport_ptr_t port, const void *buf, size_t size)
+{
+ return secure_raw_write((Port *) port, buf, size);
+}
+
+#if !HAVE_DECL_GNUTLS_X509_CRT_LIST_SORT
+/*
+ * GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ * certificate chains, so we skip doing so in these earlier versions.
+ */
+#define GNUTLS_X509_CRT_LIST_SORT 0
+#endif
+
+/*
+ * Get peer certificate from a session
+ *
+ * Returns GNUTLS_E_NO_CERTIFICATE_FOUND when not x509 certifcate was found.
+ */
+static int
+get_peer_certificate(gnutls_session_t ssl, gnutls_x509_crt_t * peer)
+{
+ if (gnutls_certificate_type_get(ssl) == GNUTLS_CRT_X509)
+ {
+ unsigned int n;
+ int ret;
+ gnutls_datum_t const *raw_certs;
+ gnutls_x509_crt_t *certs;
+
+ raw_certs = gnutls_certificate_get_peers(ssl, &n);
+
+ if (n == 0)
+ return GNUTLS_E_NO_CERTIFICATE_FOUND;
+
+ certs = palloc(n * sizeof(gnutls_x509_crt_t));
+
+ ret = gnutls_x509_crt_list_import(certs, &n, raw_certs,
+ GNUTLS_X509_FMT_DER,
+ GNUTLS_X509_CRT_LIST_SORT);
+
+ if (ret >= 1)
+ {
+ unsigned int i;
+
+ for (i = 1; i < ret; i++)
+ gnutls_x509_crt_deinit(certs[i]);
+
+ *peer = certs[0];
+
+ ret = GNUTLS_E_SUCCESS;
+ }
+ else if (ret == 0)
+ ret = GNUTLS_E_NO_CERTIFICATE_FOUND;
+
+ pfree(certs);
+
+ return ret;
+ }
+
+ return GNUTLS_E_NO_CERTIFICATE_FOUND;
+}
+
+#define MAX_DH_FILE_SIZE 10240
+
+/*
+ * Load precomputed DH parameters.
+ *
+ * To prevent "downgrade" attacks, we perform a number of checks
+ * to verify that the DBA-generated DH parameters file contains
+ * what we expect it to contain.
+ */
+static bool
+load_dh_file(gnutls_dh_params_t dh_params, char *filename, bool isServerStart)
+{
+ FILE *fp;
+ char buffer[MAX_DH_FILE_SIZE];
+ gnutls_datum_t datum = {(unsigned char *) buffer};
+ int ret;
+
+ /* attempt to open file. It's not an error if it doesn't exist. */
+ if ((fp = AllocateFile(filename, "r")) == NULL)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+ return false;
+ }
+
+ datum.size = fread(buffer, sizeof(buffer[0]), sizeof(buffer), fp);
+
+ FreeFile(fp);
+
+ if (datum.size < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode_for_file_access(),
+ errmsg("could not load DH parameters file: %s",
+ gnutls_strerror(ret))));
+ return false;
+ }
+
+ ret = gnutls_dh_params_import_pkcs3(dh_params, &datum, GNUTLS_X509_FMT_PEM);
+
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load DH parameters file: %s",
+ gnutls_strerror(ret))));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Load hardcoded DH parameters.
+ *
+ * To prevent problems if the DH parameters files don't even
+ * exist, we can load DH parameters hardcoded into this file.
+ */
+static bool
+load_dh_buffer(gnutls_dh_params_t dh_params, const char *buffer, size_t len, bool isServerStart)
+{
+ gnutls_datum_t datum = {(unsigned char *) buffer, len};
+ int ret;
+
+ ret = gnutls_dh_params_import_pkcs3(dh_params, &datum, GNUTLS_X509_FMT_PEM);
+
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg_internal("DH load buffer: %s", gnutls_strerror(ret))));
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION
+/*
+ * PIN callback
+ */
+static int
+pin_function(void *userdata, int attempt,
+ const char *token_url,
+ const char *token_label,
+ unsigned int flags,
+ char *pin, size_t pin_max)
+{
+ simple_prompt(token_url, pin, pin_max, false);
+ return 0;
+}
+
+/*
+ * Dummy PIN callback during server reload
+ *
+ * The standard callback is no good during a postmaster SIGHUP cycle, not to
+ * mention SSL context reload in an EXEC_BACKEND postmaster child. So
+ * override it with this dummy function that just returns an error,
+ * guaranteeing failure.
+ */
+static int
+dummy_pin_function(void *userdata, int attempt,
+ const char *token_url,
+ const char *token_label,
+ unsigned int flags,
+ char *pin, size_t pin_max)
+{
+ /* Set flag to change the error message we'll report */
+ pin_function_called = true;
+ return -1;
+}
+#endif
+
+/*
+ * Certificate verification callback
+ *
+ * This callback is where we verify the identity of the client.
+ */
+static int
+verify_cb(gnutls_session_t ssl)
+{
+ unsigned int status;
+ int ret;
+
+ ret = gnutls_certificate_verify_peers2(ssl, &status);
+
+ if (ret == GNUTLS_E_NO_CERTIFICATE_FOUND)
+ return 0;
+ else if (ret < 0)
+ return ret;
+
+ return status;
+}
+
+/*
+ * Set DH parameters for generating ephemeral DH keys. The
+ * DH parameters can take a long time to compute, so they must be
+ * precomputed.
+ *
+ * Since few sites will bother to create a parameter file, we also
+ * also provide a fallback to the parameters provided by the
+ * OpenSSL project.
+ *
+ * These values can be static (once loaded or computed) since the
+ * OpenSSL library can efficiently generate random keys from the
+ * information provided.
+ */
+static bool
+initialize_dh(gnutls_dh_params_t *dh_params, bool isServerStart)
+{
+ bool loaded = false;
+ int ret;
+
+ ret = gnutls_dh_params_init(dh_params);
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg_internal("DH init error: %s",
+ gnutls_strerror(ret))));
+ return false;
+ }
+
+ if (ssl_dh_params_file[0])
+ loaded = load_dh_file(*dh_params, ssl_dh_params_file, isServerStart);
+ if (!loaded)
+ loaded = load_dh_buffer(*dh_params, FILE_DH2048, sizeof(FILE_DH2048), isServerStart);
+ if (!loaded)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ (errmsg("DH: could not load DH parameters"))));
+ return false;
+ }
+
+ return true;
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ if (port->ssl)
+ return gnutls_cipher_get_key_size(gnutls_cipher_get(port->ssl)) * 8;
+ else
+ return 0;
+}
+
+bool
+be_tls_get_compression(Port *port)
+{
+ if (port->ssl)
+ {
+ gnutls_compression_method_t comp = gnutls_compression_get(port->ssl);
+
+ return comp != GNUTLS_COMP_UNKNOWN && comp != GNUTLS_COMP_NULL;
+ }
+ else
+ return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+ if (port->ssl)
+ return gnutls_protocol_get_name(gnutls_protocol_get_version(port->ssl));
+ else
+ return NULL;
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ if (port->ssl)
+ return gnutls_cipher_get_name(gnutls_cipher_get(port->ssl));
+ else
+ return NULL;
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ if (port->peer)
+ {
+ int ret;
+
+ ret = gnutls_x509_crt_get_dn_by_oid(port->peer,
+ GNUTLS_OID_X520_COMMON_NAME,
+ 0, 0, ptr, &len);
+
+ if (ret != 0)
+ ptr[0] = '\0';
+ }
+ else
+ ptr[0] = '\0';
+}
+
+char *
+be_tls_get_peer_finished(Port *port, size_t *len)
+{
+ gnutls_datum_t cb;
+ int ret;
+ char *result;
+
+ ret = gnutls_session_channel_binding(port->ssl, GNUTLS_CB_TLS_UNIQUE, &cb);
+
+ if (ret < 0)
+ ereport(FATAL,
+ (errmsg("could not get SSL channel binding data: %s",
+ gnutls_strerror(ret))));
+
+ result = palloc(cb.size);
+ memcpy(result, cb.data, cb.size);
+ *len = cb.size;
+
+ return result;
+}
+
+/*
+ * Stub function for SCRAM channel binding type tls-server-end-point,
+ * currently not supported with GnuTLS.
+ */
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding type \"tls-server-end-point\" is not supported by this build")));
+ return NULL;
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index e1ddfb3c16..f2aeb58a30 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -212,10 +212,8 @@ be_tls_init(bool isServerStart)
}
}
- /*----------
+ /*
* Load the Certificate Revocation List (CRL).
- * http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,00.html
- *----------
*/
if (ssl_crl_file[0])
{
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 76c0a9e39b..624765aec3 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -40,6 +40,7 @@
#include "storage/proc.h"
+char *ssl_library;
char *ssl_cert_file;
char *ssl_key_file;
char *ssl_ca_file;
@@ -59,6 +60,9 @@ char *SSLECDHCurve;
/* GUC variable: if false, prefer client ciphers */
bool SSLPreferServerCiphers;
+/* GUC variable controlling GnuTLS priorities */
+char *gnutls_priority;
+
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
/* ------------------------------------------------------------ */
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index acf625e4ec..62531c990b 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1016,7 +1016,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-gnutls or --with-openssl to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a4f9b3668e..88ce482d3c 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3542,6 +3542,25 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, NULL, NULL
},
+ {
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Name of the SSL library."),
+ NULL,
+ GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library,
+#ifdef USE_SSL
+#if defined(USE_OPENSSL)
+ "OpenSSL",
+#elif defined(USE_GNUTLS)
+ "GnuTLS",
+#else
+#error SSL implementation must set ssl_library string
+#endif
+#endif
+ NULL, NULL, NULL
+ },
+
{
{"ssl_cert_file", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Location of the SSL server certificate file."),
@@ -3644,6 +3663,25 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"gnutls_priority", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Sets priorities for the cipher suites supported by GnuTLS."),
+ NULL,
+ GUC_SUPERUSER_ONLY
+ },
+ &gnutls_priority,
+#ifdef USE_SSL
+#if HAVE_DECL_GNUTLS_ALPN_SERVER_PRECEDENCE
+ "NORMAL:%SERVER_PRECEDENCE",
+#else
+ "NORMAL",
+#endif
+#else
+ "none",
+#endif
+ NULL, NULL, NULL
+ },
+
{
{"ssl_dh_params_file", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Location of the SSL DH parameters file."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 39272925fb..45f892b968 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -100,10 +100,15 @@
#ssl_cert_file = 'server.crt'
#ssl_crl_file = ''
#ssl_key_file = 'server.key'
+#ssl_dh_params_file = ''
+
+# Parameters for OpenSSL. Leave these commented out if not using OpenSSL.
#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
#ssl_prefer_server_ciphers = on
#ssl_ecdh_curve = 'prime256v1'
-#ssl_dh_params_file = ''
+
+# Parameters for GnuTLS. Leave these commented out if not using GnuTLS.
+#gnutls_priority = 'NORMAL:%SERVER_PRECEDENCE'
#------------------------------------------------------------------------------
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..cb1c91373a 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -45,7 +45,9 @@ OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
username.o wait_error.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_gnutls),yes)
+OBJS_COMMON += sha2_gnutls.o
+else ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
else
OBJS_COMMON += sha2.o
diff --git a/src/common/sha2_gnutls.c b/src/common/sha2_gnutls.c
new file mode 100644
index 0000000000..279b5370fa
--- /dev/null
+++ b/src/common/sha2_gnutls.c
@@ -0,0 +1,99 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2_gnutlsl.c
+ * Set of wrapper routines on top of GnuTLS to support SHA-224
+ * SHA-256, SHA-384 and SHA-512 functions.
+ *
+ * This should only be used if code is compiled with GnuTLS support.
+ *
+ * Portions Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2_gnutls.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/sha2.h"
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ gnutls_hash_init(ctx, GNUTLS_DIG_SHA256);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ gnutls_hash(*ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ gnutls_hash_deinit(*ctx, dest);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ gnutls_hash_init(ctx, GNUTLS_DIG_SHA512);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ gnutls_hash(*ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ gnutls_hash_deinit(*ctx, dest);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ gnutls_hash_init(ctx, GNUTLS_DIG_SHA384);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ gnutls_hash(*ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ gnutls_hash_deinit(*ctx, dest);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ gnutls_hash_init(ctx, GNUTLS_DIG_SHA224);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ gnutls_hash(*ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ gnutls_hash_deinit(*ctx, dest);
+}
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index f3fd0d0d28..b064554a3e 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,8 +50,11 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_OPENSSL)
#include <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
+#if defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
typedef SHA512_CTX pg_sha384_ctx;
+#elif defined(USE_GNUTLS)
+typedef gnutls_hash_hd_t pg_sha256_ctx;
+typedef gnutls_hash_hd_t pg_sha512_ctx;
+typedef gnutls_hash_hd_t pg_sha224_ctx;
+typedef gnutls_hash_hd_t pg_sha384_ctx;
#else
typedef struct pg_sha256_ctx
{
@@ -89,7 +97,7 @@ typedef struct pg_sha512_ctx
} pg_sha512_ctx;
typedef struct pg_sha256_ctx pg_sha224_ctx;
typedef struct pg_sha512_ctx pg_sha384_ctx;
-#endif /* USE_SSL */
+#endif
/* Interface routines for SHA224/256/384/512 */
extern void pg_sha224_init(pg_sha224_ctx *ctx);
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7698cd1f88..0208ac6f3f 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -19,9 +19,11 @@
#define LIBPQ_BE_H
#include <sys/time.h>
-#ifdef USE_OPENSSL
+#if defined(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
+#if defined(USE_OPENSSL)
SSL *ssl;
X509 *peer;
+#elif defined(USE_GNUTLS)
+ gnutls_session_t ssl;
+ gnutls_x509_crt_t peer;
#endif
} Port;
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 255222acd7..b7e5d41344 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -75,6 +75,7 @@ extern int pq_putbytes(const char *s, size_t len);
/*
* prototypes for functions in be-secure.c
*/
+extern char *ssl_library;
extern char *ssl_cert_file;
extern char *ssl_key_file;
extern char *ssl_ca_file;
@@ -100,5 +101,6 @@ extern WaitEventSet *FeBeWaitSet;
extern char *SSLCipherSuites;
extern char *SSLECDHCurve;
extern bool SSLPreferServerCiphers;
+extern char *gnutls_priority;
#endif /* LIBPQ_H */
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index f98f773ff0..023b68bee2 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -134,6 +134,14 @@
don't. */
#undef HAVE_DECL_F_FULLFSYNC
+/* Define to 1 if you have the declaration of `GNUTLS_ALPN_SERVER_PRECEDENCE',
+ and to 0 if you don't. */
+#undef HAVE_DECL_GNUTLS_ALPN_SERVER_PRECEDENCE
+
+/* Define to 1 if you have the declaration of `GNUTLS_X509_CRT_LIST_SORT', and
+ to 0 if you don't. */
+#undef HAVE_DECL_GNUTLS_X509_CRT_LIST_SORT
+
/* Define to 1 if you have the declaration of `posix_fadvise', and to 0 if you
don't. */
#undef HAVE_DECL_POSIX_FADVISE
@@ -256,6 +264,9 @@
/* Define to 1 if you have the `gettimeofday' function. */
#undef HAVE_GETTIMEOFDAY
+/* Define to 1 if you have the `gnutls_pkcs11_set_pin_function' function. */
+#undef HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION
+
/* Define to 1 if you have the <gssapi/gssapi.h> header file. */
#undef HAVE_GSSAPI_GSSAPI_H
@@ -834,6 +845,12 @@
(--enable-float8-byval) */
#undef USE_FLOAT8_BYVAL
+/* Define to build with GnuTLS support. (--with-gnutls) */
+#undef USE_GNUTLS
+
+/* Define to use GnuTLS for random number generation */
+#undef USE_GNUTLS_RANDOM
+
/* Define to build with ICU support. (--with-icu) */
#undef USE_ICU
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index b309395f11..e2ff2ce693 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -165,7 +165,7 @@
* implementation. (Currently, only OpenSSL is supported, but we might add
* more implementations in the future.)
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_GNUTLS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index 5c232ae2d1..371800f0c7 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -27,6 +27,7 @@
/base64.c
/scram-common.c
/sha2.c
+/sha2_gnutls.c
/sha2_openssl.c
/saslprep.c
/unicode_norm.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index abe0a50e98..b5958daf1d 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -51,7 +51,9 @@ OBJS += encnames.o wchar.o
# src/common
OBJS += base64.o ip.o md5.o scram-common.o saslprep.o unicode_norm.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_gnutls),yes)
+OBJS += fe-secure-gnutls.o fe-secure-common.o sha2_gnutls.o
+else ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o fe-secure-common.o sha2_openssl.o
else
OBJS += sha2.o
@@ -78,12 +80,12 @@ endif
# shared library link. (The order in which you list them here doesn't
# matter.)
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lgnutls -lsocket -lnsl -lresolv -lintl, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lgnutls -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
ifeq ($(PORTNAME), win32)
-SHLIB_LINK += -lshell32 -lws2_32 -lsecur32 $(filter -leay32 -lssleay32 -lcomerr32 -lkrb5_32, $(LIBS))
+SHLIB_LINK += -lshell32 -lws2_32 -lsecur32 $(filter -leay32 -lssleay32 -lgnutls -lcomerr32 -lkrb5_32, $(LIBS))
endif
SHLIB_EXPORTS = exports.txt
@@ -106,7 +108,7 @@ backend_src = $(top_srcdir)/src/backend
chklocale.c crypt.c erand48.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c strnlen.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
rm -f $@ && $(LN_S) $< .
-ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm.c: % : $(top_srcdir)/src/common/%
+ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c sha2_gnutls.c saslprep.c unicode_norm.c: % : $(top_srcdir)/src/common/%
rm -f $@ && $(LN_S) $< .
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
@@ -156,7 +158,7 @@ clean distclean: clean-lib
rm -f pg_config_paths.h
# Remove files we (may have) symlinked in from src/port and other places
rm -f chklocale.c crypt.c erand48.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c strnlen.c thread.c win32error.c win32setlocale.c
- rm -f ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm.c
+ rm -f ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c sha2_gnutls.c saslprep.c unicode_norm.c
rm -f encnames.c wchar.c
maintainer-clean: distclean maintainer-clean-lib
diff --git a/src/interfaces/libpq/fe-secure-gnutls.c b/src/interfaces/libpq/fe-secure-gnutls.c
new file mode 100644
index 0000000000..8a82edb6c3
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gnutls.c
@@ -0,0 +1,836 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-gnutls.c
+ * GnuTLS support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-gnutls.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+
+#include <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 int initialize_SSL(PGconn *conn);
+static PostgresPollingStatusType open_client_SSL(PGconn *);
+
+static ssize_t my_sock_read(gnutls_transport_ptr_t h, void *buf, size_t size);
+static ssize_t my_sock_write(gnutls_transport_ptr_t h, const void *buf, size_t size);
+static int get_peer_certificate(gnutls_session_t ssl, gnutls_x509_crt_t *peer);
+static int verify_cb(gnutls_session_t ssl);
+
+static bool ssl_lib_initialized = false;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* not used with GnuTLS */
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ /* First time through? */
+ if (conn->ssl == NULL)
+ {
+ /*
+ * Create a connection-specific SSL object, and load client
+ * certificate, private key, and trusted CA certs.
+ */
+ if (initialize_SSL(conn) != 0)
+ {
+ /* initialize_SSL already put a message in conn->errorMessage */
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /* Begin or continue the actual handshake */
+ return open_client_SSL(conn);
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ ssize_t n;
+ int result_errno;
+ char sebuf[256];
+
+ n = gnutls_record_recv(conn->ssl, ptr, len);
+
+ if (n > 0)
+ {
+ SOCK_ERRNO_SET(0);
+ return n;
+ }
+
+ switch (n)
+ {
+ case 0:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ result_errno = ECONNRESET;
+ n = -1;
+ break;
+ case GNUTLS_E_REHANDSHAKE:
+ /* Ignore re-handsake requests and have the caller retry */
+ case GNUTLS_E_INTERRUPTED:
+ result_errno = EINTR;
+ n = -1;
+ break;
+ case GNUTLS_E_AGAIN:
+ result_errno = EAGAIN;
+ n = -1;
+ break;
+#ifdef GNUTLS_E_PREMATURE_TERMINATION
+ case GNUTLS_E_PREMATURE_TERMINATION:
+#endif
+ case GNUTLS_E_PUSH_ERROR:
+ result_errno = SOCK_ERRNO;
+ n = -1;
+ if (result_errno == EPIPE || result_errno == ECONNRESET)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(
+ "server closed the connection unexpectedly\n"
+ "\tThis probably means the server terminated abnormally\n"
+ "\tbefore or while processing the request.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL SYSCALL error: %s\n"),
+ SOCK_STRERROR(result_errno,
+ sebuf, sizeof(sebuf)));
+ break;
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s\n"),
+ gnutls_strerror(n));
+ /* assume the connection is broken */
+ result_errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+
+ /* ensure we return the intended errno to caller */
+ SOCK_ERRNO_SET(result_errno);
+
+ return n;
+}
+
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return gnutls_record_check_pending(conn->ssl);
+}
+
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ ssize_t n;
+ int result_errno;
+ char sebuf[256];
+
+ n = gnutls_record_send(conn->ssl, ptr, len);
+
+ if (n >= 0)
+ {
+ SOCK_ERRNO_SET(0);
+ return n;
+ }
+
+ switch (n)
+ {
+ case GNUTLS_E_INTERRUPTED:
+ result_errno = EINTR;
+ n = -1;
+ break;
+ case GNUTLS_E_AGAIN:
+ result_errno = EAGAIN;
+ n = -1;
+ break;
+#ifdef GNUTLS_E_PREMATURE_TERMINATION
+ case GNUTLS_E_PREMATURE_TERMINATION:
+#endif
+ case GNUTLS_E_PUSH_ERROR:
+ result_errno = SOCK_ERRNO;
+ n = -1;
+ if (result_errno == EPIPE || result_errno == ECONNRESET)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(
+ "server closed the connection unexpectedly\n"
+ "\tThis probably means the server terminated abnormally\n"
+ "\tbefore or while processing the request.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL SYSCALL error: %s\n"),
+ SOCK_STRERROR(result_errno,
+ sebuf, sizeof(sebuf)));
+ break;
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s\n"),
+ gnutls_strerror(n));
+ /* assume the connection is broken */
+ result_errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+
+ /* ensure we return the intended errno to caller */
+ SOCK_ERRNO_SET(result_errno);
+
+ return n;
+}
+
+char *
+pgtls_get_finished(PGconn *conn, size_t *len)
+{
+ gnutls_datum_t cb;
+ int ret;
+ char *result;
+
+ ret = gnutls_session_channel_binding(conn->ssl, GNUTLS_CB_TLS_UNIQUE, &cb);
+
+ if (ret < 0)
+ return NULL;
+
+ result = malloc(cb.size);
+ if (result == NULL)
+ return NULL;
+ memcpy(result, cb.data, cb.size);
+ *len = cb.size;
+
+ return result;
+}
+
+/*
+ * Stub function for SCRAM channel binding type tls-server-end-point,
+ * currently not supported with GnuTLS.
+ */
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding type \"tls-server-end-point\" is not supported by this build\n"));
+ return NULL;
+}
+
+/* ------------------------------------------------------------ */
+/* GnuTLS specific code */
+/* ------------------------------------------------------------ */
+
+
+
+#define MAX_CN 256
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char namedata[MAX_CN];
+ size_t namelen;
+ int i;
+ int ret;
+ int rc = 0;
+
+ /*
+ * First, get the Subject Alternative Names (SANs) from the certificate,
+ * and compare them against the originally given hostname.
+ */
+ for (i = 0;; i++)
+ {
+ namelen = sizeof(namedata);
+ ret = gnutls_x509_crt_get_subject_alt_name(conn->peer, i,
+ namedata,
+ &namelen,
+ NULL);
+
+ if (ret < 0)
+ break;
+
+ if (ret == GNUTLS_SAN_DNSNAME)
+ {
+ char *alt_name = NULL;
+
+ (*names_examined)++;
+
+ rc = pq_verify_peer_name_matches_certificate_name(conn, namedata, namelen, &alt_name);
+
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+ }
+
+ if (rc != 0)
+ break;
+ }
+
+ /*
+ * If there is no subjectAltName extension of type dNSName, check the
+ * Common Name.
+ *
+ * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
+ * dNSName is present, the CN must be ignored.)
+ */
+ if (*names_examined == 0)
+ {
+ namelen = sizeof(namedata);
+ ret = gnutls_x509_crt_get_dn_by_oid(conn->peer, GNUTLS_OID_X520_COMMON_NAME, 0, 0, namedata, &namelen);
+
+ if (ret >= 0)
+ {
+ (*names_examined)++;
+ rc = pq_verify_peer_name_matches_certificate_name(conn, namedata, namelen, first_name);
+ }
+ }
+
+ return rc;
+}
+
+/*
+ * Initialize SSL library.
+ *
+ * In threadsafe mode, this includes setting up libcrypto callback functions
+ * to do thread locking.
+ */
+int
+pgtls_init(PGconn *conn)
+{
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* Also see similar code in fe-connect.c, default_threadlock() */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop, another thread own the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ return -1;
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ return -1;
+#endif /* ENABLE_THREAD_SAFETY */
+
+ if (!ssl_lib_initialized)
+ {
+ gnutls_global_init();
+ ssl_lib_initialized = true;
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+ return 0;
+}
+
+/*
+ * Create per-connection SSL object, and load the client certificate,
+ * private key, and trusted CA certs.
+ *
+ * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
+ */
+static int
+initialize_SSL(PGconn *conn)
+{
+ gnutls_certificate_credentials_t creds;
+ int ret;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ char keybuf[MAXPGPATH];
+ char sebuf[256];
+ bool have_homedir;
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the
+ * files could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0) ||
+ !(conn->sslcrl && strlen(conn->sslcrl) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ ret = gnutls_certificate_allocate_credentials(&creds);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL credentials: %s\n"),
+ gnutls_strerror(ret));
+ return -1;
+ }
+
+ /*
+ * If the root cert file exists, load it so we can perform certificate
+ * verification. If sslmode is "verify-full" we will also do further
+ * verification after the connection has been completed.
+ */
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0' &&
+ stat(fnbuf, &buf) == 0)
+ {
+ ret = gnutls_certificate_set_x509_trust_file(creds, fnbuf, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not read root certificate file \"%s\": %s\n"),
+ fnbuf, gnutls_strerror(ret));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+
+ gnutls_certificate_set_verify_function(creds, verify_cb);
+
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ strlcpy(fnbuf, conn->sslcrl, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0' && stat(fnbuf, &buf) == 0)
+ {
+ ret = gnutls_certificate_set_x509_crl_file(creds, fnbuf, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not read crl file \"%s\": %s\n"),
+ fnbuf, gnutls_strerror(ret));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual case
+ * that it seems worth having a specialized error message for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+ }
+
+ /* Read the client certificate file */
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] == '\0')
+ {
+ /* no home directory, proceed without a client cert */
+ }
+ else if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * If file is not present, just go on without a client cert; server
+ * might or might not accept the connection. Any other error,
+ * however, is grounds for complaint.
+ */
+ if (errno != ENOENT && errno != ENOTDIR)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not open certificate file \"%s\": %s\n"),
+ fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(keybuf, conn->sslkey, sizeof(keybuf));
+ else if (have_homedir)
+ snprintf(keybuf, sizeof(keybuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ keybuf[0] = '\0';
+
+ if (keybuf[0] != '\0')
+ {
+ if (stat(keybuf, &buf) != 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but not private key file \"%s\"\n"),
+ keybuf);
+ return -1;
+ }
+#ifndef WIN32
+ if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
+ keybuf);
+ return -1;
+ }
+#endif
+ }
+
+ ret = gnutls_certificate_set_x509_key_file(creds, fnbuf, keybuf, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not read certificate and key files \"%s\" \"%s\": %s\n"),
+ fnbuf, keybuf, gnutls_strerror(ret));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+ }
+
+ ret = gnutls_init(&conn->ssl, GNUTLS_CLIENT);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ gnutls_strerror(ret));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+
+ gnutls_priority_set_direct(conn->ssl, "NORMAL", NULL);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ gnutls_strerror(ret));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+
+ ret = gnutls_credentials_set(conn->ssl, GNUTLS_CRD_CERTIFICATE, creds);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ gnutls_strerror(ret));
+ gnutls_deinit(conn->ssl);
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+
+ gnutls_transport_set_ptr(conn->ssl, conn);
+ gnutls_transport_set_pull_function(conn->ssl, my_sock_read);
+ gnutls_transport_set_push_function(conn->ssl, my_sock_write);
+
+ conn->ssl_in_use = true;
+
+ return 0;
+}
+
+/*
+ * Attempt to negotiate SSL connection.
+ */
+static PostgresPollingStatusType
+open_client_SSL(PGconn *conn)
+{
+ int ret;
+
+ do
+ {
+ ret = gnutls_handshake(conn->ssl);
+ }
+ while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s\n"),
+ gnutls_strerror(ret));
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * We already checked the server certificate in gnutls_handshake() using
+ * verify_cb(), if root.crt exists.
+ */
+
+ /* get server certificate */
+ ret = get_peer_certificate(conn->ssl, &conn->peer);
+ if (conn->peer == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate could not be obtained: %s\n"),
+ gnutls_strerror(ret));
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* SSL handshake is complete */
+ return PGRES_POLLING_OK;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->ssl)
+ {
+ gnutls_bye(conn->ssl, GNUTLS_SHUT_RDWR);
+ gnutls_deinit(conn->ssl);
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+ }
+
+ if (conn->peer)
+ {
+ gnutls_x509_crt_deinit(conn->peer);
+ conn->peer = NULL;
+ }
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Return pointer to OpenSSL object, which is none for GnuTLS.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "GnuTLS") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "compression",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ if (!conn)
+ return NULL;
+ if (conn->ssl == NULL)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "GnuTLS";
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char sslbits_str[10];
+ int sslbytes;
+
+ sslbytes = gnutls_cipher_get_key_size(gnutls_cipher_get(conn->ssl));
+
+ if (sslbytes == 0)
+ return NULL;
+
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", sslbytes * 8);
+ return sslbits_str;
+ }
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return gnutls_cipher_get_name(gnutls_cipher_get(conn->ssl));
+
+ if (strcmp(attribute_name, "compression") == 0)
+ {
+ gnutls_compression_method_t comp = gnutls_compression_get(conn->ssl);
+
+ if (comp == GNUTLS_COMP_NULL || comp == GNUTLS_COMP_UNKNOWN)
+ return "off";
+ else
+ return "on";
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return gnutls_protocol_get_name(gnutls_protocol_get_version(conn->ssl));
+
+ return NULL; /* unknown attribute */
+}
+
+/*
+ * Private substitute transport layer: this does the sending and receiving using
+ * pqsecure_raw_write() and pqsecure_raw_read() instead, to allow those
+ * functions to disable SIGPIPE and give better error messages on I/O errors.
+ */
+
+static ssize_t
+my_sock_read(gnutls_transport_ptr_t conn, void *buf, size_t size)
+{
+ return pqsecure_raw_read((PGconn *) conn, buf, size);
+}
+
+static ssize_t
+my_sock_write(gnutls_transport_ptr_t conn, const void *buf, size_t size)
+{
+ return pqsecure_raw_write((PGconn *) conn, buf, size);
+}
+
+#if !HAVE_DECL_GNUTLS_X509_CRT_LIST_SORT
+/*
+ * GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ * certificate chains, so we skip doing so in these earlier versions.
+ */
+#define GNUTLS_X509_CRT_LIST_SORT 0
+#endif
+
+/*
+ * Get peer certificate from a session
+ *
+ * Returns GNUTLS_E_NO_CERTIFICATE_FOUND when not x509 certifcate was found.
+ */
+static int
+get_peer_certificate(gnutls_session_t ssl, gnutls_x509_crt_t *peer)
+{
+ if (gnutls_certificate_type_get(ssl) == GNUTLS_CRT_X509)
+ {
+ unsigned int n;
+ int ret;
+ gnutls_datum_t const *raw_certs;
+ gnutls_x509_crt_t *certs;
+
+ raw_certs = gnutls_certificate_get_peers(ssl, &n);
+
+ if (n == 0)
+ return GNUTLS_E_NO_CERTIFICATE_FOUND;
+
+ certs = malloc(n * sizeof(gnutls_x509_crt_t));
+ if (!certs)
+ return GNUTLS_E_NO_CERTIFICATE_FOUND;
+
+ ret = gnutls_x509_crt_list_import(certs, &n, raw_certs,
+ GNUTLS_X509_FMT_DER,
+ GNUTLS_X509_CRT_LIST_SORT);
+
+ if (ret >= 1)
+ {
+ unsigned int i;
+
+ for (i = 1; i < ret; i++)
+ gnutls_x509_crt_deinit(certs[i]);
+
+ *peer = certs[0];
+
+ ret = GNUTLS_E_SUCCESS;
+ }
+ else if (ret == 0)
+ ret = GNUTLS_E_NO_CERTIFICATE_FOUND;
+
+ free(certs);
+
+ return ret;
+ }
+
+ return GNUTLS_E_NO_CERTIFICATE_FOUND;
+}
+
+/*
+ * Certificate verification callback
+ *
+ * This callback is where we verify the identity of the server.
+ */
+static int
+verify_cb(gnutls_session_t ssl)
+{
+ unsigned int status;
+ int ret;
+
+ ret = gnutls_certificate_verify_peers2(ssl, &status);
+ if (ret < 0)
+ return ret;
+
+ return status;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index cfb77f6d85..37bb86044a 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -139,7 +139,7 @@ PQsslInUse(PGconn *conn)
/*
* Exported function to allow application to tell us it's already
- * initialized OpenSSL.
+ * initialized the SSL library.
*/
void
PQinitSSL(int do_init)
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index ed9c806861..68e4053056 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -339,7 +339,7 @@ extern const char *const *PQsslAttributeNames(PGconn *conn);
* unencrypted connections or if any other TLS library is in use. */
extern void *PQgetssl(PGconn *conn);
-/* Tell libpq whether it needs to initialize OpenSSL */
+/* Tell libpq whether it needs to initialize the SSL library */
extern void PQinitSSL(int do_init);
/* More detailed way to tell libpq whether it needs to initialize OpenSSL */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index eba23dcecc..16b7676703 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -71,14 +71,15 @@ typedef struct
#endif
#endif /* ENABLE_SSPI */
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
#include <openssl/ssl.h>
#include <openssl/err.h>
-
#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.
@@ -461,7 +462,7 @@ struct pg_conn
bool allow_ssl_try; /* Allowed to try SSL negotiation */
bool wait_ssl_try; /* Delay SSL negotiation until after
* attempting normal connection */
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
SSL *ssl; /* SSL status, if have SSL connection */
X509 *peer; /* X509 cert of server */
#ifdef USE_SSL_ENGINE
@@ -470,7 +471,10 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
+#elif defined(USE_GNUTLS)
+ gnutls_session_t ssl; /* SSL status, if have SSL connection */
+ gnutls_x509_crt_t peer; /* X509 cert of server */
+#endif
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index bc7a8aacb9..73c02251ad 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -24,8 +24,11 @@
#include <unistd.h>
#include <sys/time.h>
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
#include <openssl/rand.h>
+#elif defined(USE_GNUTLS)
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
#endif
#ifdef WIN32
#include <wincrypt.h>
@@ -85,8 +88,9 @@ random_from_file(char *filename, void *buf, size_t len)
* We support a number of sources:
*
* 1. OpenSSL's RAND_bytes()
- * 2. Windows' CryptGenRandom() function
- * 3. /dev/urandom
+ * 2. GnuTLS's gnutls_rnd()
+ * 3. Windows' CryptGenRandom() function
+ * 4. /dev/urandom
*
* The configure script will choose which one to use, and set
* a USE_*_RANDOM flag accordingly.
@@ -107,6 +111,14 @@ pg_strong_random(void *buf, size_t len)
return true;
return false;
+ /*
+ * When built with GnuTLS, use GnuTLS's gnutls_rnd function.
+ */
+#elif defined(USE_GNUTLS_RANDOM)
+ if (gnutls_rnd(GNUTLS_RND_RANDOM, buf, len) == 0)
+ return true;
+ return false;
+
/*
* Windows has CryptoAPI for strong cryptographic numbers.
*/
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..a6d41c7e46 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(filter yes,$(with_openssl) $(with_gnutls)),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 5cd2c5a404..5b166dc433 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_openssl with_gnutls
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 34df5e9dbb..1169b5e608 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -6,15 +6,13 @@
use ServerSetup;
use File::Copy;
-if ($ENV{with_openssl} eq 'yes')
-{
- plan tests => 62;
-}
-else
+if ($ENV{with_openssl} ne 'yes' && $ENV{with_gnutls} ne 'yes')
{
plan skip_all => 'SSL not supported by this build';
}
+my $number_of_tests = 61;
+
#### Some configuration
# This is the hostname used to connect to the server. This cannot be a
@@ -49,6 +47,15 @@
$ENV{PGHOST} = $node->host;
$ENV{PGPORT} = $node->port;
$node->start;
+
+# Run this before we lock down access below.
+my $result = $node->safe_psql('postgres', "SHOW ssl_library");
+my $expected;
+if ($ENV{'with_openssl'} eq 'yes') { $expected = 'OpenSSL'; }
+elsif ($ENV{'with_gnutls'} eq 'yes') { $expected = 'GnuTLS'; }
+else { $expected = ''; }
+is($result, $expected, 'ssl_library parameter');
+
configure_test_server_for_ssl($node, $SERVERHOSTADDR, 'trust');
switch_server_cert($node, 'server-cn-only');
@@ -94,12 +101,22 @@
qr/SSL error/,
"connect with wrong server root cert sslmode=verify-full");
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/,
- "connect with server CA cert, without root CA");
+# Try with just the server CA's cert. This fails with OpenSSL because
+# the root file must contain the whole chain up to the root CA.
+if ($ENV{'with_openssl'} eq 'yes')
+{
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/,
+ "connect with server CA cert, without root CA");
+ $number_of_tests++;
+}
+else
+{
+ test_connect_ok($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok($common_connstr,
@@ -128,11 +145,21 @@
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+if ($ENV{'with_openssl'} eq 'yes')
+{
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+ $number_of_tests++;
+}
+else
+{
+ test_connect_ok($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok($common_connstr,
@@ -282,10 +309,14 @@
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/,
+ ($ENV{'with_openssl'} eq 'yes' ?
+ qr/SSL error/ :
+ qr/connection requires a valid client certificate/),
"intermediate client certificate is missing");
# clean up
unlink("ssl/client_tmp.key",
"ssl/client_wrongperms_tmp.key",
"ssl/client-revoked_tmp.key");
+
+done_testing($number_of_tests);
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index a805a3196b..89d770a729 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -8,7 +8,7 @@
use ServerSetup;
use File::Copy;
-if ($ENV{with_openssl} ne 'yes')
+if ($ENV{with_openssl} ne 'yes' && $ENV{with_gnutls} ne 'yes')
{
plan skip_all => 'SSL not supported by this build';
}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 72976f44d8..29d768c7e7 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -119,6 +119,10 @@ sub mkvcbuild
{
push(@pgcommonallfiles, 'sha2_openssl.c');
}
+ elsif ($solution->{options}->{gnutls})
+ {
+ push(@pgcommonallfiles, 'sha2_gnutls.c');
+ }
else
{
push(@pgcommonallfiles, 'sha2.c');
@@ -246,6 +250,12 @@ sub mkvcbuild
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
$libpq->RemoveFile('src/common/sha2_openssl.c');
}
+ elsif (!$solution->{options}->{gnutls})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gnutls.c');
+ $libpq->RemoveFile('src/common/sha2_gnutls.c');
+ }
else
{
$libpq->RemoveFile('src/common/sha2.c');
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d4765ce3b0..ba2cf56e0c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2729,6 +2729,9 @@ ginxlogVacuumDataLeafPage
gistxlogPage
gistxlogPageSplit
gistxlogPageUpdate
+gnutls_datum_t
+gnutls_dh_params_t
+gnutls_x509_crt_t
grouping_sets_data
gseg_picksplit_item
gss_OID
base-commit: 04e7ecadf64d18f67ca1d0632d8ab71f120ca5e3
--
2.16.2
On 08/03/18 14:13, Peter Eisentraut wrote:
There are two failures in the SSL tests that I cannot explain. The
tests are for some rather obscure configurations, so the changed
behaviors are not obviously wrong, perhaps legitimate implementation
differences. But someone wrote those tests with a purpose (probably),
so we should have some kind of explanation for the regressions.
I applied this over commit 4e0c743c18 (because this doesn't compile
against current master, needs rebasing), and ran "make check" in
src/test/ssl. All the tests passed. I'm using GnuTLS version 3.5.8. What
failures did you see?
- Heikki
On 6/2/18 16:50, Heikki Linnakangas wrote:
On 08/03/18 14:13, Peter Eisentraut wrote:
There are two failures in the SSL tests that I cannot explain. The
tests are for some rather obscure configurations, so the changed
behaviors are not obviously wrong, perhaps legitimate implementation
differences. But someone wrote those tests with a purpose (probably),
so we should have some kind of explanation for the regressions.I applied this over commit 4e0c743c18 (because this doesn't compile
against current master, needs rebasing), and ran "make check" in
src/test/ssl. All the tests passed. I'm using GnuTLS version 3.5.8. What
failures did you see?
The patch adjusts the expected test results so that the tests pass.
Look for the tests named
- "connect with server CA cert, without root CA"
- "CRL belonging to a different CA"
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 3/8/18 20:13, Peter Eisentraut wrote:
In the thread about Secure Transport we agreed to move the consideration
of new SSL libraries to PG12.Here is my current patch, after all the refactorings.
The status is that it works fine and could be used.
There are two failures in the SSL tests that I cannot explain. The
tests are for some rather obscure configurations, so the changed
behaviors are not obviously wrong, perhaps legitimate implementation
differences. But someone wrote those tests with a purpose (probably),
so we should have some kind of explanation for the regressions.Other non-critical, nice-to-have issues:
- Do something about sslinfo, perhaps fold into pg_stat_ssl view.
- Do something about pgcrypto.
- Add tests for load_dh_file().
- Implement channel binding tls-server-end-point.
Also, ...
- Add ssl_passphrase_command support.
I'm moving this patch forward to CF 2018-09, since it's not going to be
ready for -07, and we're still whacking around some channel binding
details, which would potentially interfere with this patch.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 05/06/18 00:44, Peter Eisentraut wrote:
On 6/2/18 16:50, Heikki Linnakangas wrote:
On 08/03/18 14:13, Peter Eisentraut wrote:
There are two failures in the SSL tests that I cannot explain. The
tests are for some rather obscure configurations, so the changed
behaviors are not obviously wrong, perhaps legitimate implementation
differences. But someone wrote those tests with a purpose (probably),
so we should have some kind of explanation for the regressions.I applied this over commit 4e0c743c18 (because this doesn't compile
against current master, needs rebasing), and ran "make check" in
src/test/ssl. All the tests passed. I'm using GnuTLS version 3.5.8. What
failures did you see?The patch adjusts the expected test results so that the tests pass.
Ah, gotcha.
Look for the tests named
- "connect with server CA cert, without root CA"
So, in this test, the client puts the server's certificate in
sslrootcert, but not the CA cert that the server's certificate was
signed with. OpenSSL doesn't accept that, but apparently GnuTLS is OK
with it.
I think the GnuTLS behavior is reasonable, I was actually surprised that
OpenSSL is so strict about that. If the user explicitly lists a server's
certificate as trusted, by putting it in sslrootcert, it seems
reasonable to accept it even if the CA cert is missing.
- "CRL belonging to a different CA"
Hmm. So in OpenSSL, when we load the CRL, we call
X509_STORE_set_flags(cvstore, X509_V_FLAG_CRL_CHECK |
X509_V_FLAG_CRL_CHECK_ALL). With that option, if a CRL for the server CA
cannot be found (in this case, because the CRL is for a different CA),
OpenSSL throws an error. Apparently, GnuTLS is more lenient. At a quick
glance, I don't see an option in GnuTLS to change that behavior. But I
think we can live with it, it's not wrong per se, just different.
- Heikki
On Thu, Mar 08, 2018 at 02:13:51PM -0500, Peter Eisentraut wrote:
In the thread about Secure Transport we agreed to move the consideration
of new SSL libraries to PG12.Here is my current patch, after all the refactorings.
The status is that it works fine and could be used.
There are two failures in the SSL tests that I cannot explain. The
tests are for some rather obscure configurations, so the changed
behaviors are not obviously wrong, perhaps legitimate implementation
differences. But someone wrote those tests with a purpose (probably),
so we should have some kind of explanation for the regressions.
Patch v6 of this thread is failing to apply. Could you rebase?
--
Michael
On 20/08/2018 05:13, Michael Paquier wrote:
Patch v6 of this thread is failing to apply. Could you rebase?
attached
Changes in v7 since v6:
- Added support for ssl_passphrase_command.
- Test suite needed some adjustment because GnuTLS doesn't appear to
understand the previously used file format for encrypted keys.
- Removed tls-unique channel binding support. Support for
tls-server-end-point still needs to be added, but it could be a separate
patch.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v7-0001-GnuTLS-support.patchtext/plain; charset=UTF-8; name=v7-0001-GnuTLS-support.patch; x-mac-creator=0; x-mac-type=0Download
From 85465f4ad3e210a3948216ebc5c6fbd8c6993bdb Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 31 Aug 2018 13:00:26 +0200
Subject: [PATCH v7] GnuTLS support
---
configure | 166 +++-
configure.in | 37 +-
doc/src/sgml/client-auth.sgml | 2 +-
doc/src/sgml/config.sgml | 69 +-
doc/src/sgml/installation.sgml | 47 +-
doc/src/sgml/libpq.sgml | 54 +-
doc/src/sgml/runtime.sgml | 13 +-
doc/src/sgml/sslinfo.sgml | 1 +
src/Makefile.global.in | 1 +
src/backend/libpq/Makefile | 4 +-
src/backend/libpq/be-secure-gnutls.c | 795 +++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 4 +-
src/backend/libpq/be-secure.c | 3 +
src/backend/libpq/hba.c | 2 +-
src/backend/utils/misc/guc.c | 25 +
src/backend/utils/misc/postgresql.conf.sample | 11 +-
src/common/Makefile | 4 +-
src/common/sha2_gnutls.c | 99 +++
src/include/common/sha2.h | 14 +-
src/include/libpq/libpq-be.h | 15 +-
src/include/libpq/libpq.h | 1 +
src/include/pg_config.h.in | 17 +
src/include/pg_config_manual.h | 2 +-
src/interfaces/libpq/.gitignore | 1 +
src/interfaces/libpq/Makefile | 14 +-
src/interfaces/libpq/fe-secure-gnutls.c | 803 ++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 2 +-
src/interfaces/libpq/libpq-fe.h | 2 +-
src/interfaces/libpq/libpq-int.h | 14 +-
src/port/pg_strong_random.c | 18 +-
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 7 +-
src/test/ssl/ssl/server-password.key | 35 +-
src/test/ssl/t/001_ssltests.pl | 63 +-
src/test/ssl/t/002_scram.pl | 2 +-
src/tools/msvc/Mkvcbuild.pm | 10 +
src/tools/pgindent/typedefs.list | 3 +
37 files changed, 2248 insertions(+), 114 deletions(-)
create mode 100644 src/backend/libpq/be-secure-gnutls.c
create mode 100644 src/common/sha2_gnutls.c
create mode 100644 src/interfaces/libpq/fe-secure-gnutls.c
diff --git a/configure b/configure
index dd94c5bbab..564f33ae5d 100755
--- a/configure
+++ b/configure
@@ -707,6 +707,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_gnutls
with_openssl
with_ldap
with_krb_srvnam
@@ -853,6 +854,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_gnutls
with_selinux
with_systemd
with_readline
@@ -1553,6 +1555,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-gnutls build with GnuTLS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -7933,6 +7936,47 @@ fi
$as_echo "$with_openssl" >&6; }
+
+#
+# GnuTLS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with GnuTLS support" >&5
+$as_echo_n "checking whether to build with GnuTLS support... " >&6; }
+
+
+
+# Check whether --with-gnutls was given.
+if test "${with_gnutls+set}" = set; then :
+ withval=$with_gnutls;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_GNUTLS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-gnutls option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_gnutls=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_gnutls" >&5
+$as_echo "$with_gnutls" >&6; }
+
+
+if test "$with_openssl" = yes && test "$with_gnutls" = yes; then
+ as_fn_error $? "cannot specify both --with-openssl and --with-gnutls" "$LINENO" 5
+fi
+
+
#
# SELinux
#
@@ -12052,6 +12096,107 @@ done
fi
+if test "$with_gnutls" = yes ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing gnutls_init" >&5
+$as_echo_n "checking for library containing gnutls_init... " >&6; }
+if ${ac_cv_search_gnutls_init+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char gnutls_init ();
+int
+main ()
+{
+return gnutls_init ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' gnutls; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_gnutls_init=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_gnutls_init+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_gnutls_init+:} false; then :
+
+else
+ ac_cv_search_gnutls_init=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_gnutls_init" >&5
+$as_echo "$ac_cv_search_gnutls_init" >&6; }
+ac_res=$ac_cv_search_gnutls_init
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+else
+ as_fn_error $? "library 'gnutls' is required for GnuTLS" "$LINENO" 5
+fi
+
+ # GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ # certificate chains.
+ ac_fn_c_check_decl "$LINENO" "GNUTLS_ALPN_SERVER_PRECEDENCE" "ac_cv_have_decl_GNUTLS_ALPN_SERVER_PRECEDENCE" "#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+"
+if test "x$ac_cv_have_decl_GNUTLS_ALPN_SERVER_PRECEDENCE" = xyes; then :
+ ac_have_decl=1
+else
+ ac_have_decl=0
+fi
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_DECL_GNUTLS_ALPN_SERVER_PRECEDENCE $ac_have_decl
+_ACEOF
+ac_fn_c_check_decl "$LINENO" "GNUTLS_X509_CRT_LIST_SORT" "ac_cv_have_decl_GNUTLS_X509_CRT_LIST_SORT" "#include <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; }
@@ -12954,6 +13099,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
@@ -17835,9 +17991,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
@@ -17876,6 +18034,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 3280afa0da..29b203673b 100644
--- a/configure.in
+++ b/configure.in
@@ -822,6 +822,21 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+
+#
+# GnuTLS
+#
+AC_MSG_CHECKING([whether to build with GnuTLS support])
+PGAC_ARG_BOOL(with, gnutls, no, [build with GnuTLS support],
+ [AC_DEFINE([USE_GNUTLS], 1, [Define to build with GnuTLS support. (--with-gnutls)])])
+AC_MSG_RESULT([$with_gnutls])
+AC_SUBST(with_gnutls)
+
+if test "$with_openssl" = yes && test "$with_gnutls" = yes; then
+ AC_MSG_ERROR([cannot specify both --with-openssl and --with-gnutls])
+fi
+
+
#
# SELinux
#
@@ -1190,6 +1205,17 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_gnutls" = yes ; then
+ AC_SEARCH_LIBS(gnutls_init, gnutls, [], [AC_MSG_ERROR([library 'gnutls' is required for GnuTLS])])
+ # GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ # certificate chains.
+ AC_CHECK_DECLS([GNUTLS_ALPN_SERVER_PRECEDENCE, GNUTLS_X509_CRT_LIST_SORT], [], [],
+[#include <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
@@ -1340,6 +1366,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, [],
@@ -2139,9 +2169,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
@@ -2158,6 +2190,9 @@ if test "$enable_strong_random" = yes ; then
if test x"$USE_OPENSSL_RANDOM" = x"1" ; then
AC_DEFINE(USE_OPENSSL_RANDOM, 1, [Define to use OpenSSL for random number generation])
AC_MSG_RESULT([OpenSSL])
+ elif test x"$USE_GNUTLS_RANDOM" = x"1" ; then
+ AC_DEFINE(USE_GNUTLS_RANDOM, 1, [Define to use GnuTLS for random number generation])
+ AC_MSG_RESULT([GnuTLS])
elif test x"$USE_WIN32_RANDOM" = x"1" ; then
AC_DEFINE(USE_WIN32_RANDOM, 1, [Define to use native Windows API for random number generation])
AC_MSG_RESULT([Windows native])
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index c2114021c3..6a7d5bc3bc 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -1790,7 +1790,7 @@ <title>RADIUS Authentication</title>
<para>
The encryption vector used will only be cryptographically
strong if <productname>PostgreSQL</productname> is built with support for
- <productname>OpenSSL</productname>. In other cases, the transmission to the
+ an SSL library (e.g., <productname>OpenSSL</productname>). In other cases, the transmission to the
RADIUS server should only be considered obfuscated, not secured, and
external security measures should be applied if necessary.
</para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index bee4afbe4e..e9e4d06b72 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1156,6 +1156,37 @@ <title>SSL</title>
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-dh-params-file" xreflabel="ssl_dh_params_file">
+ <term><varname>ssl_dh_params_file</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_dh_params_file</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing Diffie-Hellman parameters
+ used for so-called ephemeral DH family of SSL ciphers. The default is
+ empty, in which case compiled-in default DH parameters used. Using
+ custom DH parameters reduces the exposure if an attacker manages to
+ crack the well-known compiled-in DH parameters. You can create your own
+ DH parameters file with the command
+ <command>openssl dhparam -out dhparams.pem 2048</command>.
+ </para>
+
+ <para>
+ This parameter can only be set in the <filename>postgresql.conf</filename>
+ file or on the server command line.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>
+ The following settings are only applicable if the
+ <productname>OpenSSL</productname> library is used.
+ </para>
+
+ <variablelist>
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
@@ -1289,27 +1320,36 @@ <title>SSL</title>
</para>
</listitem>
</varlistentry>
+ </variablelist>
- <varlistentry id="guc-ssl-dh-params-file" xreflabel="ssl_dh_params_file">
- <term><varname>ssl_dh_params_file</varname> (<type>string</type>)
+ <para>
+ The following settings are only applicable if the
+ <productname>GnuTLS</productname> library is used.
+ </para>
+
+ <variablelist>
+ <varlistentry id="guc-gnutls-priority" xreflabel="gnutls_priority">
+ <term><varname>gnutls_priority</varname> (<type>string</type>)
<indexterm>
- <primary><varname>ssl_dh_params_file</varname> configuration parameter</primary>
+ <primary><varname>gnutls_priority</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
- Specifies the name of the file containing Diffie-Hellman parameters
- used for so-called ephemeral DH family of SSL ciphers. The default is
- empty, in which case compiled-in default DH parameters used. Using
- custom DH parameters reduces the exposure if an attacker manages to
- crack the well-known compiled-in DH parameters. You can create your own
- DH parameters file with the command
- <command>openssl dhparam -out dhparams.pem 2048</command>.
+ Sets the priorities for the cipher suites supported by GnuTLS. This
+ can be used to specify which SSL cipher suites are allowed to be
+ used on secure connections and related settings. See the
+ documentation on GnuTLS priority strings for details.
+ This parameter can only be set in the <filename>postgresql.conf</filename>
+ file or on the server command line.
</para>
<para>
- This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ The default value is <literal>NORMAL:%SERVER_PRECEDENCE</literal> (or
+ just <literal>NORMAL</literal> with older
+ <productname>GnuTLS</productname> versions that don't support the
+ server precedence setting). The default is usually a reasonable
+ choice unless you have specific security requirements.
</para>
</listitem>
</varlistentry>
@@ -1374,6 +1414,7 @@ <title>SSL</title>
</listitem>
</varlistentry>
</variablelist>
+
</sect2>
</sect1>
@@ -8411,8 +8452,8 @@ <title>Preset Options</title>
<para>
Reports the name of the SSL library that this PostgreSQL server was
built with (even if SSL is not currently configured or in use on this
- instance), for example <literal>OpenSSL</literal>, or an empty string
- if none.
+ instance), for example <literal>OpenSSL</literal> or
+ <literal>GnuTLS</literal>, or an empty string if none.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 4487d0cfd1..e0231bdc5e 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -244,9 +244,9 @@ <title>Requirements</title>
<listitem>
<para>
- You need <productname>OpenSSL</productname>, if you want to support
- encrypted client connections. The minimum required version is
- 0.9.8.
+ If you want to support encrypted client connections (SSL/TLS), you need
+ <productname>OpenSSL</productname> (at least version 0.9.8) or
+ <productname>GnuTLS</productname>.
</para>
</listitem>
@@ -727,6 +727,29 @@ <title>Configuration</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--with-gnutls</option>
+ <indexterm>
+ <primary>GnuTLS</primary>
+ <seealso>SSL</seealso>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Build with support for <acronym>TLS</acronym>/<acronym>SSL</acronym>
+ (encrypted) connections using the <productname>GnuTLS</productname>
+ package. <filename>configure</filename> will check for the required
+ header files and libraries to make sure that your
+ <productname>GnuTLS</productname> installation is sufficient before
+ proceeding.
+ </para>
+
+ <para>
+ See also <option>--with-openssl</option> for an alternative.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--with-gssapi</option></term>
<listitem>
@@ -833,12 +856,16 @@ <title>Configuration</title>
</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.
+ Build with support for <acronym>SSL</acronym>/<acronym>TLS</acronym>
+ (encrypted) connections using the <productname>OpenSSL</productname>
+ package. <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.
+ </para>
+
+ <para>
+ See also <option>--with-gnutls</option> for an alternative.
</para>
</listitem>
</varlistentry>
@@ -2370,7 +2397,7 @@ <title>Cygwin</title>
<listitem>
<para>
- OpenSSL is not supported.
+ SSL is not supported.
</para>
</listitem>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 5e7931ba90..5ec3e78717 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1449,6 +1449,9 @@ <title>Parameter Key Words</title>
does not support disabling compression, so this parameter is ignored
with those versions, and whether compression is used depends on the
server.
+ <productname>GnuTLS</productname> does not support SSL compression,
+ and this parameter will be ignored if <productname>libpq</productname>
+ is using <productname>GnuTLS</productname>.
</para>
<para>
@@ -2201,8 +2204,8 @@ <title>Connection Status Functions</title>
<term><literal>library</literal></term>
<listitem>
<para>
- Name of the SSL implementation in use. (Currently, only
- <literal>"OpenSSL"</literal> is implemented)
+ Name of the SSL implementation in use, e.g.,
+ <literal>OpenSSL</literal> or <literal>GnuTLS</literal>
</para>
</listitem>
</varlistentry>
@@ -2271,11 +2274,11 @@ <title>Connection Status Functions</title>
<synopsis>
void *PQsslStruct(const PGconn *conn, const char *struct_name);
</synopsis>
+ The struct(s) available depend on the SSL implementation in use.
</para>
<para>
- The struct(s) available depend on the SSL implementation in use.
- For OpenSSL, there is one struct, available under the name "OpenSSL",
- and it returns a pointer to the OpenSSL <literal>SSL</literal> struct.
+ For OpenSSL, there is one struct, available under the name <literal>"OpenSSL"</literal>,
+ and it returns a pointer to the OpenSSL <type>SSL</type> struct.
To use this function, code along the following lines could be used:
<programlisting><![CDATA[
#include <libpq-fe.h>
@@ -2294,12 +2297,31 @@ <title>Connection Status Functions</title>
/* use OpenSSL functions to access ssl */
}
]]></programlisting>
- </para>
- <para>
This structure can be used to verify encryption levels, check server
certificates, and more. Refer to the <productname>OpenSSL</productname>
documentation for information about this structure.
</para>
+ <para>
+ Similarly, for GnuTLS, the <type>gnutls_session_t</type> can be
+ obtained under the name <literal>"GnuTLS"</literal>. For example:
+<programlisting><![CDATA[
+#include <libpq-fe.h>
+#include <gnutls/gnutls.h>
+
+...
+
+ gnutls_session_t ssl;
+
+ dbconn = PQconnectdb(...);
+ ...
+
+ ssl = PQsslStruct(dbconn, "GnuTLS");
+ if (ssl)
+ {
+ /* use GnuTLS functions to access ssl */
+ }
+ ]]></programlisting>
+ </para>
</listitem>
</varlistentry>
@@ -7697,6 +7719,7 @@ <title>SSL Support</title>
</para>
<para>
+ When <productname>OpenSSL</productname> is used, then
<application>libpq</application> reads the system-wide
<productname>OpenSSL</productname> configuration file. By default, this
file is named <filename>openssl.cnf</filename> and is located in the
@@ -8070,6 +8093,13 @@ <title>SSL Library Initialization</title>
for details on the SSL API.
</para>
+ <para>
+ If you are using <productname>OpenSSL</productname> version 1.1.0 or later
+ and use initialization function <function>OPENSSL_init_ssl()</function> or
+ the implicit initialization, then you don't need to do this. Similarly,
+ this is not necessary with <productname>GnuTLS</productname>.
+ </para>
+
<para>
<variablelist>
<varlistentry id="libpq-pqinitopenssl">
@@ -8105,6 +8135,10 @@ <title>SSL Library Initialization</title>
before first opening a database connection. Also be sure that you
have done that initialization before opening a database connection.
</para>
+
+ <para>
+ With <productname>GnuTLS</productname>, this function does nothing.
+ </para>
</listitem>
</varlistentry>
@@ -8125,12 +8159,16 @@ <title>SSL Library Initialization</title>
</para>
<para>
- This function is equivalent to
+ With <productname>OpenSSL</productname>, this function is equivalent to
<literal>PQinitOpenSSL(do_ssl, do_ssl)</literal>.
It is sufficient for applications that initialize both or neither
of <application>OpenSSL</application> and <literal>libcrypto</literal>.
</para>
+ <para>
+ With <productname>GnuTLS</productname>, this function does nothing.
+ </para>
+
<para>
<function>PQinitSSL</function> has been present since
<productname>PostgreSQL</productname> 8.0, while <function>PQinitOpenSSL</function>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 8d9d40664b..8b8201f89f 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2167,10 +2167,14 @@ <title>Secure TCP/IP Connections with SSL</title>
<para>
<productname>PostgreSQL</productname> has native support for using
<acronym>SSL</acronym> connections to encrypt client/server communications
- for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
- server systems and that support in <productname>PostgreSQL</productname> is
- enabled at build time (see <xref linkend="installation"/>).
+ for increased security. This requires that support in
+ <productname>PostgreSQL</productname> is enabled at build time (see <xref
+ linkend="installation"/>). <productname>PostgreSQL</productname> supports
+ both <productname>OpenSSL</productname> and
+ <productname>GnuTLS</productname> as the SSL implementation. This is
+ chosen at build time. Third-party clients may also use other SSL
+ implementations. Clients and servers using different SSL implementations
+ are compatible.
</para>
<sect2 id="ssl-setup">
@@ -2249,6 +2253,7 @@ <title>Basic Setup</title>
<title>OpenSSL Configuration</title>
<para>
+ When using <productname>OpenSSL</productname>,
<productname>PostgreSQL</productname> reads the system-wide
<productname>OpenSSL</productname> configuration file. By default, this
file is named <filename>openssl.cnf</filename> and is located in the
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index cda09aaafd..782d63fd40 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -17,6 +17,7 @@ <title>sslinfo</title>
<para>
This extension won't build at all unless the installation was
configured with <literal>--with-openssl</literal>.
+ (GnuTLS is currently not supported.)
</para>
<sect2>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 91d7cb83dc..e9c3eed337 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
+with_gnutls = @with_gnutls@
with_openssl = @with_openssl@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30..c3720539d3 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -17,7 +17,9 @@ include $(top_builddir)/src/Makefile.global
OBJS = be-fsstubs.o be-secure.o be-secure-common.o auth.o crypt.o hba.o ifaddr.o pqcomm.o \
pqformat.o pqmq.o pqsignal.o auth-scram.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_gnutls),yes)
+OBJS += be-secure-gnutls.o
+else ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
diff --git a/src/backend/libpq/be-secure-gnutls.c b/src/backend/libpq/be-secure-gnutls.c
new file mode 100644
index 0000000000..24fcfa500c
--- /dev/null
+++ b/src/backend/libpq/be-secure-gnutls.c
@@ -0,0 +1,795 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-gnutls.c
+ * functions for GnuTLS support in the backend.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-gnutls.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <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);
+#ifdef HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION
+static int pin_function(void *userdata, int attempt,
+ const char *token_url,
+ const char *token_label,
+ unsigned int flags,
+ char *pin, size_t pin_max);
+static int dummy_pin_function(void *userdata, int attempt,
+ const char *token_url,
+ const char *token_label,
+ unsigned int flags,
+ char *pin, size_t pin_max);
+#endif
+static int verify_cb(gnutls_session_t ssl);
+static bool initialize_dh(gnutls_dh_params_t *dh_params, bool isServerStart);
+
+static gnutls_certificate_credentials_t tls_credentials = NULL;
+static gnutls_dh_params_t tls_dh_params = NULL;
+static gnutls_priority_t tls_priority = NULL;
+static bool tls_initialized = false;
+static bool pin_function_called = false;
+static bool ssl_is_server_start;
+
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+int
+be_tls_init(bool isServerStart)
+{
+ gnutls_certificate_credentials_t credentials = NULL;
+ gnutls_priority_t priority = NULL;
+ gnutls_dh_params_t dh_params = NULL;
+ int ret;
+ const char *err_pos;
+
+ /* This stuff need be done only once. */
+ if (!tls_initialized)
+ {
+ gnutls_global_init();
+ tls_initialized = true;
+ }
+
+ ret = gnutls_certificate_allocate_credentials(&credentials);
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not create SSL credentials: %s",
+ gnutls_strerror(ret))));
+ goto error;
+ }
+
+ /*
+ * Set PIN (passphrase) callback. Note that there is no default for this
+ * in GnuTLS.
+ *
+ * If reloading, substitute a dummy callback, because we don't want to
+ * prompt for a passphrase in an already-running server.
+ */
+#ifdef HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION
+ if (isServerStart)
+ gnutls_pkcs11_set_pin_function(pin_function, NULL);
+ else
+ gnutls_pkcs11_set_pin_function(dummy_pin_function, NULL);
+#endif
+ /* used by the callback */
+ ssl_is_server_start = isServerStart;
+
+ /*
+ * Load and verify server's certificate and private key
+ */
+ if (!check_ssl_key_file_permissions(ssl_key_file, isServerStart))
+ goto error;
+
+ pin_function_called = false;
+
+ ret = gnutls_certificate_set_x509_key_file(credentials, ssl_cert_file, ssl_key_file, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ if (pin_function_called)
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase",
+ ssl_key_file)));
+ else
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load server certificate \"%s\" or key file \"%s\": %s",
+ ssl_cert_file, ssl_key_file, gnutls_strerror(ret))));
+ goto error;
+ }
+
+ /* set up ephemeral DH keys */
+ if (!initialize_dh(&dh_params, isServerStart))
+ goto error;
+
+ gnutls_certificate_set_dh_params(credentials, dh_params);
+
+ /* set up the allowed cipher list */
+ ret = gnutls_priority_init(&priority, gnutls_priority, &err_pos);
+ if (ret < 0)
+ {
+ if (ret == GNUTLS_E_INVALID_REQUEST)
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not set the cipher list: syntax error at %s", err_pos)));
+ else
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not set the cipher list: %s", gnutls_strerror(ret))));
+ goto error;
+ }
+
+ /*
+ * Load CA store, so we can verify client certificates if needed.
+ */
+ if (ssl_ca_file[0])
+ {
+ ret = gnutls_certificate_set_x509_trust_file(credentials, ssl_ca_file, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load root certificate file \"%s\": %s",
+ ssl_ca_file, gnutls_strerror(ret))));
+ goto error;
+ }
+
+ gnutls_certificate_set_verify_function(credentials, verify_cb);
+ }
+
+ /*
+ * Load the Certificate Revocation List (CRL).
+ */
+ if (ssl_crl_file[0])
+ {
+ ret = gnutls_certificate_set_x509_crl_file(credentials, ssl_crl_file, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load SSL certificate revocation list file \"%s\": %s",
+ ssl_crl_file, gnutls_strerror(ret))));
+ goto error;
+ }
+ }
+
+ /*
+ * Success! Replace any existing credentials.
+ */
+ if (tls_credentials)
+ gnutls_certificate_free_credentials(tls_credentials);
+ if (tls_priority)
+ gnutls_priority_deinit(tls_priority);
+ if (tls_dh_params)
+ gnutls_dh_params_deinit(tls_dh_params);
+
+ tls_credentials = credentials;
+ tls_priority = priority;
+ tls_dh_params = dh_params;
+
+ /*
+ * Set flag to remember whether CA store has been loaded.
+ */
+ if (ssl_ca_file[0])
+ ssl_loaded_verify_locations = true;
+ else
+ ssl_loaded_verify_locations = false;
+
+ return 0;
+
+error:
+ if (credentials)
+ gnutls_certificate_free_credentials(credentials);
+ if (priority)
+ gnutls_priority_deinit(priority);
+ if (dh_params)
+ gnutls_dh_params_deinit(dh_params);
+ return -1;
+}
+
+void
+be_tls_destroy(void)
+{
+ if (tls_credentials)
+ gnutls_certificate_free_credentials(tls_credentials);
+ if (tls_priority)
+ gnutls_priority_deinit(tls_priority);
+ if (tls_dh_params)
+ gnutls_dh_params_deinit(tls_dh_params);
+ tls_credentials = NULL;
+ tls_priority = NULL;
+ tls_dh_params = NULL;
+ ssl_loaded_verify_locations = false;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ int ret;
+
+ Assert(!port->ssl);
+ Assert(!port->peer);
+
+ if (!tls_credentials || !tls_priority)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not initialize SSL connection: SSL context not set up")));
+ return -1;
+ }
+
+ ret = gnutls_init(&port->ssl, GNUTLS_SERVER);
+ if (ret < 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not initialize SSL connection: %s",
+ gnutls_strerror(ret))));
+ return -1;
+ }
+
+ gnutls_transport_set_ptr(port->ssl, port);
+ gnutls_transport_set_pull_function(port->ssl, my_sock_read);
+ gnutls_transport_set_push_function(port->ssl, my_sock_write);
+
+ ret = gnutls_priority_set(port->ssl, tls_priority);
+ if (ret < 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not initialize SSL connection: %s",
+ gnutls_strerror(ret))));
+ return -1;
+ }
+
+ ret = gnutls_credentials_set(port->ssl, GNUTLS_CRD_CERTIFICATE, tls_credentials);
+ if (ret < 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not initialize SSL connection: %s",
+ gnutls_strerror(ret))));
+ return -1;
+ }
+
+ if (ssl_loaded_verify_locations)
+ gnutls_certificate_server_set_request(port->ssl, GNUTLS_CERT_REQUEST);
+
+ port->ssl_in_use = true;
+
+ do
+ {
+ ret = gnutls_handshake(port->ssl);
+ }
+ while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+
+ if (ret < 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not accept SSL connection: %s",
+ gnutls_strerror(ret))));
+ return -1;
+ }
+
+ /* Get client certificate, if available. */
+ ret = get_peer_certificate(port->ssl, &port->peer);
+ if (ret < 0 && ret != GNUTLS_E_NO_CERTIFICATE_FOUND)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not load peer certificates: %s",
+ gnutls_strerror(ret))));
+ }
+
+ /* and extract the Common Name from it. */
+ port->peer_cn = NULL;
+ port->peer_cert_valid = false;
+ if (port->peer != NULL)
+ {
+ size_t len = 0;
+
+ gnutls_x509_crt_get_dn_by_oid(port->peer,
+ GNUTLS_OID_X520_COMMON_NAME,
+ 0, 0, NULL, &len);
+
+ if (len > 0)
+ {
+ char *peer_cn;
+
+ peer_cn = MemoryContextAlloc(TopMemoryContext, len);
+
+ ret = gnutls_x509_crt_get_dn_by_oid(port->peer,
+ GNUTLS_OID_X520_COMMON_NAME,
+ 0, 0, peer_cn, &len);
+
+ if (ret != 0)
+ {
+ /* shouldn't happen */
+ pfree(peer_cn);
+ return -1;
+ }
+
+ /*
+ * Reject embedded NULLs in certificate common name to prevent
+ * attacks like CVE-2009-4034.
+ */
+ if (len != strlen(peer_cn))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL certificate's common name contains embedded null")));
+ pfree(peer_cn);
+ return -1;
+ }
+
+
+ if (ret == 0)
+ port->peer_cn = peer_cn;
+ else
+ pfree(peer_cn);
+
+ }
+
+ port->peer_cert_valid = true;
+ }
+
+ return 0;
+}
+
+void
+be_tls_close(Port *port)
+{
+ if (port->ssl)
+ {
+ gnutls_bye(port->ssl, GNUTLS_SHUT_RDWR);
+ gnutls_deinit(port->ssl);
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+ }
+
+ if (port->peer)
+ {
+ gnutls_x509_crt_deinit(port->peer);
+ port->peer = NULL;
+ }
+
+ if (port->peer_cn)
+ {
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n;
+
+ n = gnutls_record_recv(port->ssl, ptr, len);
+
+ if (n > 0)
+ return n;
+
+ switch (n)
+ {
+ case 0:
+
+ /*
+ * the SSL connnection was closed, leave it to the caller to
+ * ereport it
+ */
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_INTERRUPTED:
+ *waitfor = WL_SOCKET_READABLE;
+ errno = EWOULDBLOCK;
+ n = -1;
+ break;
+ default:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ gnutls_strerror(n))));
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+
+ return n;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n;
+
+ n = gnutls_record_send(port->ssl, ptr, len);
+
+ if (n >= 0)
+ return n;
+
+ switch (n)
+ {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_INTERRUPTED:
+ *waitfor = WL_SOCKET_WRITEABLE;
+ errno = EWOULDBLOCK;
+ n = -1;
+ break;
+ default:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ gnutls_strerror(n))));
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+
+ return n;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Private substitute transport layer: this does the sending and receiving
+ * using send() and recv() instead. This is so that we can enable and disable
+ * interrupts just while calling recv(). We cannot have interrupts occurring
+ * while the bulk of GnuTLS runs, because it uses malloc() and possibly other
+ * non-reentrant libc facilities. We also need to call send() and recv()
+ * directly so it gets passed through the socket/signals layer on Win32.
+ */
+
+static ssize_t
+my_sock_read(gnutls_transport_ptr_t port, void *buf, size_t size)
+{
+ return secure_raw_read((Port *) port, buf, size);
+}
+
+static ssize_t
+my_sock_write(gnutls_transport_ptr_t port, const void *buf, size_t size)
+{
+ return secure_raw_write((Port *) port, buf, size);
+}
+
+#if !HAVE_DECL_GNUTLS_X509_CRT_LIST_SORT
+/*
+ * GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ * certificate chains, so we skip doing so in these earlier versions.
+ */
+#define GNUTLS_X509_CRT_LIST_SORT 0
+#endif
+
+/*
+ * Get peer certificate from a session
+ *
+ * Returns GNUTLS_E_NO_CERTIFICATE_FOUND when not x509 certifcate was found.
+ */
+static int
+get_peer_certificate(gnutls_session_t ssl, gnutls_x509_crt_t *peer)
+{
+ if (gnutls_certificate_type_get(ssl) == GNUTLS_CRT_X509)
+ {
+ unsigned int n;
+ int ret;
+ gnutls_datum_t const *raw_certs;
+ gnutls_x509_crt_t *certs;
+
+ raw_certs = gnutls_certificate_get_peers(ssl, &n);
+
+ if (n == 0)
+ return GNUTLS_E_NO_CERTIFICATE_FOUND;
+
+ certs = palloc(n * sizeof(gnutls_x509_crt_t));
+
+ ret = gnutls_x509_crt_list_import(certs, &n, raw_certs,
+ GNUTLS_X509_FMT_DER,
+ GNUTLS_X509_CRT_LIST_SORT);
+
+ if (ret >= 1)
+ {
+ unsigned int i;
+
+ for (i = 1; i < ret; i++)
+ gnutls_x509_crt_deinit(certs[i]);
+
+ *peer = certs[0];
+
+ ret = GNUTLS_E_SUCCESS;
+ }
+ else if (ret == 0)
+ ret = GNUTLS_E_NO_CERTIFICATE_FOUND;
+
+ pfree(certs);
+
+ return ret;
+ }
+
+ return GNUTLS_E_NO_CERTIFICATE_FOUND;
+}
+
+#define MAX_DH_FILE_SIZE 10240
+
+/*
+ * Load precomputed DH parameters.
+ *
+ * To prevent "downgrade" attacks, we perform a number of checks
+ * to verify that the DBA-generated DH parameters file contains
+ * what we expect it to contain.
+ */
+static bool
+load_dh_file(gnutls_dh_params_t dh_params, char *filename, bool isServerStart)
+{
+ FILE *fp;
+ char buffer[MAX_DH_FILE_SIZE];
+ gnutls_datum_t datum = {(unsigned char *) buffer};
+ int ret;
+
+ /* attempt to open file. It's not an error if it doesn't exist. */
+ if ((fp = AllocateFile(filename, "r")) == NULL)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+ return false;
+ }
+
+ datum.size = fread(buffer, sizeof(buffer[0]), sizeof(buffer), fp);
+
+ FreeFile(fp);
+
+ if (datum.size < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode_for_file_access(),
+ errmsg("could not load DH parameters file: %s",
+ gnutls_strerror(ret))));
+ return false;
+ }
+
+ ret = gnutls_dh_params_import_pkcs3(dh_params, &datum, GNUTLS_X509_FMT_PEM);
+
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load DH parameters file: %s",
+ gnutls_strerror(ret))));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Load hardcoded DH parameters.
+ *
+ * To prevent problems if the DH parameters files don't even
+ * exist, we can load DH parameters hardcoded into this file.
+ */
+static bool
+load_dh_buffer(gnutls_dh_params_t dh_params, const char *buffer, size_t len, bool isServerStart)
+{
+ gnutls_datum_t datum = {(unsigned char *) buffer, len};
+ int ret;
+
+ ret = gnutls_dh_params_import_pkcs3(dh_params, &datum, GNUTLS_X509_FMT_PEM);
+
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg_internal("DH load buffer: %s", gnutls_strerror(ret))));
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef HAVE_GNUTLS_PKCS11_SET_PIN_FUNCTION
+/*
+ * PIN callback
+ */
+static int
+pin_function(void *userdata, int attempt,
+ const char *token_url,
+ const char *token_label,
+ unsigned int flags,
+ char *pin, size_t pin_max)
+{
+ const char *prompt = token_url;
+
+ if (ssl_passphrase_command[0])
+ run_ssl_passphrase_command(prompt, ssl_is_server_start, pin, pin_max);
+ else
+ simple_prompt(prompt, pin, pin_max, false);
+
+ return 0;
+}
+
+/*
+ * Dummy PIN callback during server reload
+ *
+ * The standard callback is no good during a postmaster SIGHUP cycle, not to
+ * mention SSL context reload in an EXEC_BACKEND postmaster child. So
+ * override it with this dummy function that just returns an error,
+ * guaranteeing failure.
+ */
+static int
+dummy_pin_function(void *userdata, int attempt,
+ const char *token_url,
+ const char *token_label,
+ unsigned int flags,
+ char *pin, size_t pin_max)
+{
+ /* Set flag to change the error message we'll report */
+ pin_function_called = true;
+ return -1;
+}
+#endif
+
+/*
+ * Certificate verification callback
+ *
+ * This callback is where we verify the identity of the client.
+ */
+static int
+verify_cb(gnutls_session_t ssl)
+{
+ unsigned int status;
+ int ret;
+
+ ret = gnutls_certificate_verify_peers2(ssl, &status);
+
+ if (ret == GNUTLS_E_NO_CERTIFICATE_FOUND)
+ return 0;
+ else if (ret < 0)
+ return ret;
+
+ return status;
+}
+
+/*
+ * Set DH parameters for generating ephemeral DH keys. The
+ * DH parameters can take a long time to compute, so they must be
+ * precomputed.
+ *
+ * Since few sites will bother to create a parameter file, we also
+ * also provide a fallback to the parameters provided by the
+ * OpenSSL project.
+ *
+ * These values can be static (once loaded or computed) since the
+ * OpenSSL library can efficiently generate random keys from the
+ * information provided.
+ */
+static bool
+initialize_dh(gnutls_dh_params_t *dh_params, bool isServerStart)
+{
+ bool loaded = false;
+ int ret;
+
+ ret = gnutls_dh_params_init(dh_params);
+ if (ret < 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg_internal("DH init error: %s",
+ gnutls_strerror(ret))));
+ return false;
+ }
+
+ if (ssl_dh_params_file[0])
+ loaded = load_dh_file(*dh_params, ssl_dh_params_file, isServerStart);
+ if (!loaded)
+ loaded = load_dh_buffer(*dh_params, FILE_DH2048, sizeof(FILE_DH2048), isServerStart);
+ if (!loaded)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ (errmsg("DH: could not load DH parameters"))));
+ return false;
+ }
+
+ return true;
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ if (port->ssl)
+ return gnutls_cipher_get_key_size(gnutls_cipher_get(port->ssl)) * 8;
+ else
+ return 0;
+}
+
+bool
+be_tls_get_compression(Port *port)
+{
+ if (port->ssl)
+ {
+ gnutls_compression_method_t comp = gnutls_compression_get(port->ssl);
+
+ return comp != GNUTLS_COMP_UNKNOWN && comp != GNUTLS_COMP_NULL;
+ }
+ else
+ return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+ if (port->ssl)
+ return gnutls_protocol_get_name(gnutls_protocol_get_version(port->ssl));
+ else
+ return NULL;
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ if (port->ssl)
+ return gnutls_cipher_get_name(gnutls_cipher_get(port->ssl));
+ else
+ return NULL;
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ if (port->peer)
+ {
+ int ret;
+
+ ret = gnutls_x509_crt_get_dn_by_oid(port->peer,
+ GNUTLS_OID_X520_COMMON_NAME,
+ 0, 0, ptr, &len);
+
+ if (ret != 0)
+ ptr[0] = '\0';
+ }
+ else
+ ptr[0] = '\0';
+}
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 1b659a5870..5fd58a2f38 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -229,10 +229,8 @@ be_tls_init(bool isServerStart)
}
}
- /*----------
+ /*
* Load the Certificate Revocation List (CRL).
- * http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,00.html
- *----------
*/
if (ssl_crl_file[0])
{
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index d349d7c2c7..6a4aa32e74 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -60,6 +60,9 @@ char *SSLECDHCurve;
/* GUC variable: if false, prefer client ciphers */
bool SSLPreferServerCiphers;
+/* GUC variable controlling GnuTLS priorities */
+char *gnutls_priority;
+
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
/* ------------------------------------------------------------ */
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 1a65ec87bd..f34559dc7d 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1016,7 +1016,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("hostssl record cannot match because SSL is not supported by this build"),
- errhint("Compile with --with-openssl to use SSL connections."),
+ errhint("Compile with --with-gnutls or --with-openssl to use SSL connections."),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0625eff219..567392398f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3731,7 +3731,13 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_GNUTLS)
+ "GnuTLS",
+#else
+#error SSL implementation must set ssl_library string
+#endif
#else
"",
#endif
@@ -3840,6 +3846,25 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"gnutls_priority", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Sets priorities for the cipher suites supported by GnuTLS."),
+ NULL,
+ GUC_SUPERUSER_ONLY
+ },
+ &gnutls_priority,
+#ifdef USE_SSL
+#if HAVE_DECL_GNUTLS_ALPN_SERVER_PRECEDENCE
+ "NORMAL:%SERVER_PRECEDENCE",
+#else
+ "NORMAL",
+#endif
+#else
+ "none",
+#endif
+ NULL, NULL, NULL
+ },
+
{
{"ssl_dh_params_file", PGC_SIGHUP, CONN_AUTH_SSL,
gettext_noop("Location of the SSL DH parameters file."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 7486d20a34..ce0de5d362 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -100,13 +100,18 @@
#ssl_cert_file = 'server.crt'
#ssl_crl_file = ''
#ssl_key_file = 'server.key'
-#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
-#ssl_prefer_server_ciphers = on
-#ssl_ecdh_curve = 'prime256v1'
#ssl_dh_params_file = ''
#ssl_passphrase_command = ''
#ssl_passphrase_command_supports_reload = off
+# 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'
+
#------------------------------------------------------------------------------
# RESOURCE USAGE (except WAL)
diff --git a/src/common/Makefile b/src/common/Makefile
index 1fc2c66225..6abe10f0d5 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -45,7 +45,9 @@ OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
username.o wait_error.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_gnutls),yes)
+OBJS_COMMON += sha2_gnutls.o
+else ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
else
OBJS_COMMON += sha2.o
diff --git a/src/common/sha2_gnutls.c b/src/common/sha2_gnutls.c
new file mode 100644
index 0000000000..279b5370fa
--- /dev/null
+++ b/src/common/sha2_gnutls.c
@@ -0,0 +1,99 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2_gnutlsl.c
+ * Set of wrapper routines on top of GnuTLS to support SHA-224
+ * SHA-256, SHA-384 and SHA-512 functions.
+ *
+ * This should only be used if code is compiled with GnuTLS support.
+ *
+ * Portions Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2_gnutls.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/sha2.h"
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ gnutls_hash_init(ctx, GNUTLS_DIG_SHA256);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ gnutls_hash(*ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ gnutls_hash_deinit(*ctx, dest);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ gnutls_hash_init(ctx, GNUTLS_DIG_SHA512);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ gnutls_hash(*ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ gnutls_hash_deinit(*ctx, dest);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ gnutls_hash_init(ctx, GNUTLS_DIG_SHA384);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ gnutls_hash(*ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ gnutls_hash_deinit(*ctx, dest);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ gnutls_hash_init(ctx, GNUTLS_DIG_SHA224);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ gnutls_hash(*ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ gnutls_hash_deinit(*ctx, dest);
+}
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index f3fd0d0d28..b064554a3e 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,8 +50,11 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_OPENSSL)
#include <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
+#if defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
typedef SHA512_CTX pg_sha384_ctx;
+#elif defined(USE_GNUTLS)
+typedef gnutls_hash_hd_t pg_sha256_ctx;
+typedef gnutls_hash_hd_t pg_sha512_ctx;
+typedef gnutls_hash_hd_t pg_sha224_ctx;
+typedef gnutls_hash_hd_t pg_sha384_ctx;
#else
typedef struct pg_sha256_ctx
{
@@ -89,7 +97,7 @@ typedef struct pg_sha512_ctx
} pg_sha512_ctx;
typedef struct pg_sha256_ctx pg_sha224_ctx;
typedef struct pg_sha512_ctx pg_sha384_ctx;
-#endif /* USE_SSL */
+#endif
/* Interface routines for SHA224/256/384/512 */
extern void pg_sha224_init(pg_sha224_ctx *ctx);
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index ef5528c897..fe36a062c0 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -19,9 +19,11 @@
#define LIBPQ_BE_H
#include <sys/time.h>
-#ifdef USE_OPENSSL
+#if defined(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
+#if defined(USE_OPENSSL)
SSL *ssl;
X509 *peer;
+#elif defined(USE_GNUTLS)
+ gnutls_session_t ssl;
+ gnutls_x509_crt_t peer;
#endif
} Port;
@@ -275,7 +280,7 @@ extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
-#endif /* USE_SSL */
+#endif /* USE_SSL */
extern ProtocolVersion FrontendProtocol;
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 36baf6b919..4cf67523f5 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -102,6 +102,7 @@ extern WaitEventSet *FeBeWaitSet;
extern char *SSLCipherSuites;
extern char *SSLECDHCurve;
extern bool SSLPreferServerCiphers;
+extern char *gnutls_priority;
/*
* prototypes for functions in be-secure-common.c
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 347d5b56dc..b508ab0959 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -134,6 +134,14 @@
don't. */
#undef HAVE_DECL_F_FULLFSYNC
+/* Define to 1 if you have the declaration of `GNUTLS_ALPN_SERVER_PRECEDENCE',
+ and to 0 if you don't. */
+#undef HAVE_DECL_GNUTLS_ALPN_SERVER_PRECEDENCE
+
+/* Define to 1 if you have the declaration of `GNUTLS_X509_CRT_LIST_SORT', and
+ to 0 if you don't. */
+#undef HAVE_DECL_GNUTLS_X509_CRT_LIST_SORT
+
/* Define to 1 if you have the declaration of
`LLVMCreateGDBRegistrationListener', and to 0 if you don't. */
#undef HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER
@@ -282,6 +290,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
@@ -890,6 +901,12 @@
(--enable-float8-byval) */
#undef USE_FLOAT8_BYVAL
+/* Define to build with GnuTLS support. (--with-gnutls) */
+#undef USE_GNUTLS
+
+/* Define to use GnuTLS for random number generation */
+#undef USE_GNUTLS_RANDOM
+
/* Define to build with ICU support. (--with-icu) */
#undef USE_ICU
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index b309395f11..e2ff2ce693 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -165,7 +165,7 @@
* implementation. (Currently, only OpenSSL is supported, but we might add
* more implementations in the future.)
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_GNUTLS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index 5c232ae2d1..371800f0c7 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -27,6 +27,7 @@
/base64.c
/scram-common.c
/sha2.c
+/sha2_gnutls.c
/sha2_openssl.c
/saslprep.c
/unicode_norm.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index abe0a50e98..b5958daf1d 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -51,7 +51,9 @@ OBJS += encnames.o wchar.o
# src/common
OBJS += base64.o ip.o md5.o scram-common.o saslprep.o unicode_norm.o
-ifeq ($(with_openssl),yes)
+ifeq ($(with_gnutls),yes)
+OBJS += fe-secure-gnutls.o fe-secure-common.o sha2_gnutls.o
+else ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o fe-secure-common.o sha2_openssl.o
else
OBJS += sha2.o
@@ -78,12 +80,12 @@ endif
# shared library link. (The order in which you list them here doesn't
# matter.)
ifneq ($(PORTNAME), win32)
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lsocket -lnsl -lresolv -lintl, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lgss -lgssapi -lssl -lgnutls -lsocket -lnsl -lresolv -lintl, $(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS)
else
-SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
+SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi32 -lssl -lgnutls -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS), $(LIBS)) $(LDAP_LIBS_FE)
endif
ifeq ($(PORTNAME), win32)
-SHLIB_LINK += -lshell32 -lws2_32 -lsecur32 $(filter -leay32 -lssleay32 -lcomerr32 -lkrb5_32, $(LIBS))
+SHLIB_LINK += -lshell32 -lws2_32 -lsecur32 $(filter -leay32 -lssleay32 -lgnutls -lcomerr32 -lkrb5_32, $(LIBS))
endif
SHLIB_EXPORTS = exports.txt
@@ -106,7 +108,7 @@ backend_src = $(top_srcdir)/src/backend
chklocale.c crypt.c erand48.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c strnlen.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
rm -f $@ && $(LN_S) $< .
-ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm.c: % : $(top_srcdir)/src/common/%
+ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c sha2_gnutls.c saslprep.c unicode_norm.c: % : $(top_srcdir)/src/common/%
rm -f $@ && $(LN_S) $< .
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
@@ -156,7 +158,7 @@ clean distclean: clean-lib
rm -f pg_config_paths.h
# Remove files we (may have) symlinked in from src/port and other places
rm -f chklocale.c crypt.c erand48.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c strnlen.c thread.c win32error.c win32setlocale.c
- rm -f ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm.c
+ rm -f ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c sha2_gnutls.c saslprep.c unicode_norm.c
rm -f encnames.c wchar.c
maintainer-clean: distclean maintainer-clean-lib
diff --git a/src/interfaces/libpq/fe-secure-gnutls.c b/src/interfaces/libpq/fe-secure-gnutls.c
new file mode 100644
index 0000000000..99ee53d067
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gnutls.c
@@ -0,0 +1,803 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-gnutls.c
+ * GnuTLS support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-gnutls.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+#include "fe-secure-common.h"
+#include "libpq-int.h"
+
+#include <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 int initialize_SSL(PGconn *conn);
+static PostgresPollingStatusType open_client_SSL(PGconn *);
+
+static ssize_t my_sock_read(gnutls_transport_ptr_t h, void *buf, size_t size);
+static ssize_t my_sock_write(gnutls_transport_ptr_t h, const void *buf, size_t size);
+static int get_peer_certificate(gnutls_session_t ssl, gnutls_x509_crt_t *peer);
+static int verify_cb(gnutls_session_t ssl);
+
+static bool ssl_lib_initialized = false;
+
+#ifdef ENABLE_THREAD_SAFETY
+#ifndef WIN32
+static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
+#else
+static pthread_mutex_t ssl_config_mutex = NULL;
+static long win32_ssl_create_mutex = 0;
+#endif
+#endif /* ENABLE_THREAD_SAFETY */
+
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* not used with GnuTLS */
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ /* First time through? */
+ if (conn->ssl == NULL)
+ {
+ /*
+ * Create a connection-specific SSL object, and load client
+ * certificate, private key, and trusted CA certs.
+ */
+ if (initialize_SSL(conn) != 0)
+ {
+ /* initialize_SSL already put a message in conn->errorMessage */
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+
+ /* Begin or continue the actual handshake */
+ return open_client_SSL(conn);
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ ssize_t n;
+ int result_errno;
+ char sebuf[256];
+
+ n = gnutls_record_recv(conn->ssl, ptr, len);
+
+ if (n > 0)
+ {
+ SOCK_ERRNO_SET(0);
+ return n;
+ }
+
+ switch (n)
+ {
+ case 0:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ result_errno = ECONNRESET;
+ n = -1;
+ break;
+ case GNUTLS_E_REHANDSHAKE:
+ /* Ignore re-handsake requests and have the caller retry */
+ case GNUTLS_E_INTERRUPTED:
+ result_errno = EINTR;
+ n = -1;
+ break;
+ case GNUTLS_E_AGAIN:
+ result_errno = EAGAIN;
+ n = -1;
+ break;
+#ifdef GNUTLS_E_PREMATURE_TERMINATION
+ case GNUTLS_E_PREMATURE_TERMINATION:
+#endif
+ case GNUTLS_E_PUSH_ERROR:
+ result_errno = SOCK_ERRNO;
+ n = -1;
+ if (result_errno == EPIPE || result_errno == ECONNRESET)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(
+ "server closed the connection unexpectedly\n"
+ "\tThis probably means the server terminated abnormally\n"
+ "\tbefore or while processing the request.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL SYSCALL error: %s\n"),
+ SOCK_STRERROR(result_errno,
+ sebuf, sizeof(sebuf)));
+ break;
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s\n"),
+ gnutls_strerror(n));
+ /* assume the connection is broken */
+ result_errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+
+ /* ensure we return the intended errno to caller */
+ SOCK_ERRNO_SET(result_errno);
+
+ return n;
+}
+
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ return gnutls_record_check_pending(conn->ssl);
+}
+
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ ssize_t n;
+ int result_errno;
+ char sebuf[256];
+
+ n = gnutls_record_send(conn->ssl, ptr, len);
+
+ if (n >= 0)
+ {
+ SOCK_ERRNO_SET(0);
+ return n;
+ }
+
+ switch (n)
+ {
+ case GNUTLS_E_INTERRUPTED:
+ result_errno = EINTR;
+ n = -1;
+ break;
+ case GNUTLS_E_AGAIN:
+ result_errno = EAGAIN;
+ n = -1;
+ break;
+#ifdef GNUTLS_E_PREMATURE_TERMINATION
+ case GNUTLS_E_PREMATURE_TERMINATION:
+#endif
+ case GNUTLS_E_PUSH_ERROR:
+ result_errno = SOCK_ERRNO;
+ n = -1;
+ if (result_errno == EPIPE || result_errno == ECONNRESET)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(
+ "server closed the connection unexpectedly\n"
+ "\tThis probably means the server terminated abnormally\n"
+ "\tbefore or while processing the request.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL SYSCALL error: %s\n"),
+ SOCK_STRERROR(result_errno,
+ sebuf, sizeof(sebuf)));
+ break;
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s\n"),
+ gnutls_strerror(n));
+ /* assume the connection is broken */
+ result_errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+
+ /* ensure we return the intended errno to caller */
+ SOCK_ERRNO_SET(result_errno);
+
+ return n;
+}
+
+/* ------------------------------------------------------------ */
+/* GnuTLS specific code */
+/* ------------------------------------------------------------ */
+
+
+
+#define MAX_CN 256
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ char namedata[MAX_CN];
+ size_t namelen;
+ int i;
+ int ret;
+ int rc = 0;
+
+ /*
+ * First, get the Subject Alternative Names (SANs) from the certificate,
+ * and compare them against the originally given hostname.
+ */
+ for (i = 0;; i++)
+ {
+ namelen = sizeof(namedata);
+ ret = gnutls_x509_crt_get_subject_alt_name(conn->peer, i,
+ namedata,
+ &namelen,
+ NULL);
+
+ if (ret < 0)
+ break;
+
+ if (ret == GNUTLS_SAN_DNSNAME)
+ {
+ char *alt_name = NULL;
+
+ (*names_examined)++;
+
+ rc = pq_verify_peer_name_matches_certificate_name(conn, namedata, namelen, &alt_name);
+
+ if (alt_name)
+ {
+ if (!*first_name)
+ *first_name = alt_name;
+ else
+ free(alt_name);
+ }
+ }
+
+ if (rc != 0)
+ break;
+ }
+
+ /*
+ * If there is no subjectAltName extension of type dNSName, check the
+ * Common Name.
+ *
+ * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
+ * dNSName is present, the CN must be ignored.)
+ */
+ if (*names_examined == 0)
+ {
+ namelen = sizeof(namedata);
+ ret = gnutls_x509_crt_get_dn_by_oid(conn->peer, GNUTLS_OID_X520_COMMON_NAME, 0, 0, namedata, &namelen);
+
+ if (ret >= 0)
+ {
+ (*names_examined)++;
+ rc = pq_verify_peer_name_matches_certificate_name(conn, namedata, namelen, first_name);
+ }
+ }
+
+ return rc;
+}
+
+/*
+ * Initialize SSL library.
+ *
+ * In threadsafe mode, this includes setting up libcrypto callback functions
+ * to do thread locking.
+ */
+int
+pgtls_init(PGconn *conn)
+{
+#ifdef ENABLE_THREAD_SAFETY
+#ifdef WIN32
+ /* Also see similar code in fe-connect.c, default_threadlock() */
+ if (ssl_config_mutex == NULL)
+ {
+ while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
+ /* loop, another thread own the lock */ ;
+ if (ssl_config_mutex == NULL)
+ {
+ if (pthread_mutex_init(&ssl_config_mutex, NULL))
+ return -1;
+ }
+ InterlockedExchange(&win32_ssl_create_mutex, 0);
+ }
+#endif
+ if (pthread_mutex_lock(&ssl_config_mutex))
+ return -1;
+#endif /* ENABLE_THREAD_SAFETY */
+
+ if (!ssl_lib_initialized)
+ {
+ gnutls_global_init();
+ ssl_lib_initialized = true;
+ }
+
+#ifdef ENABLE_THREAD_SAFETY
+ pthread_mutex_unlock(&ssl_config_mutex);
+#endif
+ return 0;
+}
+
+/*
+ * Create per-connection SSL object, and load the client certificate,
+ * private key, and trusted CA certs.
+ *
+ * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
+ */
+static int
+initialize_SSL(PGconn *conn)
+{
+ gnutls_certificate_credentials_t creds;
+ int ret;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ char keybuf[MAXPGPATH];
+ char sebuf[256];
+ bool have_homedir;
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the
+ * files could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0) ||
+ !(conn->sslcrl && strlen(conn->sslcrl) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ ret = gnutls_certificate_allocate_credentials(&creds);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL credentials: %s\n"),
+ gnutls_strerror(ret));
+ return -1;
+ }
+
+ /*
+ * If the root cert file exists, load it so we can perform certificate
+ * verification. If sslmode is "verify-full" we will also do further
+ * verification after the connection has been completed.
+ */
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0' &&
+ stat(fnbuf, &buf) == 0)
+ {
+ ret = gnutls_certificate_set_x509_trust_file(creds, fnbuf, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not read root certificate file \"%s\": %s\n"),
+ fnbuf, gnutls_strerror(ret));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+
+ gnutls_certificate_set_verify_function(creds, verify_cb);
+
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ strlcpy(fnbuf, conn->sslcrl, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0' && stat(fnbuf, &buf) == 0)
+ {
+ ret = gnutls_certificate_set_x509_crl_file(creds, fnbuf, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not read crl file \"%s\": %s\n"),
+ fnbuf, gnutls_strerror(ret));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual case
+ * that it seems worth having a specialized error message for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+ }
+
+ /* Read the client certificate file */
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] == '\0')
+ {
+ /* no home directory, proceed without a client cert */
+ }
+ else if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * If file is not present, just go on without a client cert; server
+ * might or might not accept the connection. Any other error,
+ * however, is grounds for complaint.
+ */
+ if (errno != ENOENT && errno != ENOTDIR)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not open certificate file \"%s\": %s\n"),
+ fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(keybuf, conn->sslkey, sizeof(keybuf));
+ else if (have_homedir)
+ snprintf(keybuf, sizeof(keybuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ keybuf[0] = '\0';
+
+ if (keybuf[0] != '\0')
+ {
+ if (stat(keybuf, &buf) != 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but not private key file \"%s\"\n"),
+ keybuf);
+ return -1;
+ }
+#ifndef WIN32
+ if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
+ keybuf);
+ return -1;
+ }
+#endif
+ }
+
+ ret = gnutls_certificate_set_x509_key_file(creds, fnbuf, keybuf, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not read certificate and key files \"%s\" \"%s\": %s\n"),
+ fnbuf, keybuf, gnutls_strerror(ret));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+ }
+
+ ret = gnutls_init(&conn->ssl, GNUTLS_CLIENT);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ gnutls_strerror(ret));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+
+ gnutls_priority_set_direct(conn->ssl, "NORMAL", NULL);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ gnutls_strerror(ret));
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+
+ ret = gnutls_credentials_set(conn->ssl, GNUTLS_CRD_CERTIFICATE, creds);
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ gnutls_strerror(ret));
+ gnutls_deinit(conn->ssl);
+ gnutls_certificate_free_credentials(creds);
+ return -1;
+ }
+
+ gnutls_transport_set_ptr(conn->ssl, conn);
+ gnutls_transport_set_pull_function(conn->ssl, my_sock_read);
+ gnutls_transport_set_push_function(conn->ssl, my_sock_write);
+
+ conn->ssl_in_use = true;
+
+ return 0;
+}
+
+/*
+ * Attempt to negotiate SSL connection.
+ */
+static PostgresPollingStatusType
+open_client_SSL(PGconn *conn)
+{
+ int ret;
+
+ do
+ {
+ ret = gnutls_handshake(conn->ssl);
+ }
+ while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+
+ if (ret < 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s\n"),
+ gnutls_strerror(ret));
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * We already checked the server certificate in gnutls_handshake() using
+ * verify_cb(), if root.crt exists.
+ */
+
+ /* get server certificate */
+ ret = get_peer_certificate(conn->ssl, &conn->peer);
+ if (conn->peer == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate could not be obtained: %s\n"),
+ gnutls_strerror(ret));
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ if (!pq_verify_peer_name_matches_certificate(conn))
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* SSL handshake is complete */
+ return PGRES_POLLING_OK;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (conn->ssl)
+ {
+ gnutls_bye(conn->ssl, GNUTLS_SHUT_RDWR);
+ gnutls_deinit(conn->ssl);
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+ }
+
+ if (conn->peer)
+ {
+ gnutls_x509_crt_deinit(conn->peer);
+ conn->peer = NULL;
+ }
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Return pointer to OpenSSL object, which is none for GnuTLS.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "GnuTLS") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "compression",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ if (!conn)
+ return NULL;
+ if (conn->ssl == NULL)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "GnuTLS";
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char sslbits_str[10];
+ int sslbytes;
+
+ sslbytes = gnutls_cipher_get_key_size(gnutls_cipher_get(conn->ssl));
+
+ if (sslbytes == 0)
+ return NULL;
+
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", sslbytes * 8);
+ return sslbits_str;
+ }
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return gnutls_cipher_get_name(gnutls_cipher_get(conn->ssl));
+
+ if (strcmp(attribute_name, "compression") == 0)
+ {
+ gnutls_compression_method_t comp = gnutls_compression_get(conn->ssl);
+
+ if (comp == GNUTLS_COMP_NULL || comp == GNUTLS_COMP_UNKNOWN)
+ return "off";
+ else
+ return "on";
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ return gnutls_protocol_get_name(gnutls_protocol_get_version(conn->ssl));
+
+ return NULL; /* unknown attribute */
+}
+
+/*
+ * Private substitute transport layer: this does the sending and receiving using
+ * pqsecure_raw_write() and pqsecure_raw_read() instead, to allow those
+ * functions to disable SIGPIPE and give better error messages on I/O errors.
+ */
+
+static ssize_t
+my_sock_read(gnutls_transport_ptr_t conn, void *buf, size_t size)
+{
+ return pqsecure_raw_read((PGconn *) conn, buf, size);
+}
+
+static ssize_t
+my_sock_write(gnutls_transport_ptr_t conn, const void *buf, size_t size)
+{
+ return pqsecure_raw_write((PGconn *) conn, buf, size);
+}
+
+#if !HAVE_DECL_GNUTLS_X509_CRT_LIST_SORT
+/*
+ * GnuTLS versions before 3.4.0 do not support sorting incorrectly sorted
+ * certificate chains, so we skip doing so in these earlier versions.
+ */
+#define GNUTLS_X509_CRT_LIST_SORT 0
+#endif
+
+/*
+ * Get peer certificate from a session
+ *
+ * Returns GNUTLS_E_NO_CERTIFICATE_FOUND when not x509 certifcate was found.
+ */
+static int
+get_peer_certificate(gnutls_session_t ssl, gnutls_x509_crt_t *peer)
+{
+ if (gnutls_certificate_type_get(ssl) == GNUTLS_CRT_X509)
+ {
+ unsigned int n;
+ int ret;
+ gnutls_datum_t const *raw_certs;
+ gnutls_x509_crt_t *certs;
+
+ raw_certs = gnutls_certificate_get_peers(ssl, &n);
+
+ if (n == 0)
+ return GNUTLS_E_NO_CERTIFICATE_FOUND;
+
+ certs = malloc(n * sizeof(gnutls_x509_crt_t));
+ if (!certs)
+ return GNUTLS_E_NO_CERTIFICATE_FOUND;
+
+ ret = gnutls_x509_crt_list_import(certs, &n, raw_certs,
+ GNUTLS_X509_FMT_DER,
+ GNUTLS_X509_CRT_LIST_SORT);
+
+ if (ret >= 1)
+ {
+ unsigned int i;
+
+ for (i = 1; i < ret; i++)
+ gnutls_x509_crt_deinit(certs[i]);
+
+ *peer = certs[0];
+
+ ret = GNUTLS_E_SUCCESS;
+ }
+ else if (ret == 0)
+ ret = GNUTLS_E_NO_CERTIFICATE_FOUND;
+
+ free(certs);
+
+ return ret;
+ }
+
+ return GNUTLS_E_NO_CERTIFICATE_FOUND;
+}
+
+/*
+ * Certificate verification callback
+ *
+ * This callback is where we verify the identity of the server.
+ */
+static int
+verify_cb(gnutls_session_t ssl)
+{
+ unsigned int status;
+ int ret;
+
+ ret = gnutls_certificate_verify_peers2(ssl, &status);
+ if (ret < 0)
+ return ret;
+
+ return status;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index f7dc249bf0..5776c55114 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -139,7 +139,7 @@ PQsslInUse(PGconn *conn)
/*
* Exported function to allow application to tell us it's already
- * initialized OpenSSL.
+ * initialized the SSL library.
*/
void
PQinitSSL(int do_init)
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index ed9c806861..68e4053056 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -339,7 +339,7 @@ extern const char *const *PQsslAttributeNames(PGconn *conn);
* unencrypted connections or if any other TLS library is in use. */
extern void *PQgetssl(PGconn *conn);
-/* Tell libpq whether it needs to initialize OpenSSL */
+/* Tell libpq whether it needs to initialize the SSL library */
extern void PQinitSSL(int do_init);
/* More detailed way to tell libpq whether it needs to initialize OpenSSL */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index fb04c8c61d..83a6f7cb08 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -71,14 +71,15 @@ typedef struct
#endif
#endif /* ENABLE_SSPI */
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
#include <openssl/ssl.h>
#include <openssl/err.h>
-
#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.
@@ -463,7 +464,7 @@ struct pg_conn
bool allow_ssl_try; /* Allowed to try SSL negotiation */
bool wait_ssl_try; /* Delay SSL negotiation until after
* attempting normal connection */
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
SSL *ssl; /* SSL status, if have SSL connection */
X509 *peer; /* X509 cert of server */
#ifdef USE_SSL_ENGINE
@@ -472,7 +473,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 f9a06d6606..c3bdd41885 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -24,8 +24,11 @@
#include <unistd.h>
#include <sys/time.h>
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
#include <openssl/rand.h>
+#elif defined(USE_GNUTLS)
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
#endif
#ifdef WIN32
#include <wincrypt.h>
@@ -85,8 +88,9 @@ random_from_file(char *filename, void *buf, size_t len)
* We support a number of sources:
*
* 1. OpenSSL's RAND_bytes()
- * 2. Windows' CryptGenRandom() function
- * 3. /dev/urandom
+ * 2. GnuTLS's gnutls_rnd()
+ * 3. Windows' CryptGenRandom() function
+ * 4. /dev/urandom
*
* The configure script will choose which one to use, and set
* a USE_*_RANDOM flag accordingly.
@@ -136,6 +140,14 @@ pg_strong_random(void *buf, size_t len)
return true;
return false;
+ /*
+ * When built with GnuTLS, use GnuTLS's gnutls_rnd function.
+ */
+#elif defined(USE_GNUTLS_RANDOM)
+ if (gnutls_rnd(GNUTLS_RND_RANDOM, buf, len) == 0)
+ return true;
+ return false;
+
/*
* Windows has CryptoAPI for strong cryptographic numbers.
*/
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..a6d41c7e46 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(filter yes,$(with_openssl) $(with_gnutls)),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 97389c90f8..e8d0ed43b0 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -13,7 +13,7 @@ subdir = src/test/ssl
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-export with_openssl
+export with_openssl with_gnutls
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -72,9 +72,10 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
rm ssl/server-ss.csr
-# Password-protected version of server-cn-only.key
+# Password-protected version of server-cn-only.key (need to use PKCS#8
+# format instead of traditional OpenSSL format for GnuTLS support)
ssl/server-password.key: ssl/server-cn-only.key
- openssl rsa -des -in $< -out $@ -passout 'pass:secret1'
+ openssl pkcs8 -topk8 -in $< -out $@ -passout 'pass:secret1'
# Client certificate, signed by the client CA:
ssl/client.crt: ssl/client.key ssl/client_ca.crt
diff --git a/src/test/ssl/ssl/server-password.key b/src/test/ssl/ssl/server-password.key
index adcd38ab88..95ee3fd203 100644
--- a/src/test/ssl/ssl/server-password.key
+++ b/src/test/ssl/ssl/server-password.key
@@ -1,18 +1,17 @@
------BEGIN RSA PRIVATE KEY-----
-Proc-Type: 4,ENCRYPTED
-DEK-Info: DES-CBC,2FAEFD1C1B2C881C
-
-PGi9r3pm05iUwz5QbZik+ZNu0fHNaX8LJFZqpOhg0TV38csLtQ2PRjZ0Q/diBlVT
-SD8JJnIvwPoIWXyMMTax/krFL0CpbFqgAzD4CEgfWxGNhwnMD1DkNaYp/UF/NfuF
-7TqXomUlcH/pVaZlu7G0wrIo5rnjef70I7GEY2vwT5adSLsUBAgrs/u3MAAx/Wh4
-PkVxZELmyiH/8MdIevodjRcJrgIzRheEph39eHrWKgWeSbO0DEQK91vv3prICwo2
-w2iU0Zohf92QuquA2MKZWruCHb4A4HusUZf3Zc14Yueu/HyztSrHmFeBp0amlWep
-/o6mx274XVj7IpanOPPM4qEhrF97LHdaSEPn9HwxvvV4GFJDNCVEBl4zuaHo0N8C
-85GPazIxUWB3CB9PrtXduxeI22lwrIiUdmzA68EXHD7Wg8R90397MNMOomLgfNcu
-rXarrTXmTNgOa20hc1Ue5AXg9fVS9V/5GP4Dn9SX/CdaE1rz0b73N/ViQzVrS9Ne
-n04qYPbnf+MQmFWnzMXctZbYG6jDCbuGFIGP4i/LG+wOE8Rntu8Re9re+HANu5VJ
-Ht20wYOGZIpNwo4YenxvPeTTlbB0Qcma2lnw2bt19owpNQVIeTnRQXxZs3/Y3a+A
-+/B8VvIkQ0u0EpnSVLBetEmJqtOQvBz7c4Z+0Cl+DL1bTqrDn54MxUBap6dgU+/1
-R6pxx1F0ZTtQauVmO8n3rWKwOGG5NeMhf4iId2JWpw39VtRk8LNtnGUbUAbL5znY
-rkUVyJstQg6U6kNTgDWQ1nBxCzlRz2xpHyghnyxLkMpW5ECpmwwLDQ==
------END RSA PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIICoTAbBgkqhkiG9w0BBQMwDgQIECagSEM3B/MCAggABIICgF9tG895A9Q81uR/
+d+EcY21a787f2yiUvgOuhuClmWiqOTNSEC/iJqHb83NL0q31QsIG3SPGtTvtafF8
++uVDzqmx4bmKP5LVX88eOgF6kjTT88ouWk1syHtEa7hSU70XPjh/fnUWwqTqW6gn
+BVKH6J5hOhyI1aWQpRrhmlq6LQD01UlRed0sti35KUji+ohWSPyyC0wSKz8cCKIx
+oCUyc1IDxbr/PCzXwxSK9iVCIIXx+3emi4puwt9QSVMwqo+LMFMhLz/L3bLXE6iZ
+BQOBUKtVoO5kLcvtIwRJWBir8HSeIIZnzGI23YnIythY4WnkPLkUFH9uoaYZU3l6
+UkZ2/cAQwbT6xZxgZQz/HhWLHuqrOCWZboG1oYmTkvO6+8FYRVfPrbJsQqop8olj
+T2jnlD37W79lAbT5xMJEfvow+h6ebu+wf4Bq9f223JgfBXrGevoksVfWRINtQ7iy
+Mxrj4dabHRiQTF6semNnHedvulZK6UtwoNQudM0vOzZTd42W8FMjgjU4U7KI+6AX
+G9SaSAjRMlM5x4PyMnyA+wjyboWpo4jMLdUhNqSs2W+3AWh1qYuh8Ny9ENcKTAY6
+6hu2RCKQyJBsHm5XN1XaueNsyEq11yk9AZFLAWM5h5WPziZNYSHYwEBXpZlIcpWY
+6MR/XUqv43B8KvQwr7bHS2GZ2fAD5sd7oq9qpBr7o6IRO3MKh16T6pwoaQ7sqRg4
+1Ftk7htujwEIrTmIQihzmZRZguA3ceAYUduSFElckZD04LDJnxhhj+AVHnbFA+6x
+VWOoxECXWa3ReOoJMQvbR4gF5yhNtZOtlbaqOp0iVGT/d2e9Bl66E/FqnkWweE4d
+0iziYMw=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2b875a3c95..3602199ff6 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -6,15 +6,13 @@
use ServerSetup;
use File::Copy;
-if ($ENV{with_openssl} eq 'yes')
-{
- plan tests => 65;
-}
-else
+if ($ENV{with_openssl} ne 'yes' && $ENV{with_gnutls} ne 'yes')
{
plan skip_all => 'SSL not supported by this build';
}
+my $number_of_tests = 63;
+
#### Some configuration
# This is the hostname used to connect to the server. This cannot be a
@@ -52,7 +50,11 @@
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+my $expected;
+if ($ENV{'with_openssl'} eq 'yes') { $expected = 'OpenSSL'; }
+elsif ($ENV{'with_gnutls'} eq 'yes') { $expected = 'GnuTLS'; }
+else { $expected = ''; }
+is($result, $expected, 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, 'trust');
@@ -130,11 +132,22 @@
"sslrootcert=ssl/client_ca.crt sslmode=verify-full",
qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+# Try with just the server CA's cert. This fails with OpenSSL because
+# the root file must contain the whole chain up to the root CA.
+if ($ENV{'with_openssl'} eq 'yes')
+{
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/,
+ "connect with server CA cert, without root CA");
+ $number_of_tests++;
+}
+else
+{
+ test_connect_ok($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
@@ -169,12 +182,21 @@
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
"sslcrl option with invalid file name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+if ($ENV{'with_openssl'} eq 'yes')
+{
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+ $number_of_tests++;
+}
+else
+{
+ test_connect_ok($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
@@ -363,8 +385,13 @@
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+ ($ENV{'with_openssl'} eq 'yes' ?
+ qr/SSL error/ :
+ qr/connection requires a valid client certificate/),
+ "intermediate client certificate is missing");
# clean up
unlink("ssl/client_tmp.key", "ssl/client_wrongperms_tmp.key",
"ssl/client-revoked_tmp.key");
+
+done_testing($number_of_tests);
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index b460a7fa8a..0c10ea8bc4 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -8,7 +8,7 @@
use ServerSetup;
use File::Copy;
-if ($ENV{with_openssl} ne 'yes')
+if ($ENV{with_openssl} ne 'yes' && $ENV{with_gnutls} ne 'yes')
{
plan skip_all => 'SSL not supported by this build';
}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4543d87d83..0ae138fbfb 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -124,6 +124,10 @@ sub mkvcbuild
{
push(@pgcommonallfiles, 'sha2_openssl.c');
}
+ elsif ($solution->{options}->{gnutls})
+ {
+ push(@pgcommonallfiles, 'sha2_gnutls.c');
+ }
else
{
push(@pgcommonallfiles, 'sha2.c');
@@ -252,6 +256,12 @@ sub mkvcbuild
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
$libpq->RemoveFile('src/common/sha2_openssl.c');
}
+ elsif (!$solution->{options}->{gnutls})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gnutls.c');
+ $libpq->RemoveFile('src/common/sha2_gnutls.c');
+ }
else
{
$libpq->RemoveFile('src/common/sha2.c');
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9fe950b29d..07823ccfa9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2818,6 +2818,9 @@ ginxlogVacuumDataLeafPage
gistxlogPage
gistxlogPageSplit
gistxlogPageUpdate
+gnutls_datum_t
+gnutls_dh_params_t
+gnutls_x509_crt_t
grouping_sets_data
gseg_picksplit_item
gss_OID
base-commit: 2e39f69b6621bd3d67f650a5647fd0412819712d
--
2.18.0
On Fri, Aug 31, 2018 at 1:28 PM Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
On 20/08/2018 05:13, Michael Paquier wrote:
Patch v6 of this thread is failing to apply. Could you rebase?
attached
Changes in v7 since v6:
- Added support for ssl_passphrase_command.
- Test suite needed some adjustment because GnuTLS doesn't appear to
understand the previously used file format for encrypted keys.- Removed tls-unique channel binding support. Support for
tls-server-end-point still needs to be added, but it could be a separate
patch.
Unfortunately it needs to be rebased one more time, could you do this? Also I'm
wondering about this:
I'm moving this patch forward to CF 2018-09, since it's not going to be
ready for -07, and we're still whacking around some channel binding
details, which would potentially interfere with this patch.
Were you talking about this one [1]/messages/by-id/20180712041410.GC7352@paquier.xyz? As far as I see it's not a concern
anymore? I'll move it to the next CF.
On 29/11/2018 13:28, Dmitry Dolgov wrote:
Unfortunately it needs to be rebased one more time, could you do this? Also I'm
wondering about this:I'm moving this patch forward to CF 2018-09, since it's not going to be
ready for -07, and we're still whacking around some channel binding
details, which would potentially interfere with this patch.Were you talking about this one [1]? As far as I see it's not a concern
anymore? I'll move it to the next CF.
I have decided that I don't want to pursue this patch anymore. It has
served its purpose having allowed us to refine the SSL library
abstractions so that alternative implementations such as macOS Secure
Transport can go ahead. But officially supporting GnuTLS as an
alternative to OpenSSL doesn't seem to have any practical advantages, so
I don't foresee this getting committed into PostgreSQL core.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Thu, Nov 29, 2018 at 8:28 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
On 29/11/2018 13:28, Dmitry Dolgov wrote:
Unfortunately it needs to be rebased one more time, could you do this? Also I'm
wondering about this:I'm moving this patch forward to CF 2018-09, since it's not going to be
ready for -07, and we're still whacking around some channel binding
details, which would potentially interfere with this patch.Were you talking about this one [1]? As far as I see it's not a concern
anymore? I'll move it to the next CF.I have decided that I don't want to pursue this patch anymore. It has
served its purpose having allowed us to refine the SSL library
abstractions so that alternative implementations such as macOS Secure
Transport can go ahead. But officially supporting GnuTLS as an
alternative to OpenSSL doesn't seem to have any practical advantages, so
I don't foresee this getting committed into PostgreSQL core.
Hmm, I find that a bit disappointing. I'm not in a position to take up
the patch right now, unfortunately.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
On Thu, Nov 29, 2018 at 8:28 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:I have decided that I don't want to pursue this patch anymore. It has
served its purpose having allowed us to refine the SSL library
abstractions so that alternative implementations such as macOS Secure
Transport can go ahead. But officially supporting GnuTLS as an
alternative to OpenSSL doesn't seem to have any practical advantages, so
I don't foresee this getting committed into PostgreSQL core.
Hmm, I find that a bit disappointing. I'm not in a position to take up
the patch right now, unfortunately.
Yeah, I was disappointed too. OpenSSL has had a squirrelly enough track
record that it'd be nice not to be totally dependent on it. But, like
both of you, I'm not quite motivated enough to take up the patch myself.
Anyway, if the OpenSSL situation changes enough to affect the cost/benefit
calculus, at least we should be able to re-open this patch with some
confidence that we've not painted ourselves into a corner.
regards, tom lane
Hi,
On 2018-11-29 16:34:13 -0500, Tom Lane wrote:
Yeah, I was disappointed too. OpenSSL has had a squirrelly enough track
record that it'd be nice not to be totally dependent on it.
GnuTLS seems, if anything, worse though. There's obviously good reasons
to add support for TLS libraries that make it easier to use PG on
certain platforms, but GnuTLS doesn't achieve that. So I don't think
this is too sad.
- Andres
Greetings,
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Thu, Nov 29, 2018 at 8:28 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:I have decided that I don't want to pursue this patch anymore. It has
served its purpose having allowed us to refine the SSL library
abstractions so that alternative implementations such as macOS Secure
Transport can go ahead. But officially supporting GnuTLS as an
alternative to OpenSSL doesn't seem to have any practical advantages, so
I don't foresee this getting committed into PostgreSQL core.Hmm, I find that a bit disappointing. I'm not in a position to take up
the patch right now, unfortunately.Yeah, I was disappointed too. OpenSSL has had a squirrelly enough track
record that it'd be nice not to be totally dependent on it. But, like
both of you, I'm not quite motivated enough to take up the patch myself.
I'm also pretty disappointed by this, although admittedly I think my
interest would be more in adding libNSS support than GnuTLS, but I had
viewed this as a good stepping stone to get there. Perhaps it still can
be though.
Thanks!
Stephen
Greetings,
* Andres Freund (andres@anarazel.de) wrote:
On 2018-11-29 16:34:13 -0500, Tom Lane wrote:
Yeah, I was disappointed too. OpenSSL has had a squirrelly enough track
record that it'd be nice not to be totally dependent on it.GnuTLS seems, if anything, worse though. There's obviously good reasons
to add support for TLS libraries that make it easier to use PG on
certain platforms, but GnuTLS doesn't achieve that. So I don't think
this is too sad.
There are very good reasons to give our users the option of different
TLS libraries, even if it's platforms where OpenSSL is also available,
for the reason Tom mentioned- OpenSSL hasn't had a terribly good track
record, and because there's been independent evaluation of different
libraries and OpenSSL doesn't top the list in those.
As such, I do believe it'd be good to have support for multiple
libraries, even on Linux or other platforms where OpenSSL is available.
Thanks!
Stephen