[PATCH v7] GnuTLS support
Peter Eisentraut <peter_e@gmx.net>
From: Peter Eisentraut <peter_e@gmx.net>
To:
Date: 2018-08-31T11:00:26Z
Lists: pgsql-hackers
Commits
Same data as JSON:
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Remove extra word from src/backend/optimizer/README
- 2e39f69b6621 12.0 cited
---
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
--------------B9D2DD24F55D9D2B4F606D4C--