[PATCH v6] GSSAPI encryption support - Mailing list pgsql-hackers
From | Robbie Harwood |
---|---|
Subject | [PATCH v6] GSSAPI encryption support |
Date | |
Msg-id | jlgegbkmvxl.fsf@thriss.redhat.com Whole thread Raw |
In response to | [PATCH v1] GSSAPI encryption support (Robbie Harwood <rharwood@redhat.com>) |
Responses |
Re: [PATCH v6] GSSAPI encryption support
Re: [PATCH v6] GSSAPI encryption support |
List | pgsql-hackers |
Hello friends, Here's yet another version of GSSAPI encryption support. It's also available for viewing on my github: https://github.com/frozencemetery/postgres/tree/feature/gssencrypt6 Let me hit the highlights of this time around: - Fallback code is back! It's almost unchanged from early versions of this patchset. Corresponding doc changes for this and the next item are of course included. - Minor protocol change. I did not realize that connection parameters were not read until after auth was complete, which means that in this version I go back to sending the AUTH_REQ_OK in the clear. Though I found this initially irritating since it required re-working the should_crypto conditions, it ends up being a net positive since I can trade a library call for a couple variables. - Client buffer flush on completion of authentication. This should prevent the issue with the client getting unexpected message type of NUL due to encrypted data not getting decrypted. I continue to be unable to replicate this issue, but since the codepath triggers in the "no data buffered case" all the math is sound. (Famous last words I'm sure.) - Code motion is its own patch. This was requested and hopefully clarifies what's going on. - Some GSSAPI authentication fixes have been applied. I've been staring at this code too long now and writing this made me feel better. If it should be a separate change that's fine and easy to do. Thanks! From 5674aa74effab4931bac1044f32dee83d915aa90 Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Fri, 26 Feb 2016 16:07:05 -0500 Subject: [PATCH 1/3] Move common GSSAPI code into its own files On both the frontend and backend, prepare for GSSAPI encryption suport by moving common code for error handling into a common file. Other than build-system changes, no code changes occur in this patch. --- configure | 2 + configure.in | 1 + src/Makefile.global.in | 1 + src/backend/libpq/Makefile | 4 ++ src/backend/libpq/auth.c | 63 +-------------------------- src/backend/libpq/be-gssapi-common.c | 75 +++++++++++++++++++++++++++++++++ src/include/libpq/be-gssapi-common.h | 26 ++++++++++++ src/interfaces/libpq/Makefile | 4 ++ src/interfaces/libpq/fe-auth.c | 48 +-------------------- src/interfaces/libpq/fe-gssapi-common.c | 63 +++++++++++++++++++++++++++ src/interfaces/libpq/fe-gssapi-common.h | 21 +++++++++ 11 files changed, 199 insertions(+), 109 deletions(-) create mode 100644 src/backend/libpq/be-gssapi-common.c create mode 100644 src/include/libpq/be-gssapi-common.h create mode 100644 src/interfaces/libpq/fe-gssapi-common.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.h diff --git a/configure b/configure index b3f3abe..a5bd629 100755 --- a/configure +++ b/configure @@ -713,6 +713,7 @@ with_systemd with_selinux with_openssl krb_srvtab +with_gssapi with_python with_perl with_tcl @@ -5491,6 +5492,7 @@ $as_echo "$with_gssapi" >&6; } + # # Kerberos configuration parameters # diff --git a/configure.in b/configure.in index 0bd90d7..4fd8f05 100644 --- a/configure.in +++ b/configure.in @@ -636,6 +636,7 @@ PGAC_ARG_BOOL(with, gssapi, no, [build with GSSAPI support], krb_srvtab="FILE:\$(sysconfdir)/krb5.keytab" ]) AC_MSG_RESULT([$with_gssapi]) +AC_SUBST(with_gssapi) AC_SUBST(krb_srvtab) diff --git a/src/Makefile.global.in b/src/Makefile.global.in index e94d6a5..3dbc5c2 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -183,6 +183,7 @@ with_perl = @with_perl@ with_python = @with_python@ with_tcl = @with_tcl@ with_openssl = @with_openssl@ +with_gssapi = @with_gssapi@ 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 09410c4..a8cc9a0 100644 --- a/src/backend/libpq/Makefile +++ b/src/backend/libpq/Makefile @@ -21,4 +21,8 @@ ifeq ($(with_openssl),yes) OBJS += be-secure-openssl.o endif +ifeq ($(with_gssapi),yes) +OBJS += be-gssapi-common.o +endif + include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 57c2f48..73d493e 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -136,11 +136,7 @@ bool pg_krb_caseins_users; *---------------------------------------------------------------- */ #ifdef ENABLE_GSS -#if defined(HAVE_GSSAPI_H) -#include <gssapi.h> -#else -#include <gssapi/gssapi.h> -#endif +#include "libpq/be-gssapi-common.h" static int pg_GSS_recvauth(Port *port); #endif /* ENABLE_GSS */ @@ -715,63 +711,6 @@ recv_and_check_password_packet(Port *port, char **logdetail) */ #ifdef ENABLE_GSS -#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER) -/* - * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW - * that contain the OIDs required. Redefine here, values copied - * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c - */ -static const gss_OID_desc GSS_C_NT_USER_NAME_desc = -{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"}; -static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc; -#endif - - -static void -pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat) -{ - gss_buffer_desc gmsg; - OM_uint32 lmin_s, - msg_ctx; - char msg_major[128], - msg_minor[128]; - - /* Fetch major status message */ - msg_ctx = 0; - gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE, - GSS_C_NO_OID, &msg_ctx, &gmsg); - strlcpy(msg_major, gmsg.value, sizeof(msg_major)); - gss_release_buffer(&lmin_s, &gmsg); - - if (msg_ctx) - - /* - * More than one message available. XXX: Should we loop and read all - * messages? (same below) - */ - ereport(WARNING, - (errmsg_internal("incomplete GSS error report"))); - - /* Fetch mechanism minor status message */ - msg_ctx = 0; - gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE, - GSS_C_NO_OID, &msg_ctx, &gmsg); - strlcpy(msg_minor, gmsg.value, sizeof(msg_minor)); - gss_release_buffer(&lmin_s, &gmsg); - - if (msg_ctx) - ereport(WARNING, - (errmsg_internal("incomplete GSS minor error report"))); - - /* - * errmsg_internal, since translation of the first part must be done - * before calling this function anyway. - */ - ereport(severity, - (errmsg_internal("%s", errmsg), - errdetail_internal("%s: %s", msg_major, msg_minor))); -} - static int pg_GSS_recvauth(Port *port) { diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c new file mode 100644 index 0000000..541a18e --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,75 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.c + * Common code for GSSAPI authentication & encryption + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/libpq/be-gssapi-common.c + * + *------------------------------------------------------------------------- + */ + +#include "libpq/be-gssapi-common.h" + +#include "postgres.h" + +#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER) +/* + * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW + * that contain the OIDs required. Redefine here, values copied + * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c + */ +static const gss_OID_desc GSS_C_NT_USER_NAME_desc = +{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"}; +static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc; +#endif + +void +pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat) +{ + gss_buffer_desc gmsg; + OM_uint32 lmin_s, + msg_ctx; + char msg_major[128], + msg_minor[128]; + + /* Fetch major status message */ + msg_ctx = 0; + gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(msg_major, gmsg.value, sizeof(msg_major)); + gss_release_buffer(&lmin_s, &gmsg); + + if (msg_ctx) + + /* + * More than one message available. XXX: Should we loop and read all + * messages? (same below) + */ + ereport(WARNING, + (errmsg_internal("incomplete GSS error report"))); + + /* Fetch mechanism minor status message */ + msg_ctx = 0; + gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(msg_minor, gmsg.value, sizeof(msg_minor)); + gss_release_buffer(&lmin_s, &gmsg); + + if (msg_ctx) + ereport(WARNING, + (errmsg_internal("incomplete GSS minor error report"))); + + /* + * errmsg_internal, since translation of the first part must be done + * before calling this function anyway. + */ + ereport(severity, + (errmsg_internal("%s", errmsg), + errdetail_internal("%s: %s", msg_major, msg_minor))); +} + diff --git a/src/include/libpq/be-gssapi-common.h b/src/include/libpq/be-gssapi-common.h new file mode 100644 index 0000000..eca5a9d --- /dev/null +++ b/src/include/libpq/be-gssapi-common.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.h + * Definitions for GSSAPI authentication & encryption handling + * + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/libpq/be-gssapi-common.h + * + *------------------------------------------------------------------------- + */ + +#ifndef BE_GSSAPI_COMMON_H +#define BE_GSSAPI_COMMON_H + +#if defined(HAVE_GSSAPI_H) +#include <gssapi.h> +#else +#include <gssapi/gssapi.h> +#endif + +void pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat); + +#endif /* BE_GSSAPI_COMMON_H */ diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index 1b292d2..0ea87b6 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -48,6 +48,10 @@ ifeq ($(with_openssl),yes) OBJS += fe-secure-openssl.o endif +ifeq ($(with_gssapi),yes) +OBJS += fe-gssapi-common.o +endif + ifeq ($(PORTNAME), cygwin) override shlib = cyg$(NAME)$(DLSUFFIX) endif diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index cd863a5..56528f2 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -47,53 +47,7 @@ /* * GSSAPI authentication system. */ - -#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER) -/* - * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW - * that contain the OIDs required. Redefine here, values copied - * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c - */ -static const gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_desc = -{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"}; -static GSS_DLLIMP gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_desc; -#endif - -/* - * Fetch all errors of a specific type and append to "str". - */ -static void -pg_GSS_error_int(PQExpBuffer str, const char *mprefix, - OM_uint32 stat, int type) -{ - OM_uint32 lmin_s; - gss_buffer_desc lmsg; - OM_uint32 msg_ctx = 0; - - do - { - gss_display_status(&lmin_s, stat, type, - GSS_C_NO_OID, &msg_ctx, &lmsg); - appendPQExpBuffer(str, "%s: %s\n", mprefix, (char *) lmsg.value); - gss_release_buffer(&lmin_s, &lmsg); - } while (msg_ctx); -} - -/* - * GSSAPI errors contain two parts; put both into conn->errorMessage. - */ -static void -pg_GSS_error(const char *mprefix, PGconn *conn, - OM_uint32 maj_stat, OM_uint32 min_stat) -{ - resetPQExpBuffer(&conn->errorMessage); - - /* Fetch major error codes */ - pg_GSS_error_int(&conn->errorMessage, mprefix, maj_stat, GSS_C_GSS_CODE); - - /* Add the minor codes as well */ - pg_GSS_error_int(&conn->errorMessage, mprefix, min_stat, GSS_C_MECH_CODE); -} +#include "fe-gssapi-common.h" /* * Continue GSS authentication with next token as needed. diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c new file mode 100644 index 0000000..bc2c977 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.c @@ -0,0 +1,63 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.c + * The front-end (client) GSSAPI common code + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-gssapi-common.c + * + *------------------------------------------------------------------------- + */ + +#include "fe-auth.h" +#include "fe-gssapi-common.h" + +#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER) +/* + * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW + * that contain the OIDs required. Redefine here, values copied + * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c + */ +static const gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_desc = +{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"}; +static GSS_DLLIMP gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_desc; +#endif + +/* + * Fetch all errors of a specific type and append to "str". + */ +static void +pg_GSS_error_int(PQExpBuffer str, const char *mprefix, + OM_uint32 stat, int type) +{ + OM_uint32 lmin_s; + gss_buffer_desc lmsg; + OM_uint32 msg_ctx = 0; + + do + { + gss_display_status(&lmin_s, stat, type, + GSS_C_NO_OID, &msg_ctx, &lmsg); + appendPQExpBuffer(str, "%s: %s\n", mprefix, (char *) lmsg.value); + gss_release_buffer(&lmin_s, &lmsg); + } while (msg_ctx); +} + +/* + * GSSAPI errors contain two parts; put both into conn->errorMessage. + */ +void +pg_GSS_error(const char *mprefix, PGconn *conn, + OM_uint32 maj_stat, OM_uint32 min_stat) +{ + resetPQExpBuffer(&conn->errorMessage); + + /* Fetch major error codes */ + pg_GSS_error_int(&conn->errorMessage, mprefix, maj_stat, GSS_C_GSS_CODE); + + /* Add the minor codes as well */ + pg_GSS_error_int(&conn->errorMessage, mprefix, min_stat, GSS_C_MECH_CODE); +} diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h new file mode 100644 index 0000000..4b31371 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.h @@ -0,0 +1,21 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.h + * + * Definitions for GSSAPI common routines + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/interfaces/libpq/fe-gssapi-common.h + * + *------------------------------------------------------------------------- + */ + +#ifndef FE_GSSAPI_COMMON_H +#define FE_GSSAPI_COMMON_H + +void pg_GSS_error(const char *mprefix, PGconn *conn, + OM_uint32 maj_stat, OM_uint32 min_stat); + +#endif /* FE_GSSAPI_COMMON_H */ -- 2.7.0 From 14684db3ed32a9034bafd871d2a6b625de66e729 Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Tue, 1 Mar 2016 14:07:53 -0500 Subject: [PATCH 2/3] Connection encryption support for GSSAPI Existing GSSAPI authentication code is extended to support connection encryption. Encryption begins immediately following AUTH_REQ_OK. Fallback for talking to older clients is included, as are both client and server controls for requiring encryption. --- doc/src/sgml/client-auth.sgml | 15 +- doc/src/sgml/libpq.sgml | 12 ++ doc/src/sgml/runtime.sgml | 20 ++- src/backend/libpq/Makefile | 2 +- src/backend/libpq/auth.c | 8 +- src/backend/libpq/be-secure-gssapi.c | 267 ++++++++++++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 ++ src/backend/libpq/hba.c | 9 ++ src/backend/postmaster/postmaster.c | 12 ++ src/backend/utils/init/postinit.c | 9 +- src/backend/utils/misc/guc.c | 30 ++++ src/include/libpq/hba.h | 1 + src/include/libpq/libpq-be.h | 8 + src/include/libpq/libpq.h | 2 + src/interfaces/libpq/Makefile | 2 +- src/interfaces/libpq/fe-connect.c | 88 ++++++++++- src/interfaces/libpq/fe-gssapi-common.c | 12 ++ src/interfaces/libpq/fe-gssapi-common.h | 1 + src/interfaces/libpq/fe-protocol3.c | 5 + src/interfaces/libpq/fe-secure-gssapi.c | 259 +++++++++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-int.h | 18 ++- 22 files changed, 798 insertions(+), 14 deletions(-) create mode 100644 src/backend/libpq/be-secure-gssapi.c create mode 100644 src/interfaces/libpq/fe-secure-gssapi.c diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 3b2935c..1c2519a 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -915,7 +915,8 @@ omicron bryanh guest1 provides automatic authentication (single sign-on) for systems that support it. The authentication itself is secure, but the data sent over the database connection will be sent unencrypted unless - <acronym>SSL</acronym> is used. + <acronym>SSL</acronym> is used, or <acronym>GSSAPI</acronym> encryption + is in use. </para> <para> @@ -1046,6 +1047,18 @@ omicron bryanh guest1 </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>require_encrypt</literal></term> + <listitem> + <para> + Whether to require GSSAPI encryption. Default is off, which causes + GSSAPI encryption to be enabled if available and requested for + compatability with old clients. It is recommended to set this unless + old clients are present. + </para> + </listitem> + </varlistentry> </variablelist> </para> </sect2> diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 2328d8f..c701ebb 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1356,6 +1356,18 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <varlistentry id="libpq-connect-gss-enc-require" xreflabel="gss-enc-require"> + <term><literal>gss_enc_require</literal></term> + <listitem> + <para> + If set, whether to require GSSAPI encryption support from the remote + server. Defaults to unset, which will cause the client to fall back + to not using GSSAPI encryption if the server does not support + encryption through GSSAPI. + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-service" xreflabel="service"> <term><literal>service</literal></term> <listitem> diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index c699f21..87a19cd 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1917,7 +1917,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 </para> <para> - To prevent spoofing on TCP connections, the best solution is to use + To prevent spoofing on TCP connections, one possible solution is to use SSL certificates and make sure that clients check the server's certificate. To do that, the server must be configured to accept only <literal>hostssl</> connections (<xref @@ -1927,6 +1927,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 <literal>verify-full</> and have the appropriate root certificate file installed (<xref linkend="libq-ssl-certificates">). </para> + + <para> + Another way of preventing spoofing on TCP connections is to use GSSAPI + encryption. In order to force all GSSAPI connections to be encrypted, one + should set <literal>require_encrypt</> in <filename>pg_hba.conf</> on + GSSAPI connections. Then the client and server will mutually authenticate, + and the connection will be encrypted once the client and server agree that + the authentication step is complete. + </para> </sect1> <sect1 id="encryption-options"> @@ -2042,6 +2051,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 connect to servers only via SSL. <application>Stunnel</> or <application>SSH</> can also be used to encrypt transmissions. </para> + + <para> + GSSAPI connections can also encrypt all data sent across the network. + In the <filename>pg_hba.conf</> file, the GSSAPI authentication method + has a parameter to require encryption; otherwise connections will be + encrypted if available and requested by the client. On the client side, + there is also a parameter to require GSSAPI encryption support from the + server. + </para> </listitem> </varlistentry> diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index a8cc9a0..5fa43e4 100644 --- a/src/backend/libpq/Makefile +++ b/src/backend/libpq/Makefile @@ -22,7 +22,7 @@ OBJS += be-secure-openssl.o endif ifeq ($(with_gssapi),yes) -OBJS += be-gssapi-common.o +OBJS += be-gssapi-common.o be-secure-gssapi.o endif include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 73d493e..20e8953 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -596,11 +596,11 @@ sendAuthRequest(Port *port, AuthRequest areq) pq_endmessage(&buf); /* - * Flush message so client will see it, except for AUTH_REQ_OK, which need - * not be sent until we are ready for queries. + * In most cases, we do not need to send AUTH_REQ_OK until we are ready + * for queries, but if we are doing GSSAPI encryption that request must go + * out now. */ - if (areq != AUTH_REQ_OK) - pq_flush(); + pq_flush(); CHECK_FOR_INTERRUPTS(); } diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c new file mode 100644 index 0000000..c05eb66 --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,267 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gssapi.c + * GSSAPI encryption support + * + * Portions Copyright (c) 2016, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "libpq/be-gssapi-common.h" + +#include "postgres.h" + +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" + +static ssize_t +be_gssapi_should_crypto(Port *port) +{ + if (port->gss->ctx == GSS_C_NO_CONTEXT) + return 0; + return gss_encrypt; +} + +ssize_t +be_gssapi_write(Port *port, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t ret; + int conf; + uint32 netlen; + char lenbuf[4]; + struct iovec iov[2]; + + ret = be_gssapi_should_crypto(port); + if (ret == -1) + return -1; + else if (ret == 0) + return secure_raw_write(port, ptr, len); + + if (port->gss->writebuf.len != 0) + { + ret = send(port->sock, + port->gss->writebuf.data + port->gss->writebuf.cursor, + port->gss->writebuf.len - port->gss->writebuf.cursor, + 0); + if (ret < 0) + return ret; + + port->gss->writebuf.cursor += ret; + if (port->gss->writebuf.cursor == port->gss->writebuf.len) + { + port->gss->writebuf.len = port->gss->writebuf.cursor = 0; + port->gss->writebuf.data[0] = '\0'; + /* The entire request has now been written */ + return len; + } + /* need to be called again */ + return 0; + } + + output.value = NULL; + output.length = 0; + + input.value = ptr; + input.length = len; + + conf = 0; + major = gss_wrap(&minor, port->gss->ctx, 1, GSS_C_QOP_DEFAULT, + &input, &conf, &output); + if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, + gettext_noop("GSSAPI wrap error"), + major, minor); + ret = -1; + goto cleanup; + } + else if (conf == 0) + { + ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality"))); + ret = -1; + goto cleanup; + } + + netlen = htonl(output.length); + memcpy(lenbuf, &netlen, 4); + iov[0].iov_base = lenbuf; + iov[0].iov_len = 4; + iov[1].iov_base = output.value; + iov[1].iov_len = output.length; + ret = writev(port->sock, iov, 2); + if (ret == output.length + 4) + { + /* + * Strictly speaking, this isn't true; we did write more than `len` + * bytes. However, this information is actually used to keep track of + * what has/hasn't been written yet, not actually report the number of + * bytes we wrote. + */ + ret = len; + goto cleanup; + } + else if (ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK) + { + ereport(FATAL, (errmsg("Failed to send entire GSSAPI blob"))); + ret = -1; + goto cleanup; + } + + if (ret < 4) + { + appendBinaryStringInfo(&port->gss->writebuf, lenbuf + ret, 4 - ret); + ret = 0; + } + else + { + ret -= 4; + } + appendBinaryStringInfo(&port->gss->writebuf, (char *)output.value + ret, + output.length - ret); + + /* Set return so that we get retried when the socket becomes writable */ + ret = 0; + cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + + return ret; +} + +static ssize_t +be_gssapi_read_from_buffer(Port *port, void *ptr, size_t len) +{ + ssize_t ret = 0; + + if (port->gss->buf.len > 4 && port->gss->buf.cursor < port->gss->buf.len) + { + if (len > port->gss->buf.len - port->gss->buf.cursor) + len = port->gss->buf.len - port->gss->buf.cursor; + + memcpy(ptr, port->gss->buf.data + port->gss->buf.cursor, len); + port->gss->buf.cursor += len; + + ret = len; + } + + if (port->gss->buf.cursor == port->gss->buf.len) + { + port->gss->buf.cursor = port->gss->buf.len = 0; + port->gss->buf.data[0] = '\0'; + } + + return ret; +} + +/* + * Here's how the buffering works: + * + * First, we read the packet into port->gss->buf.data. The first four bytes + * of this will be the network-order length of the GSSAPI-encrypted blob; from + * position 4 to port->gss->buf.len is then this blob. Therefore, at this + * point port->gss->buf.len is the length of the blob plus 4. + * port->gss->buf.cursor is zero for this entire step. + * + * Then we overwrite port->gss->buf.data entirely with the decrypted contents. + * At this point, port->gss->buf.len reflects the actual length of the + * decrypted data. port->gss->buf.cursor is then used to incrementally return + * this data to the caller and is therefore nonzero during this step. + * + * Once all decrypted data is returned to the caller, the cycle repeats. + */ +ssize_t +be_gssapi_read(Port *port, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t ret; + int conf = 0; + + ret = be_gssapi_should_crypto(port); + if (ret == -1) + return -1; + else if (ret == 0) + return secure_raw_read(port, ptr, len); + + if (len == 0) + return 0; + + if (port->gss->buf.cursor > 0) + { + ret = be_gssapi_read_from_buffer(port, ptr, len); + if (ret > 0) + return ret + be_gssapi_read(port, (char *)ptr + ret, len - ret); + } + + /* our buffer is now empty */ + if (port->gss->buf.len < 4) + { + enlargeStringInfo(&port->gss->buf, 4); + ret = secure_raw_read(port, port->gss->buf.data + port->gss->buf.len, + 4 - port->gss->buf.len); + if (ret < 0) + return ret; + + port->gss->buf.len += ret; + port->gss->buf.data[port->gss->buf.len] = '\0'; + if (port->gss->buf.len < 4) + return 0; + } + + /* we know the length of the packet at this point */ + memcpy((char *)&input.length, port->gss->buf.data, 4); + input.length = ntohl(input.length); + enlargeStringInfo(&port->gss->buf, input.length - port->gss->buf.len + 4); + + ret = secure_raw_read(port, port->gss->buf.data + port->gss->buf.len, + input.length - port->gss->buf.len + 4); + if (ret < 0) + return ret; + + port->gss->buf.len += ret; + port->gss->buf.data[port->gss->buf.len] = '\0'; + if (port->gss->buf.len - 4 < input.length) + return 0; + + output.value = NULL; + output.length = 0; + input.value = port->gss->buf.data + 4; + major = gss_unwrap(&minor, port->gss->ctx, &input, &output, &conf, NULL); + if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, + gettext_noop("GSSAPI unwrap error"), + major, minor); + ret = -1; + goto cleanup; + } + else if (conf == 0) + { + ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality"))); + ret = -1; + goto cleanup; + } + + port->gss->buf.cursor = port->gss->buf.len = 0; + port->gss->buf.data[0] = '\0'; + enlargeStringInfo(&port->gss->buf, output.length); + memcpy(port->gss->buf.data, output.value, output.length); + port->gss->buf.len = output.length; + port->gss->buf.data[port->gss->buf.len] = '\0'; + + ret = be_gssapi_read_from_buffer(port, ptr, len); + cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + + return ret; +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index ac709d1..d43fa1b 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -132,6 +132,14 @@ retry: } else #endif +#ifdef ENABLE_GSS + if (port->gss != NULL) + { + n = be_gssapi_read(port, ptr, len); + waitfor = WL_SOCKET_READABLE; + } + else +#endif { n = secure_raw_read(port, ptr, len); waitfor = WL_SOCKET_READABLE; @@ -234,6 +242,14 @@ retry: } else #endif +#ifdef ENABLE_GSS + if (port->gss != NULL) + { + n = be_gssapi_write(port, ptr, len); + waitfor = WL_SOCKET_WRITEABLE; + } + else +#endif { n = secure_raw_write(port, ptr, len); waitfor = WL_SOCKET_WRITEABLE; diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 28f9fb5..509b9ab 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1570,6 +1570,15 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num) else hbaline->include_realm = false; } + else if (strcmp(name, "require_encrypt") == 0) + { + if (hbaline->auth_method != uaGSS) + INVALID_AUTH_OPTION("require_encrypt", "gssapi"); + if (strcmp(val, "1") == 0) + hbaline->require_encrypt = true; + else + hbaline->require_encrypt = false; + } else if (strcmp(name, "radiusserver") == 0) { struct addrinfo *gai_result; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index b16fc28..d2f2a63 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -2355,6 +2355,10 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); +#endif #endif return port; @@ -2371,7 +2375,15 @@ ConnFree(Port *conn) secure_close(conn); #endif if (conn->gss) + { +#ifdef ENABLE_GSS + if (conn->gss->buf.data) + pfree(conn->gss->buf.data); + if (conn->gss->writebuf.data) + pfree(conn->gss->writebuf.data); +#endif free(conn->gss); + } free(conn); } diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index e22d4db..14284b8 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -32,7 +32,7 @@ #include "catalog/pg_db_role_setting.h" #include "catalog/pg_tablespace.h" #include "libpq/auth.h" -#include "libpq/libpq-be.h" +#include "libpq/libpq.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "pgstat.h" @@ -1087,6 +1087,13 @@ process_startup_options(Port *port, bool am_superuser) SetConfigOption(name, value, gucctx, PGC_S_CLIENT); } + + if (!gss_encrypt && port->hba->require_encrypt) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("GSSAPI encryption required from user \"%s\"", + port->user_name))); + } } /* diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index ea5a09a..ea0c266 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -186,6 +186,9 @@ static const char *show_log_file_mode(void); static ConfigVariable *ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel); +static void assign_gss_encrypt(bool newval, void *extra); +static bool check_gss_encrypt(bool *newval, void **extra, GucSource source); + /* * Options for enum values defined in this module. @@ -498,6 +501,9 @@ static bool assert_enabled; /* should be static, but commands/variable.c needs to get at this */ char *role_string; +/* GUC for GSSAPI encryption handling */ +bool gss_encrypt; + /* * Displayable names for context types (enum GucContext) @@ -1632,6 +1638,15 @@ static struct config_bool ConfigureNamesBool[] = NULL, NULL, NULL }, + { + {"gss_encrypt", PGC_USERSET, CONN_AUTH_SECURITY, + gettext_noop("Whether client wants encryption for this connection."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &gss_encrypt, false, check_gss_encrypt, assign_gss_encrypt, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL @@ -10184,4 +10199,19 @@ show_log_file_mode(void) return buf; } +static void +assign_gss_encrypt(bool newval, void *extra) +{ + gss_encrypt = newval; +} + +static bool +check_gss_encrypt(bool *newval, void **extra, GucSource source) +{ + if (MyProcPort && MyProcPort->hba && MyProcPort->hba->require_encrypt && + !*newval) + return false; + return true; +} + #include "guc-file.c" diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 68a953a..3435674 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -77,6 +77,7 @@ typedef struct HbaLine bool clientcert; char *krb_realm; bool include_realm; + bool require_encrypt; char *radiusserver; char *radiussecret; char *radiusidentifier; diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 5d07b78..ce58099 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -68,6 +68,7 @@ typedef struct #include "datatype/timestamp.h" #include "libpq/hba.h" #include "libpq/pqcomm.h" +#include "lib/stringinfo.h" typedef enum CAC_state @@ -88,6 +89,8 @@ typedef struct gss_cred_id_t cred; /* GSSAPI connection cred's */ gss_ctx_id_t ctx; /* GSSAPI connection context */ gss_name_t name; /* GSSAPI client name */ + StringInfoData buf; /* GSSAPI encryption data buffering */ + StringInfoData writebuf; /* GSSAPI nonblocking write buffering */ #endif } pg_gssinfo; #endif @@ -214,6 +217,11 @@ 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); #endif +#ifdef ENABLE_GSS +ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); +ssize_t be_gssapi_write(Port *port, void *ptr, size_t len); +#endif + extern ProtocolVersion FrontendProtocol; /* TCP keepalives configuration. These are no-ops on an AF_UNIX socket. */ diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 0569994..0c7653c 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -100,4 +100,6 @@ extern char *SSLCipherSuites; extern char *SSLECDHCurve; extern bool SSLPreferServerCiphers; +extern bool gss_encrypt; + #endif /* LIBPQ_H */ diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index 0ea87b6..e834d12 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -49,7 +49,7 @@ OBJS += fe-secure-openssl.o endif ifeq ($(with_gssapi),yes) -OBJS += fe-gssapi-common.o +OBJS += fe-gssapi-common.o fe-secure-gssapi.o endif ifeq ($(PORTNAME), cygwin) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5ad4755..ed63fca 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -72,6 +72,10 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, PQExpBuffer errorMessage); #endif +#ifdef ENABLE_GSS +#include "fe-gssapi-common.h" +#endif + #include "libpq/ip.h" #include "mb/pg_wchar.h" @@ -91,8 +95,10 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, * application_name in a startup packet. We hard-wire the value rather * than looking into errcodes.h since it reflects historical behavior * rather than that of the current code. + * + * Servers that do not support GSSAPI encryption will also return this error. */ -#define ERRCODE_APPNAME_UNKNOWN "42704" +#define ERRCODE_UNKNOWN_PARAM "42704" /* This is part of the protocol so just define it */ #define ERRCODE_INVALID_PASSWORD "28P01" @@ -296,6 +302,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = { offsetof(struct pg_conn, gsslib)}, #endif +#if defined(ENABLE_GSS) + {"gss_enc_require", "GSS_ENC_REQUIRE", "0", NULL, + "Require-GSS-encryption", "", 1, /* should be '0' or '1' */ + offsetof(struct pg_conn, gss_enc_require)}, +#endif + {"replication", NULL, NULL, NULL, "Replication", "D", 5, offsetof(struct pg_conn, replication)}, @@ -2518,6 +2530,25 @@ keep_going: /* We will come back to here until there is /* We are done with authentication exchange */ conn->status = CONNECTION_AUTH_OK; +#ifdef ENABLE_GSS + if (conn->gctx != 0) + conn->gss_auth_done = true; + + if (pg_GSS_should_crypto(conn)) + { + /* + * If we've any data from the server buffered, it's + * encrypted and we need to decrypt it. Pass it back + * down a layer to decrypt. At this point in time, + * conn->inStart and conn->inCursor match. + */ + appendBinaryPQExpBuffer(&conn->gwritebuf, + conn->inBuffer + conn->inStart, + conn->inEnd - conn->inStart); + conn->inEnd = conn->inStart; + } +#endif + /* * Set asyncStatus so that PQgetResult will think that * what comes back next is the result of a query. See @@ -2558,6 +2589,37 @@ keep_going: /* We will come back to here until there is if (res->resultStatus != PGRES_FATAL_ERROR) appendPQExpBufferStr(&conn->errorMessage, libpq_gettext("unexpected message from server during startup\n")); +#ifdef ENABLE_GSS + else if (!conn->gss_disable_enc && + *conn->gss_enc_require != '1') + { + /* + * We tried to request GSSAPI encryption, but the + * server doesn't support it. Retries are permitted + * here, so hang up and try again. A connection that + * doesn't rupport appname will also not support + * GSSAPI encryption, so this check goes before that + * check. See comment below. + */ + const char *sqlstate; + + sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE); + if (sqlstate && + strcmp(sqlstate, ERRCODE_UNKNOWN_PARAM) == 0) + { + OM_uint32 minor; + + PQclear(res); + conn->gss_disable_enc = true; + /* Must drop the old connection */ + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + gss_delete_sec_context(&minor, &conn->gctx, + GSS_C_NO_BUFFER); + goto keep_going; + } + } +#endif else if (conn->send_appname && (conn->appname || conn->fbappname)) { @@ -2575,7 +2637,7 @@ keep_going: /* We will come back to here until there is sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE); if (sqlstate && - strcmp(sqlstate, ERRCODE_APPNAME_UNKNOWN) == 0) + strcmp(sqlstate, ERRCODE_UNKNOWN_PARAM) == 0) { PQclear(res); conn->send_appname = false; @@ -2585,7 +2647,17 @@ keep_going: /* We will come back to here until there is goto keep_going; } } - +#ifdef ENABLE_GSS + else if (*conn->gss_enc_require == '1') + { + /* + * It has been determined that appname was not the + * cause of connection failure, so give up. + */ + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("Server does not support required GSSAPI encryption\n")); + } +#endif /* * if the resultStatus is FATAL, then conn->errorMessage * already has a copy of the error; needn't copy it back. @@ -2798,6 +2870,10 @@ makeEmptyPGconn(void) conn->wait_ssl_try = false; conn->ssl_in_use = false; #endif +#ifdef ENABLE_GSS + initPQExpBuffer(&conn->gbuf); + initPQExpBuffer(&conn->gwritebuf); +#endif /* * We try to send at least 8K at a time, which is the usual size of pipe @@ -2914,6 +2990,10 @@ freePGconn(PGconn *conn) if (conn->krbsrvname) free(conn->krbsrvname); #endif +#if defined(ENABLE_GSS) + termPQExpBuffer(&conn->gbuf); + termPQExpBuffer(&conn->gwritebuf); +#endif #if defined(ENABLE_GSS) && defined(ENABLE_SSPI) if (conn->gsslib) free(conn->gsslib); @@ -3019,6 +3099,8 @@ closePGconn(PGconn *conn) gss_release_buffer(&min_s, &conn->ginbuf); if (conn->goutbuf.length) gss_release_buffer(&min_s, &conn->goutbuf); + resetPQExpBuffer(&conn->gbuf); + resetPQExpBuffer(&conn->gwritebuf); } #endif #ifdef ENABLE_SSPI diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c index bc2c977..cd7ae5a 100644 --- a/src/interfaces/libpq/fe-gssapi-common.c +++ b/src/interfaces/libpq/fe-gssapi-common.c @@ -61,3 +61,15 @@ pg_GSS_error(const char *mprefix, PGconn *conn, /* Add the minor codes as well */ pg_GSS_error_int(&conn->errorMessage, mprefix, min_stat, GSS_C_MECH_CODE); } + +/* + * Only consider encryption when GSS context is complete + */ +ssize_t +pg_GSS_should_crypto(PGconn *conn) +{ + if (conn->gctx == GSS_C_NO_CONTEXT) + return 0; + else + return conn->gss_auth_done && !conn->gss_disable_enc; +} diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h index 4b31371..e9cc9c7 100644 --- a/src/interfaces/libpq/fe-gssapi-common.h +++ b/src/interfaces/libpq/fe-gssapi-common.h @@ -17,5 +17,6 @@ void pg_GSS_error(const char *mprefix, PGconn *conn, OM_uint32 maj_stat, OM_uint32 min_stat); +ssize_t pg_GSS_should_crypto(PGconn *conn); #endif /* FE_GSSAPI_COMMON_H */ diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 43898a4..296d2fd 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -2125,6 +2125,11 @@ build_startup_packet(const PGconn *conn, char *packet, if (conn->client_encoding_initial && conn->client_encoding_initial[0]) ADD_STARTUP_OPTION("client_encoding", conn->client_encoding_initial); +#ifdef ENABLE_GSS + if (!conn->gss_disable_enc) + ADD_STARTUP_OPTION("gss_encrypt", "on"); +#endif + /* Add any environment-driven GUC settings needed */ for (next_eo = options; next_eo->envName; next_eo++) { diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c new file mode 100644 index 0000000..d6fbc68 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,259 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-gssapi.c + * The front-end (client) encryption support for GSSAPI + * + * Portions Copyright (c) 2016, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/interfaces/libpq/fe-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "libpq-fe.h" +#include "libpq-int.h" +#include "fe-gssapi-common.h" + +ssize_t +pg_GSS_write(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t ret; + int conf; + uint32 netlen; + char lenbuf[4]; + struct iovec iov[2]; + + ret = pg_GSS_should_crypto(conn); + if (ret == -1) + return -1; + else if (ret == 0) + return pqsecure_raw_write(conn, ptr, len); + + if (conn->gwritebuf.len != 0) + { + ret = send(conn->sock, conn->gwritebuf.data + conn->gwritecurs, + conn->gwritebuf.len - conn->gwritecurs, 0); + if (ret < 0) + return ret; + conn->gwritecurs += ret; + if (conn->gwritecurs == conn->gwritebuf.len) + { + conn->gwritebuf.len = conn->gwritecurs = 0; + conn->gwritebuf.data[0] = '\0'; + /* The entire request has now been written */ + return len; + } + /* need to be called again */ + return 0; + } + + output.value = NULL; + output.length = 0; + + input.value = ptr; + input.length = len; + + conf = 0; + major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT, + &input, &conf, &output); + if (GSS_ERROR(major)) + { + pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, + major, minor); + ret = -1; + goto cleanup; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI did not provide confidentiality\n")); + ret = -1; + goto cleanup; + } + + netlen = htonl(output.length); + memcpy(lenbuf, &netlen, 4); + iov[0].iov_base = lenbuf; + iov[0].iov_len = 4; + iov[1].iov_base = output.value; + iov[1].iov_len = output.length; + errno = 0; + ret = writev(conn->sock, iov, 2); + if (ret == output.length + 4) + { + /* + * pqsecure_write expects the return value, when >= 0, to be the + * number bytes from ptr delivered, not the number of bytes actually + * written to socket. + */ + ret = len; + goto cleanup; + } + else if (ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI writev() failed to send everything\n")); + ret = -1; + goto cleanup; + } + + if (ret < 4) + { + appendBinaryPQExpBuffer(&conn->gwritebuf, lenbuf + ret, 4 - ret); + ret = 0; + } + else + { + ret -= 4; + } + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)output.value + 4 - ret, + output.length + 4 - ret); + + /* Set return so that we get retried when the socket becomes writable */ + ret = 0; + cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + + return ret; +} + +static ssize_t +pg_GSS_read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + if (conn->gcursor < conn->gbuf.len) + { + if (len > conn->gbuf.len - conn->gcursor) + len = conn->gbuf.len - conn->gcursor; + + memcpy(ptr, conn->gbuf.data + conn->gcursor, len); + conn->gcursor += len; + ret = len; + } + + if (conn->gcursor == conn->gbuf.len) + { + conn->gcursor = conn->gbuf.len = 0; + conn->gbuf.data[0] = '\0'; + } + + return ret; +} + +/* + * Buffering behaves as in be_gssapi_read (in be-gssapi.c). Because this is + * the frontend, we use a PQExpBuffer at conn->gbuf instead of a StringInfo, + * and so there is an additional, separate cursor field in the structure. + */ +ssize_t +pg_GSS_read(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t ret; + int conf = 0; + + ret = pg_GSS_should_crypto(conn); + if (ret == -1) + return -1; + else if (ret == 0) + return pqsecure_raw_read(conn, ptr, len); + + if (len == 0) + return 0; + + if (conn->gcursor > 0) + { + ret = pg_GSS_read_from_buffer(conn, ptr, len); + if (ret > 0) + return ret + pg_GSS_read(conn, (char *)ptr + ret, len - ret); + } + + /* our buffer is now empty */ + if (conn->gbuf.len < 4) + { + ret = enlargePQExpBuffer(&conn->gbuf, 4); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "Failed to fit packet length in buffer\n")); + return -1; + } + ret = pqsecure_raw_read(conn, conn->gbuf.data, 4); + if (ret < 0) + /* error already set by secure_raw_read */ + return ret; + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + return 0; + } + + /* we know the length of the packet at this point */ + memcpy((char *)&input.length, conn->gbuf.data, 4); + input.length = ntohl(input.length); + ret = enlargePQExpBuffer(&conn->gbuf, input.length - conn->gbuf.len + 4); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI encrypted packet (length %ld) too big\n"), + input.length); + return -1; + } + + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + input.length - conn->gbuf.len + 4); + if (ret < 0) + return ret; + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < input.length) + return 0; + + output.value = NULL; + output.length = 0; + input.value = conn->gbuf.data + 4; + major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL); + if (GSS_ERROR(major)) + { + pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), conn, + major, minor); + ret = -1; + goto cleanup; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI did not provide confidentiality\n")); + ret = -1; + goto cleanup; + } + + conn->gcursor = conn->gbuf.len = 0; + conn->gbuf.data[0] = '\0'; + ret = enlargePQExpBuffer(&conn->gbuf, output.length); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI decrypted packet (length %ld) too big\n"), + output.length); + return -1; + } + memcpy(conn->gbuf.data, output.value, output.length); + conn->gbuf.len = output.length; + conn->gbuf.data[conn->gbuf.len] = '\0'; + + ret = pg_GSS_read_from_buffer(conn, ptr, len); + + cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index 94e47a5..14fba1f 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -213,6 +213,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) } else #endif +#ifdef ENABLE_GSS + if (conn->gctx != GSS_C_NO_CONTEXT) + { + n = pg_GSS_read(conn, ptr, len); + } + else +#endif { n = pqsecure_raw_read(conn, ptr, len); } @@ -279,7 +286,7 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len) * to determine whether to continue/retry after error. */ ssize_t -pqsecure_write(PGconn *conn, const void *ptr, size_t len) +pqsecure_write(PGconn *conn, void *ptr, size_t len) { ssize_t n; @@ -290,6 +297,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) } else #endif +#ifdef ENABLE_GSS + if (conn->gctx != GSS_C_NO_CONTEXT) + { + n = pg_GSS_write(conn, ptr, len); + } + else +#endif { n = pqsecure_raw_write(conn, ptr, len); } diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 6c9bbf7..7f3b40e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -23,6 +23,7 @@ /* We assume libpq-fe.h has already been included. */ #include "postgres_fe.h" #include "libpq-events.h" +#include "lib/stringinfo.h" #include <time.h> #include <sys/types.h> @@ -445,6 +446,13 @@ struct pg_conn gss_name_t gtarg_nam; /* GSS target name */ gss_buffer_desc ginbuf; /* GSS input token */ gss_buffer_desc goutbuf; /* GSS output token */ + PQExpBufferData gbuf; /* GSS encryption buffering */ + size_t gcursor; /* GSS buffering position */ + PQExpBufferData gwritebuf; /* GSS nonblocking write buffering */ + size_t gwritecurs; /* GSS write buffer position */ + bool gss_disable_enc; /* GSS encryption recognized by server */ + bool gss_auth_done; /* GSS authentication finished */ + char *gss_enc_require; /* GSS encryption required */ #endif #ifdef ENABLE_SSPI @@ -620,7 +628,7 @@ extern void pqsecure_destroy(void); extern PostgresPollingStatusType pqsecure_open_client(PGconn *); extern void pqsecure_close(PGconn *); extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len); -extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len); +extern ssize_t pqsecure_write(PGconn *, void *ptr, size_t len); extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len); extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len); @@ -642,6 +650,14 @@ extern bool pgtls_read_pending(PGconn *conn); extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len); /* + * The GSSAPI backend in fe-secure-gssapi.c provides these functions. + */ +#ifdef ENABLE_GSS +extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len); +extern ssize_t pg_GSS_write(PGconn *conn, void *ptr, size_t len); +#endif + +/* * this is so that we can check if a connection is non-blocking internally * without the overhead of a function call */ -- 2.7.0 From 67ef63a681c9eef27d22975b98b8968cd63ace95 Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Tue, 8 Mar 2016 17:16:29 -0500 Subject: [PATCH 3/3] GSSAPI authentication cleanup Become more fussy about what flags we need. Now that we want to do encryption, protection and integrity are needed for that to work. Other protections are desirable as well, and we should check the flags GSSAPI returns to us. Also remove the (now-)redundant definitions that worked around an old bug with MIT Kerberos on Windows. --- src/backend/libpq/auth.c | 14 +++++++++++--- src/backend/libpq/be-gssapi-common.c | 11 ----------- src/interfaces/libpq/fe-auth.c | 19 ++++++++++++++++--- src/interfaces/libpq/fe-gssapi-common.c | 11 ----------- 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 20e8953..3ce40a7 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -717,7 +717,8 @@ pg_GSS_recvauth(Port *port) OM_uint32 maj_stat, min_stat, lmin_s, - gflags; + gflags, + target_flags; int mtype; int ret; StringInfoData buf; @@ -815,8 +816,7 @@ pg_GSS_recvauth(Port *port) elog(DEBUG4, "Processing received GSS token of length %u", (unsigned int) gbuf.length); - maj_stat = gss_accept_sec_context( - &min_stat, + maj_stat = gss_accept_sec_context(&min_stat, &port->gss->ctx, port->gss->cred, &gbuf, @@ -872,6 +872,14 @@ pg_GSS_recvauth(Port *port) gss_release_cred(&min_stat, &port->gss->cred); } + target_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | + GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; + if ((gflags & target_flags) != target_flags) + { + ereport(FATAL, (errmsg("GSSAPI did no provide required flags"))); + return STATUS_ERROR; + } + /* * GSS_S_COMPLETE indicates that authentication is now complete. * diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c index 541a18e..73b7962 100644 --- a/src/backend/libpq/be-gssapi-common.c +++ b/src/backend/libpq/be-gssapi-common.c @@ -17,17 +17,6 @@ #include "postgres.h" -#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER) -/* - * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW - * that contain the OIDs required. Redefine here, values copied - * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c - */ -static const gss_OID_desc GSS_C_NT_USER_NAME_desc = -{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"}; -static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc; -#endif - void pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat) { diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index 56528f2..76bc3ec 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -57,20 +57,24 @@ pg_GSS_continue(PGconn *conn) { OM_uint32 maj_stat, min_stat, - lmin_s; + lmin_s, + req_flags, + ret_flags; + req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG | + GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &conn->gctx, conn->gtarg_nam, GSS_C_NO_OID, - GSS_C_MUTUAL_FLAG, + req_flags, 0, GSS_C_NO_CHANNEL_BINDINGS, (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf, NULL, &conn->goutbuf, - NULL, + &ret_flags, NULL); if (conn->gctx != GSS_C_NO_CONTEXT) @@ -109,8 +113,17 @@ pg_GSS_continue(PGconn *conn) } if (maj_stat == GSS_S_COMPLETE) + { gss_release_name(&lmin_s, &conn->gtarg_nam); + if ((ret_flags & req_flags) != req_flags) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("GSSAPI did not provide required flags\n")); + return STATUS_ERROR; + } + } + return STATUS_OK; } diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c index cd7ae5a..0ad09f7 100644 --- a/src/interfaces/libpq/fe-gssapi-common.c +++ b/src/interfaces/libpq/fe-gssapi-common.c @@ -15,17 +15,6 @@ #include "fe-auth.h" #include "fe-gssapi-common.h" -#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER) -/* - * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW - * that contain the OIDs required. Redefine here, values copied - * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c - */ -static const gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_desc = -{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"}; -static GSS_DLLIMP gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_desc; -#endif - /* * Fetch all errors of a specific type and append to "str". */ -- 2.7.0
Attachment
pgsql-hackers by date: