Thread: [PATCH v1] GSSAPI encryption support
Hello -hackers, As previously discussed on this list, I have coded up GSSAPI encryption support. If it is easier for anyone, this code is also available for viewing on my github: https://github.com/postgres/postgres/compare/master...frozencemetery:feature/gssencrypt Fallback support is present in both directions for talking to old client and old servers; GSSAPI encryption is by default auto-upgraded to where available (for compatibility), but both client and server contain settings for requiring it. There are 8 commits in this series; I have tried to err on the side of creating too much separation rather than too little. A patch for each is attached. This is v1 of the series. Thanks! From f506ba6ab6755f56c8aadba7d72a8839d5fbc0d9 Mon Sep 17 00:00:00 2001 From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> Date: Mon, 8 Jun 2015 19:27:45 -0400 Subject: build: Define with_gssapi for use in Makefiles This is needed in order to control build of GSSAPI components. --- configure | 2 ++ configure.in | 1 + src/Makefile.global.in | 1 + 3 files changed, 4 insertions(+) diff --git a/configure b/configure index 0407c4f..b9bab06 100755 --- a/configure +++ b/configure @@ -711,6 +711,7 @@ with_uuid with_selinux with_openssl krb_srvtab +with_gssapi with_python with_perl with_tcl @@ -5452,6 +5453,7 @@ $as_echo "$with_gssapi" >&6; } + # # Kerberos configuration parameters # diff --git a/configure.in b/configure.in index 1de41a2..113bd65 100644 --- a/configure.in +++ b/configure.in @@ -635,6 +635,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 c583b44..e50c87d 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -167,6 +167,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_libxml = @with_libxml@ with_libxslt = @with_libxslt@ -- 2.1.4 From d5b973752968f87c9bb2ff9434d523657eb4ba67 Mon Sep 17 00:00:00 2001 From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> Date: Mon, 8 Jun 2015 20:16:42 -0400 Subject: client: Disable GSS encryption on old servers --- src/interfaces/libpq/fe-connect.c | 34 ++++++++++++++++++++++++++++++++-- src/interfaces/libpq/fe-protocol3.c | 5 +++++ src/interfaces/libpq/libpq-int.h | 1 + 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index a45f4cb..c6c551a 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -91,8 +91,9 @@ 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 GSS 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" @@ -2552,6 +2553,35 @@ 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) + { + /* + * We tried to request GSS encryption, but the server + * doesn't support it. Hang up and try again. A + * connection that doesn't support 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); + 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)) { @@ -2569,7 +2599,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; diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index a847f08..0deaa0f 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -2051,6 +2051,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/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 2175957..1578d76 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -445,6 +445,7 @@ 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 */ + bool gss_disable_enc; /* Does server recognize gss_encrypt? */ #endif #ifdef ENABLE_SSPI -- 2.1.4 From 36bd232742eb2b920d2cd88dd06176cde7e26cb2 Mon Sep 17 00:00:00 2001 From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> Date: Mon, 8 Jun 2015 21:20:23 -0400 Subject: client: GSSAPI encryption and decryption --- src/interfaces/libpq/Makefile | 4 ++ src/interfaces/libpq/fe-auth.c | 2 +- src/interfaces/libpq/fe-auth.h | 5 ++ src/interfaces/libpq/fe-connect.c | 5 ++ src/interfaces/libpq/fe-misc.c | 5 ++ src/interfaces/libpq/fe-protocol3.c | 27 +++++++++++ src/interfaces/libpq/fe-secure-gss.c | 92 ++++++++++++++++++++++++++++++++++++ src/interfaces/libpq/libpq-int.h | 7 +++ 8 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/interfaces/libpq/fe-secure-gss.c diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index c2105f1..a9fb194 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-secure-gss.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 5891c75..af6dfff 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -82,7 +82,7 @@ pg_GSS_error_int(PQExpBuffer str, const char *mprefix, /* * GSSAPI errors contain two parts; put both into conn->errorMessage. */ -static void +void pg_GSS_error(const char *mprefix, PGconn *conn, OM_uint32 maj_stat, OM_uint32 min_stat) { diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h index 8d35767..5702a2d 100644 --- a/src/interfaces/libpq/fe-auth.h +++ b/src/interfaces/libpq/fe-auth.h @@ -21,4 +21,9 @@ extern int pg_fe_sendauth(AuthRequest areq, PGconn *conn); extern char *pg_fe_getauthname(PQExpBuffer errorMessage); +#ifdef ENABLE_GSS +void pg_GSS_error(const char *mprefix, PGconn *conn, OM_uint32 maj_stat, + OM_uint32 min_stat); +#endif + #endif /* FE_AUTH_H */ diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index c6c551a..8fb0a90 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -2513,6 +2513,11 @@ 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; +#endif + /* * Set asyncStatus so that PQgetResult will think that * what comes back next is the result of a query. See diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c index 0dbcf73..2379aff 100644 --- a/src/interfaces/libpq/fe-misc.c +++ b/src/interfaces/libpq/fe-misc.c @@ -604,6 +604,11 @@ pqPutMsgEnd(PGconn *conn) memcpy(conn->outBuffer + conn->outMsgStart, &msgLen, 4); } +#ifdef ENABLE_GSS + if (pggss_encrypt(conn) < 0) + return EOF; +#endif + /* Make message eligible to send */ conn->outCount = conn->outMsgEnd; diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 0deaa0f..65acfd1 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -129,6 +129,33 @@ pqParseInput3(PGconn *conn) return; } +#ifdef ENABLE_GSS + /* We want to be ready in both IDLE and BUSY states for encryption */ + if (id == 'g') + { + ssize_t encEnd, next; + + encEnd = pggss_inplace_decrypt(conn, msgLength); + if (encEnd <= 0) + { + /* error message placed by pggss_inplace_decrypt() */ + pqSaveErrorResult(conn); + conn->asyncStatus = PGASYNC_READY; + pqDropConnection(conn); + conn->status = CONNECTION_BAD; + return; + } + + /* shift contents of buffer to account for slack */ + encEnd += conn->inStart; + next = conn->inStart + msgLength + 5; + memmove(conn->inBuffer + encEnd, conn->inBuffer + next, + conn->inEnd - next); + conn->inEnd = (conn->inEnd - next) + encEnd; + continue; + } +#endif + /* * NOTIFY and NOTICE messages can happen in any state; always process * them right away. diff --git a/src/interfaces/libpq/fe-secure-gss.c b/src/interfaces/libpq/fe-secure-gss.c new file mode 100644 index 0000000..afea9c3 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gss.c @@ -0,0 +1,92 @@ +#include <assert.h> + +#include "libpq-fe.h" +#include "postgres_fe.h" +#include "fe-auth.h" +#include "libpq-int.h" + +ssize_t +pggss_inplace_decrypt(PGconn *conn, int gsslen) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t n; + int conf; + + input.length = gsslen; + input.value = conn->inBuffer + conn->inCursor; + output.length = 0; + output.value = NULL; + + major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL); + if (GSS_ERROR(major)) + { + pg_GSS_error("GSSAPI unwrap error", conn, major, minor); + return -1; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "received GSSAPI message without confidentiality\n")); + return -1; + } + + memcpy(conn->inBuffer + conn->inStart, output.value, output.length); + n = output.length; + gss_release_buffer(&minor, &output); + return n; +} + +int +pggss_encrypt(PGconn *conn) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + int msgLen, conf; + uint32 len_n; + + if (conn->gss_disable_enc || !conn->gctx || !conn->gss_auth_done) + return 0; + assert(conn->outMsgStart > 0); + + /* We need to encrypt message type as well */ + conn->outMsgStart -= 1; + msgLen = conn->outMsgEnd - conn->outMsgStart; + + input.value = conn->outBuffer + conn->outMsgStart; + input.length = msgLen; + output.length = 0; + output.value = NULL; + + major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT, &input, &conf, + &output); + if (GSS_ERROR(major)) + { + pg_GSS_error("GSSAPI wrap error", conn, major, minor); + return -1; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "Failed to obtain confidentiality for outgoing GSSAPI message\n")); + return -1; + } + + msgLen = output.length + 4; + if (pqCheckOutBufferSpace(conn->outMsgStart + msgLen + 1, conn)) + return -1; + + conn->outBuffer[conn->outMsgStart] = 'g'; /* GSSAPI message */ + + len_n = htonl(msgLen); + memcpy(conn->outBuffer + conn->outMsgStart + 1, &len_n, 4); + + memcpy(conn->outBuffer + conn->outMsgStart + 1 + 4, + output.value, output.length); + conn->outMsgEnd = conn->outMsgStart + msgLen + 1; + + gss_release_buffer(&minor, &output); + return msgLen + 1; +} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 1578d76..ff2e39d 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -446,6 +446,7 @@ struct pg_conn gss_buffer_desc ginbuf; /* GSS input token */ gss_buffer_desc goutbuf; /* GSS output token */ bool gss_disable_enc; /* Does server recognize gss_encrypt? */ + bool gss_auth_done; /* Did we finish the AUTH step? */ #endif #ifdef ENABLE_SSPI @@ -643,6 +644,12 @@ extern bool pgtls_read_pending(PGconn *conn); extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len); /* + * GSSAPI encryption functions defined in fe-secure-gss.c + */ +extern ssize_t pggss_inplace_decrypt(PGconn *conn, int gsslen); +extern int pggss_encrypt(PGconn *conn); + +/* * this is so that we can check if a connection is non-blocking internally * without the overhead of a function call */ -- 2.1.4 From 0991d42173394e68e989d84e6574476d6a98e571 Mon Sep 17 00:00:00 2001 From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> Date: Tue, 9 Jun 2015 14:38:26 -0400 Subject: server: GSSAPI encryption and decryption --- src/backend/libpq/Makefile | 4 ++ src/backend/libpq/auth.c | 2 +- src/backend/libpq/be-secure-gss.c | 101 ++++++++++++++++++++++++++++++++++++++ src/backend/libpq/pqcomm.c | 39 +++++++++++++++ src/backend/tcop/postgres.c | 18 ++++++- src/backend/utils/misc/guc.c | 19 +++++++ src/include/libpq/auth.h | 5 ++ src/include/libpq/libpq-be.h | 9 ++++ src/include/libpq/libpq.h | 4 ++ 9 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 src/backend/libpq/be-secure-gss.c diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 09410c4..359e9d5 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-secure-gss.o +endif + include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 4699efa..913d356 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -728,7 +728,7 @@ static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc; #endif -static void +void pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat) { gss_buffer_desc gmsg; diff --git a/src/backend/libpq/be-secure-gss.c b/src/backend/libpq/be-secure-gss.c new file mode 100644 index 0000000..64a4ed7 --- /dev/null +++ b/src/backend/libpq/be-secure-gss.c @@ -0,0 +1,101 @@ +#include <assert.h> + +#include "postgres.h" + +#include "libpq/libpq.h" +#include "libpq/auth.h" +#include "miscadmin.h" + +/* GUC value */ +bool gss_encrypt; + +size_t +be_gss_encrypt(Port *port, char msgtype, const char **msgptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + uint32 len_n; + int conf; + char *ptr = *((char **)msgptr); + char *newbuf = palloc(len + 5); + + len += 4; + len_n = htonl(len); + + newbuf[0] = msgtype; + memcpy(newbuf + 1, &len_n, 4); + memcpy(newbuf + 5, ptr, len - 4); + + input.length = len + 1; /* include type */ + input.value = newbuf; + output.length = 0; + output.value = NULL; + + 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("unwrapping GSS message failed"), + major, minor); + return -1; + } + assert(conf); + + newbuf = repalloc(newbuf, output.length); + memcpy(newbuf, output.value, output.length); + + len = output.length; + *msgptr = newbuf; + gss_release_buffer(&minor, &output); + + return len; +} + +int +be_gss_inplace_decrypt(StringInfo inBuf) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + int qtype, conf; + size_t msglen = 0; + + input.length = inBuf->len; + input.value = inBuf->data; + output.length = 0; + output.value = NULL; + + major = gss_unwrap(&minor, MyProcPort->gss->ctx, &input, &output, + &conf, NULL); + if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, gettext_noop("wrapping GSS message failed"), + major, minor); + return -1; + } + else if (conf == 0) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("Expected GSSAPI confidentiality but it was not received"))); + return -1; + } + + qtype = ((char *)output.value)[0]; /* first byte is message type */ + inBuf->len = output.length - 5; /* message starts */ + + memcpy((char *)&msglen, ((char *)output.value) + 1, 4); + msglen = ntohl(msglen); + if (msglen - 4 != inBuf->len) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("Length value inside GSSAPI-encrypted packet was malformed"))); + return -1; + } + + memcpy(inBuf->data, ((char *)output.value) + 5, inBuf->len); + inBuf->data[inBuf->len] = '\0'; /* invariant */ + gss_release_buffer(&minor, &output); + + return qtype; +} diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index a4b37ed..5a929a8 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -1485,6 +1485,19 @@ socket_putmessage(char msgtype, const char *s, size_t len) { if (DoingCopyOut || PqCommBusy) return 0; + +#ifdef ENABLE_GSS + /* Do not wrap auth requests. */ + if (MyProcPort->hba->auth_method == uaGSS && gss_encrypt && + msgtype != 'R' && msgtype != 'g') + { + len = be_gss_encrypt(MyProcPort, msgtype, &s, len); + if (len < 0) + goto fail; + msgtype = 'g'; + } +#endif + PqCommBusy = true; if (msgtype) if (internal_putbytes(&msgtype, 1)) @@ -1500,10 +1513,20 @@ socket_putmessage(char msgtype, const char *s, size_t len) if (internal_putbytes(s, len)) goto fail; PqCommBusy = false; +#ifdef ENABLE_GSS + /* if we're GSSAPI encrypting, s was allocated in be_gss_encrypt */ + if (msgtype == 'g') + pfree((char *)s); +#endif return 0; fail: PqCommBusy = false; +#ifdef ENABLE_GSS + /* if we're GSSAPI encrypting, s was allocated in be_gss_encrypt */ + if (msgtype == 'g') + pfree((char *)s); +#endif return EOF; } @@ -1519,6 +1542,22 @@ socket_putmessage_noblock(char msgtype, const char *s, size_t len) int res PG_USED_FOR_ASSERTS_ONLY; int required; +#ifdef ENABLE_GSS + /* + * Because socket_putmessage is also a front-facing function, we need the + * ability to GSSAPI encrypt from either. Since socket_putmessage_noblock + * calls into socket_putmessage, socket_putmessage will handle freeing the + * allocated string. + */ + if (gss_encrypt && msgtype != 'R' && msgtype != 'g') + { + len = be_gss_encrypt(MyProcPort, msgtype, &s, len); + if (len < 0) + return; + msgtype = 'g'; + } +#endif + /* * Ensure we have enough space in the output buffer for the message header * as well as the message itself. diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index ce4bdaf..8510908 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -336,6 +336,7 @@ static int SocketBackend(StringInfo inBuf) { int qtype; + bool msg_got = false; /* * Get message type code from the frontend. @@ -365,6 +366,21 @@ SocketBackend(StringInfo inBuf) return qtype; } +#ifdef ENABLE_GSS + else if (qtype == 'g' && gss_encrypt && + MyProcPort->hba->auth_method == uaGSS) + { + /* GSSAPI wrapping implies protocol >= 3 */ + if (pq_getmessage(inBuf, 0)) + return EOF; + msg_got = true; + + qtype = be_gss_inplace_decrypt(inBuf); + if (qtype < 0) + return EOF; + } +#endif + /* * Validate message type code before trying to read body; if we have lost * sync, better to say "command unknown" than to run out of memory because @@ -490,7 +506,7 @@ SocketBackend(StringInfo inBuf) * after the type code; we can read the message contents independently of * the type. */ - if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) + if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3 && !msg_got) { if (pq_getmessage(inBuf, 0)) return EOF; /* suitable message already logged */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 0356ecb..a978af0 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -186,6 +186,10 @@ static const char *show_log_file_mode(void); static ConfigVariable *ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel); +#ifdef ENABLE_GSS +static void assign_gss_encrypt(bool newval, void *extra); +#endif + /* * Options for enum values defined in this module. @@ -1618,6 +1622,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, NULL, assign_gss_encrypt, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL @@ -10114,4 +10127,10 @@ show_log_file_mode(void) return buf; } +static void +assign_gss_encrypt(bool newval, void *extra) +{ + gss_encrypt = newval; +} + #include "guc-file.c" diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h index 80f26a8..e98f560 100644 --- a/src/include/libpq/auth.h +++ b/src/include/libpq/auth.h @@ -26,4 +26,9 @@ extern void ClientAuthentication(Port *port); typedef void (*ClientAuthentication_hook_type) (Port *, int); extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook; +#ifdef ENABLE_GSS +void pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, + OM_uint32 min_stat); +#endif + #endif /* AUTH_H */ diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 6171ef3..58712fc 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -30,6 +30,8 @@ #endif #ifdef ENABLE_GSS +#include "lib/stringinfo.h" + #if defined(HAVE_GSSAPI_H) #include <gssapi.h> #else @@ -219,6 +221,13 @@ 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 +/* These functions are implemented in be-secure-gss.c */ +extern size_t +be_gss_encrypt(Port *port, char msgtype, const char **msgptr, size_t len); +extern int be_gss_inplace_decrypt(StringInfo inBuf); +#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 c408e5b..e788cc8 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -99,4 +99,8 @@ extern char *SSLCipherSuites; extern char *SSLECDHCurve; extern bool SSLPreferServerCiphers; +#ifdef ENABLE_GSS +extern bool gss_encrypt; +#endif + #endif /* LIBPQ_H */ -- 2.1.4 From e55795e0638ca37447ef200f21f74db80b07ebf4 Mon Sep 17 00:00:00 2001 From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> Date: Fri, 12 Jun 2015 13:27:50 -0400 Subject: Error when receiving plaintext on GSS-encrypted connections --- src/backend/tcop/postgres.c | 12 ++++++++++++ src/interfaces/libpq/fe-protocol3.c | 32 ++++++++++++++++++++++++++++++-- src/interfaces/libpq/libpq-int.h | 1 + 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 8510908..ba8ed4e 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -379,6 +379,18 @@ SocketBackend(StringInfo inBuf) if (qtype < 0) return EOF; } + else if (gss_encrypt && MyProcPort->hba->auth_method == uaGSS && + qtype != 'g' && qtype != 'R' ) + { + /* + * Either something malicious is occuring, or we have lost + * synchronization. + */ + ereport(FATAL, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("invalid frontend message type %d", qtype))); + return EOF; + } #endif /* diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 65acfd1..d5fb461 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -131,7 +131,7 @@ pqParseInput3(PGconn *conn) #ifdef ENABLE_GSS /* We want to be ready in both IDLE and BUSY states for encryption */ - if (id == 'g') + if (id == 'g' && !conn->gss_disable_enc && conn->gctx) { ssize_t encEnd, next; @@ -152,8 +152,33 @@ pqParseInput3(PGconn *conn) memmove(conn->inBuffer + encEnd, conn->inBuffer + next, conn->inEnd - next); conn->inEnd = (conn->inEnd - next) + encEnd; - continue; + + conn->inCursor = conn->inStart; + (void) pqGetc(&id, conn); + (void) pqGetInt(&msgLength, 4, conn); + msgLength -= 4; + if (msgLength != encEnd - conn->inCursor) + { + /* This isn't a sync error because decrypt was successful */ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "server lied about message length: got message length %ld, but expected legnth %d\n"), + encEnd - conn->inCursor, msgLength); + /* build an error result holding the error message */ + pqSaveErrorResult(conn); + /* drop out of GetResult wait loop */ + conn->asyncStatus = PGASYNC_READY; + + pqDropConnection(conn); + /* No more connection to backend */ + conn->status = CONNECTION_BAD; + } + conn->gss_decrypted_cur = true; } + else if (!conn->gss_disable_enc && conn->gss_auth_done && + !conn->gss_decrypted_cur && id != 'E') + /* This could be a sync error, so let's handle it as such. */ + handleSyncLoss(conn, id, msgLength); #endif /* @@ -425,6 +450,9 @@ pqParseInput3(PGconn *conn) { /* Normal case: parsing agrees with specified length */ conn->inStart = conn->inCursor; +#ifdef ENABLE_GSS + conn->gss_decrypted_cur = false; +#endif } else { diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index ff2e39d..7a3ebcd 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -447,6 +447,7 @@ struct pg_conn gss_buffer_desc goutbuf; /* GSS output token */ bool gss_disable_enc; /* Does server recognize gss_encrypt? */ bool gss_auth_done; /* Did we finish the AUTH step? */ + bool gss_decrypted_cur; /* Is first message in buffer decrypted? */ #endif #ifdef ENABLE_SSPI -- 2.1.4 From dcba977f9e37e80fafc7deb89b8856738f20928d Mon Sep 17 00:00:00 2001 From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> Date: Mon, 15 Jun 2015 16:52:10 -0400 Subject: server: hba option for requiring GSSAPI encryption Also includes logic for failing clients that do not request encryption in the startup packet when encryption is required. --- src/backend/libpq/hba.c | 9 +++++++++ src/backend/utils/init/postinit.c | 7 ++++++- src/backend/utils/misc/guc.c | 12 +++++++++++- src/include/libpq/hba.h | 1 + 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 23c8b5d..90fe57f 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/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 0f04f28..cc5c9af 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" @@ -1085,6 +1085,11 @@ 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 a978af0..3a8ba61 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -188,6 +188,7 @@ static ConfigVariable *ProcessConfigFileInternal(GucContext context, #ifdef ENABLE_GSS static void assign_gss_encrypt(bool newval, void *extra); +static bool check_gss_encrypt(bool *newval, void **extra, GucSource source); #endif @@ -1628,7 +1629,7 @@ static struct config_bool ConfigureNamesBool[] = NULL, GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, - &gss_encrypt, false, NULL, assign_gss_encrypt, NULL + &gss_encrypt, false, check_gss_encrypt, assign_gss_encrypt, NULL }, /* End-of-list marker */ @@ -10133,4 +10134,13 @@ 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; -- 2.1.4 From b32190b69479a46291088b9ef15208d47975bcba Mon Sep 17 00:00:00 2001 From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> Date: Mon, 15 Jun 2015 19:54:29 -0400 Subject: client: gss_enc_require parameter to force GSS encryption --- src/interfaces/libpq/fe-connect.c | 27 ++++++++++++++++++++++----- src/interfaces/libpq/libpq-int.h | 1 + 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 8fb0a90..115a52c 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -297,6 +297,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)}, @@ -2559,14 +2565,16 @@ keep_going: /* We will come back to here until there is appendPQExpBufferStr(&conn->errorMessage, libpq_gettext("unexpected message from server during startup\n")); #ifdef ENABLE_GSS - else if (!conn->gss_disable_enc) + else if (!conn->gss_disable_enc && + *conn->gss_enc_require != '1') { /* * We tried to request GSS encryption, but the server - * doesn't support it. Hang up and try again. A - * connection that doesn't support appname will also - * not support GSSAPI encryption, so this check goes - * before that check. See comment below. + * doesn't support it. Retries are permitted here, so + * hang up and try again. A connection that doesn't + * support appname will also not support GSSAPI + * encryption, so this check goes before that check. + * See comment below. */ const char *sqlstate; @@ -2614,6 +2622,15 @@ 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 GSS encryption\n")); +#endif /* * if the resultStatus is FATAL, then conn->errorMessage diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7a3ebcd..3140c7f 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -448,6 +448,7 @@ struct pg_conn bool gss_disable_enc; /* Does server recognize gss_encrypt? */ bool gss_auth_done; /* Did we finish the AUTH step? */ bool gss_decrypted_cur; /* Is first message in buffer decrypted? */ + char *gss_enc_require; /* Can we downgrade to plaintext? */ #endif #ifdef ENABLE_SSPI -- 2.1.4 From d5b374d0a466c4bf642d806168d973d4aad638e8 Mon Sep 17 00:00:00 2001 From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> Date: Mon, 29 Jun 2015 15:29:36 -0400 Subject: Document GSSAPI encryption --- doc/src/sgml/client-auth.sgml | 19 ++++++++++++++++--- doc/src/sgml/libpq.sgml | 12 ++++++++++++ doc/src/sgml/protocol.sgml | 40 ++++++++++++++++++++++++++++++++++++++++ doc/src/sgml/runtime.sgml | 20 +++++++++++++++++++- 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 5f72beb..3c49612 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -913,9 +913,10 @@ omicron bryanh guest1 <productname>GSSAPI</productname> with <productname>Kerberos</productname> authentication according to RFC 1964. <productname>GSSAPI</productname> 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. + that support it. The authentication itself is secure, and GSSAPI can be + used for connection encryption as well (see the + <literal>require_encrypt</literal> parameter below); <acronym>SSL</acronym> + can also be used for connection security. </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 de6b3ad..db2340c 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/protocol.sgml b/doc/src/sgml/protocol.sgml index 42e9497..8355e54 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1295,6 +1295,46 @@ of authentication checking. </para> </sect2> + + <sect2> + <title><acronym>GSSAPI</acronym> Session Encryption</title> + + <para> + If <productname>PostgreSQL</> was built with + <acronym>GSSAPI</acronym> and <acronym>GSSAPI</acronym> support, traffic + can also be encrypted using <acronym>GSSAPI</acronym>. To force encryption + using <acronym>GSSAPI</acronym>, set require_encrypt in + <filename>pg_hba.conf</filename>. + </para> + + <para> + In order to probe for <acronym>GSSAPI</acronym> support, the client will + include in their StartupMessage the parameter gss_encrypt. If the server + does not support <acronym>GSSAPI</acronym> or <acronym>GSSAPI</acronym> + encryption, the server will error the connection; otherwise, it continues + as normal. The client may retry the connection + without <acronym>GSSAPI</acronym> encryption support depending on its + settings. If the client does not probe support, depending on settings + in <filename>pg_hba.conf</filename>, the server may drop + <acronym>GSSAPI</acronym>-authenticated connections without encryption. + </para> + + <para> + If the client has probed <acronym>GSSAPI</acronym> encryption support and + the connection is <acronym>GSSAPI</acronym>-authenticated, then after the + server sends AuthenticationOk, all traffic between the client and server + will be <acronym>GSSAPI</acronym>-encrypted. Because + <acronym>GSSAPI</acronym> does not provide framing, + <acronym>GSSAPI</acronym>-encrypted messages are modeled after protocol-3 + messages: the first byte is the caracter g, then four bytes of length, and + then an encrypted message. + </para> + + <para> + It is valid to use <acronym>GSSAPI</acronym> encryption over + <acronym>SSL</acronym>-encrypted connections. + </para> + </sect2> </sect1> <sect1 id="protocol-replication"> diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 547567e..0b1b009 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1833,7 +1833,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 @@ -1843,6 +1843,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 GSS + connections. Then, using Kerberos, the client and server will mutually + authenticate, and the connection will be encrypted once the authentication + step is complete. + </para> </sect1> <sect1 id="encryption-options"> @@ -1958,6 +1967,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 GSS authenticaion + method has a parameter to require encryption; otherwise, connections + will be encrypted if available and requiested by the client. On the + client side, there is also a parameter to require GSSAPI encryption + support from the server. + </para> </listitem> </varlistentry> -- 2.1.4
Attachment
Robbie, * Robbie Harwood (rharwood@redhat.com) wrote: > As previously discussed on this list, I have coded up GSSAPI encryption > support. If it is easier for anyone, this code is also available for > viewing on my github: > https://github.com/postgres/postgres/compare/master...frozencemetery:feature/gssencrypt > > Fallback support is present in both directions for talking to old client > and old servers; GSSAPI encryption is by default auto-upgraded to where > available (for compatibility), but both client and server contain > settings for requiring it. > > There are 8 commits in this series; I have tried to err on the side of > creating too much separation rather than too little. A patch for each > is attached. This is v1 of the series. Excellent, thanks! I've got other things to tend to at the moment, but this is definitely something I'm interested in and will work on (likely in August). If we could get a reviewer or two to take a look and take the patch out for a test-drive, at least, that would be a huge help. Thanks again! Stephen
On Fri, Jul 3, 2015 at 3:22 AM, Robbie Harwood <rharwood@redhat.com> wrote:
Hello -hackers,
As previously discussed on this list, I have coded up GSSAPI encryption
support. If it is easier for anyone, this code is also available for
viewing on my github:
https://github.com/postgres/postgres/compare/master...frozencemetery:feature/gssencrypt
Fallback support is present in both directions for talking to old client
and old servers; GSSAPI encryption is by default auto-upgraded to where
available (for compatibility), but both client and server contain
settings for requiring it.
There are 8 commits in this series; I have tried to err on the side of
creating too much separation rather than too little. A patch for each
is attached. This is v1 of the series.
I just had a quick look at this patch, and here are some comments:
+ <para>
+ If the client has probed <acronym>GSSAPI</acronym> encryption support and
+ the connection is <acronym>GSSAPI</acronym>-authenticated, then after the
+ server sends AuthenticationOk, all traffic between the client and server
+ will be <acronym>GSSAPI</acronym>-encrypted. Because
+ <acronym>GSSAPI</acronym> does not provide framing,
+ <acronym>GSSAPI</acronym>-encrypted messages are modeled after protocol-3
+ messages: the first byte is the caracter g, then four bytes of length, and
+ then an encrypted message.
+ </para>
+ <para>
+ If the client has probed <acronym>GSSAPI</acronym> encryption support and
+ the connection is <acronym>GSSAPI</acronym>-authenticated, then after the
+ server sends AuthenticationOk, all traffic between the client and server
+ will be <acronym>GSSAPI</acronym>-encrypted. Because
+ <acronym>GSSAPI</acronym> does not provide framing,
+ <acronym>GSSAPI</acronym>-encrypted messages are modeled after protocol-3
+ messages: the first byte is the caracter g, then four bytes of length, and
+ then an encrypted message.
+ </para>
Message formats should be described in protocol.sgml in the section for message formats.
+ network. In the <filename>pg_hba.conf</> file, the GSS authenticaion
+ method has a parameter to require encryption; otherwise, connections
+ will be encrypted if available and requiested by the client. On the
s/authenticaion/authentication
s/requiested/requested
+ 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.
+ 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.
s/compatability/compatibility
Going through the docs, the overall approach taken by the patch looks neat, and the default values as designed for both the client and the server are good things to do. Now actually looking at the code I am suspecting that some code portions could be largely simplified in the authentication protocol code, though I don't have the time yet to look at that in details.
Going through the docs, the overall approach taken by the patch looks neat, and the default values as designed for both the client and the server are good things to do. Now actually looking at the code I am suspecting that some code portions could be largely simplified in the authentication protocol code, though I don't have the time yet to look at that in details.
Also, when trying to connect with GSSAPI, I found the following problem:
psql: lost synchronization with server: got message type "S", length 22
psql: lost synchronization with server: got message type "S", length 22
This happens whatever the value of require_encrypt on server-side is, either 0 or 1.
Regards,
--
Michael
Michael Paquier <michael.paquier@gmail.com> writes: > On Fri, Jul 3, 2015 at 3:22 AM, Robbie Harwood <rharwood@redhat.com> wrote: > >> There are 8 commits in this series; I have tried to err on the side of >> creating too much separation rather than too little. A patch for each >> is attached. This is v1 of the series. > > I just had a quick look at this patch, and here are some comments: Hi! Thanks for taking it for a spin. > + <para> > + If the client has probed <acronym>GSSAPI</acronym> encryption support and > + the connection is <acronym>GSSAPI</acronym>-authenticated, then after the > + server sends AuthenticationOk, all traffic between the client and server > + will be <acronym>GSSAPI</acronym>-encrypted. Because > + <acronym>GSSAPI</acronym> does not provide framing, > + <acronym>GSSAPI</acronym>-encrypted messages are modeled after protocol-3 > + messages: the first byte is the caracter g, then four bytes of length, and > + then an encrypted message. > + </para> > > Message formats should be described in protocol.sgml in the section for > message formats. ACK. In next version of patch. > + network. In the <filename>pg_hba.conf</> file, the GSS authenticaion > + method has a parameter to require encryption; otherwise, connections > + will be encrypted if available and requiested by the client. On the > s/authenticaion/authentication > s/requiested/requested > > + 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. > s/compatability/compatibility Thanks for catching these. They'll be included in a new version of the series, which I'll post once you and I have resolved the issue at the bottom. > Going through the docs, the overall approach taken by the patch looks neat, > and the default values as designed for both the client and the server are > good things to do. Now actually looking at the code I am suspecting that > some code portions could be largely simplified in the authentication > protocol code, though I don't have the time yet to look at that in details. If there are ways to make it simpler without sacrificing clarity, I welcome them. Fresh eyes could definitely help with that! > Also, when trying to connect with GSSAPI, I found the following problem: > psql: lost synchronization with server: got message type "S", length 22 > This happens whatever the value of require_encrypt on server-side is, > either 0 or 1. Well that's not good! Since I'm not seeing this failure (even after rebuilding my setup with patches applied to master), can you give me more information here? Since it's independent of require_encrypt, can you verify it doesn't happen on master without my patches? What messages went over the wire to/from the server before this occurred (and what was it trying to send at the time)? Did you have valid credentials?
On Sat, Aug 22, 2015 at 4:06 AM, Robbie Harwood wrote: > > Michael Paquier <michael.paquier@gmail.com> writes: > > Going through the docs, the overall approach taken by the patch looks neat, > > and the default values as designed for both the client and the server are > > good things to do. Now actually looking at the code I am suspecting that > > some code portions could be largely simplified in the authentication > > protocol code, though I don't have the time yet to look at that in details. > > If there are ways to make it simpler without sacrificing clarity, I > welcome them. Fresh eyes could definitely help with that! I'll look at that more at next week or the week after. > > Also, when trying to connect with GSSAPI, I found the following problem: > > psql: lost synchronization with server: got message type "S", length 22 > > This happens whatever the value of require_encrypt on server-side is, > > either 0 or 1. > > Well that's not good! Since I'm not seeing this failure (even after > rebuilding my setup with patches applied to master), can you give me > more information here? Since it's independent of require_encrypt, can > you verify it doesn't happen on master without my patches? Well, I imagine that I have done nothing complicated... I have simply set up a Kerberos KDC on a dev box, created necessary credentials on this box in a keytab file that I have used afterwards to initialize a Kerberos context with kinit for the psql client. On master things worked fine, I was able to connect via gssapi. But with your patch the communication protocol visibly lost track of the messages. I took a memo about that, it's a bit rough, does not use pg_ident, but if that can help: http://michael.otacoo.com/manuals/postgresql/kerberos/ > What messages went over the wire to/from the server before this occurred (and > what was it trying to send at the time)? I haven't checked what were the messages sent over the network yet. > Did you have valid credentials? Yep. I just tried on master before switching to a build with your patch that failed. After moving back to master things worked again. -- Michael
Michael Paquier <michael.paquier@gmail.com> writes: > On Fri, Jul 3, 2015 at 3:22 AM, Robbie Harwood <rharwood@redhat.com> wrote: > >> Hello -hackers, >> >> As previously discussed on this list, I have coded up GSSAPI encryption >> support. If it is easier for anyone, this code is also available for >> viewing on my github: >> >> https://github.com/postgres/postgres/compare/master...frozencemetery:feature/gssencrypt >> >> Fallback support is present in both directions for talking to old client >> and old servers; GSSAPI encryption is by default auto-upgraded to where >> available (for compatibility), but both client and server contain >> settings for requiring it. >> >> There are 8 commits in this series; I have tried to err on the side of >> creating too much separation rather than too little. A patch for each >> is attached. This is v1 of the series. > > I just had a quick look at this patch, and here are some comments: > + <para> > + If the client has probed <acronym>GSSAPI</acronym> encryption support > and > + the connection is <acronym>GSSAPI</acronym>-authenticated, then after > the > + server sends AuthenticationOk, all traffic between the client and > server > + will be <acronym>GSSAPI</acronym>-encrypted. Because > + <acronym>GSSAPI</acronym> does not provide framing, > + <acronym>GSSAPI</acronym>-encrypted messages are modeled after > protocol-3 > + messages: the first byte is the caracter g, then four bytes of length, > and > + then an encrypted message. > + </para> > Message formats should be described in protocol.sgml in the section for > message formats. > > + network. In the <filename>pg_hba.conf</> file, the GSS authenticaion > + method has a parameter to require encryption; otherwise, connections > + will be encrypted if available and requiested by the client. On the > s/authenticaion/authentication > s/requiested/requested > > + 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. > s/compatability/compatibility As promised, here's a V2 to address your issues with comments. I haven't heard back on the issues you found in testing, so no other changes are present. This means that only the last patch has changed. For convenience, I will therefore only provide this new patch. I have also updated the version available from my github. Thanks! From 2e9017a572a3097fecf2f7e53bf5f9aabf6ae36d Mon Sep 17 00:00:00 2001 From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> Date: Mon, 29 Jun 2015 15:29:36 -0400 Subject: [PATCH] Document GSSAPI encryption --- doc/src/sgml/client-auth.sgml | 19 ++++++++-- doc/src/sgml/libpq.sgml | 12 +++++++ doc/src/sgml/protocol.sgml | 82 ++++++++++++++++++++++++++++++++++++++++++- doc/src/sgml/runtime.sgml | 20 ++++++++++- 4 files changed, 128 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index ba04bdf..0863468 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -913,9 +913,10 @@ omicron bryanh guest1 <productname>GSSAPI</productname> with <productname>Kerberos</productname> authentication according to RFC 1964. <productname>GSSAPI</productname> 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. + that support it. The authentication itself is secure, and GSSAPI can be + used for connection encryption as well (see the + <literal>require_encrypt</literal> parameter below); <acronym>SSL</acronym> + can also be used for connection security. </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 + compatibility 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 7940ef2..b80d29d 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/protocol.sgml b/doc/src/sgml/protocol.sgml index 42e9497..15e0014 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1295,6 +1295,42 @@ of authentication checking. </para> </sect2> + + <sect2> + <title><acronym>GSSAPI</acronym> Session Encryption</title> + + <para> + If <productname>PostgreSQL</> was built with + <acronym>GSSAPI</acronym> and <acronym>GSSAPI</acronym> support, traffic + can also be encrypted using <acronym>GSSAPI</acronym>. To force encryption + using <acronym>GSSAPI</acronym>, set require_encrypt in + <filename>pg_hba.conf</filename>. + </para> + + <para> + In order to probe for <acronym>GSSAPI</acronym> support, the client will + include in their StartupMessage the parameter gss_encrypt. If the server + does not support <acronym>GSSAPI</acronym> or <acronym>GSSAPI</acronym> + encryption, the server will error the connection; otherwise, it continues + as normal. The client may retry the connection + without <acronym>GSSAPI</acronym> encryption support depending on its + settings. If the client does not probe support, depending on settings + in <filename>pg_hba.conf</filename>, the server may drop + <acronym>GSSAPI</acronym>-authenticated connections without encryption. + </para> + + <para> + If the client has probed <acronym>GSSAPI</acronym> encryption support and + the connection is <acronym>GSSAPI</acronym>-authenticated, then after the + server sends AuthenticationOk, all traffic between the client and server + will be <acronym>GSSAPI</acronym>-encrypted. See message formats above. + </para> + + <para> + It is valid to use <acronym>GSSAPI</acronym> encryption over + <acronym>SSL</acronym>-encrypted connections. + </para> + </sect2> </sect1> <sect1 id="protocol-replication"> @@ -2191,7 +2227,9 @@ Notice that although each message includes a byte count at the beginning, the message format is defined so that the message end can be found without reference to the byte count. This aids validity checking. (The CopyData message is an exception, because it forms part of a data stream; the contents -of any individual CopyData message cannot be interpretable on their own.) +of any individual CopyData message cannot be interpretable on their own. The +GSSAPI message type is another exception because GSSAPI does not require +payloads to include length information.) </para> <variablelist> @@ -3921,6 +3959,48 @@ FunctionCallResponse (B) </listitem> </varlistentry> +<varlistentry> +<term> +GSSAPI (F & B) +</term> +<listitem> +<para> +<variablelist> +<varlistentry> +<term> + Byte1('g') +</term> +<listitem> +<para> + Identifies the message as GSSAPI-encrypted data. +</para> +</listitem> +</varlistentry> +<varlistentry> +<term> + Int32 +</term> +<listitem> +<para> + Length of message contents in bytes, including self. +</para> +</listitem> +</varlistentry> +<varlistentry> +<term> + Byte<replaceable>n</replaceable> +</term> +<listitem> +<para> + GSSAPI-encrypted data. Once decrypted, will be in a valid + message format. +</para> +</listitem> +</varlistentry> +</variablelist> +</para> +</listitem> +</varlistentry> <varlistentry> <term> diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 6d5b108..0d65fd6 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1868,7 +1868,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 @@ -1878,6 +1878,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 GSS + connections. Then, using Kerberos, the client and server will mutually + authenticate, and the connection will be encrypted once the authentication + step is complete. + </para> </sect1> <sect1 id="encryption-options"> @@ -1993,6 +2002,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> -- 2.5.1
Attachment
On Wed, Sep 9, 2015 at 4:12 AM, Robbie Harwood wrote: > Michael Paquier writes: > As promised, here's a V2 to address your issues with comments. I > haven't heard back on the issues you found in testing, so no other > changes are present. Well, the issue is still here: login through gssapi fails with your patch, not with HEAD. This patch is next on my review list by the way so I'll see what I can do about it soon even if I am in the US for Postgres Open next week. Still, how did you test it? I am just creating by myself a KDC, setting up a valid credential with kinit, and after setting up Postgres for this purpose the protocol communication just fails. > This means that only the last patch has changed. For convenience, I > will therefore only provide this new patch. I have also updated the > version available from my github. Thanks, this looks better. I have updated my local branch by replacing the last patch of the previous series by this one, so I'll base any potential hacking on this one. Regards, -- Michael
Michael Paquier <michael.paquier@gmail.com> writes: > On Wed, Sep 9, 2015 at 4:12 AM, Robbie Harwood wrote: >> Michael Paquier writes: >> As promised, here's a V2 to address your issues with comments. I >> haven't heard back on the issues you found in testing, so no other >> changes are present. > > Well, the issue is still here: login through gssapi fails with your > patch, not with HEAD. This patch is next on my review list by the way > so I'll see what I can do about it soon even if I am in the US for > Postgres Open next week. Still, how did you test it? I am just > creating by myself a KDC, setting up a valid credential with kinit, > and after setting up Postgres for this purpose the protocol > communication just fails. My KDC is setup through freeIPA; I create a service for postgres, acquire a keytab, set it in the config file, and fire up the server. It should go without saying that this is working for me, which is why I asked you for more information so I could try to debug. I wrote a post on this back in June when this was still in development: http://mivehind.net/page/view-page-slug/16/postgres-kerberos
On Thu, Sep 10, 2015 at 1:44 AM, Robbie Harwood <rharwood@redhat.com> wrote: > Michael Paquier <michael.paquier@gmail.com> writes: > >> On Wed, Sep 9, 2015 at 4:12 AM, Robbie Harwood wrote: >>> Michael Paquier writes: >>> As promised, here's a V2 to address your issues with comments. I >>> haven't heard back on the issues you found in testing, so no other >>> changes are present. >> >> Well, the issue is still here: login through gssapi fails with your >> patch, not with HEAD. This patch is next on my review list by the way >> so I'll see what I can do about it soon even if I am in the US for >> Postgres Open next week. Still, how did you test it? I am just >> creating by myself a KDC, setting up a valid credential with kinit, >> and after setting up Postgres for this purpose the protocol >> communication just fails. > > My KDC is setup through freeIPA; I create a service for postgres, > acquire a keytab, set it in the config file, and fire up the server. It > should go without saying that this is working for me, which is why I > asked you for more information so I could try to debug. I wrote a post > on this back in June when this was still in development: > http://mivehind.net/page/view-page-slug/16/postgres-kerberos Hm. OK. I'll give it a try with freeipa and your patch with Fedora for example. Could you as well try the configuration I have used? In any case, it seems to me that we have a real problem with your patch: the gss authentication protocol is broken with your patch and *not* HEAD when using a custom kdc like the one I have set up manually on one of my VMs. -- Michael
On Thu, Sep 10, 2015 at 4:27 PM, Michael Paquier <michael.paquier@gmail.com> wrote: > On Thu, Sep 10, 2015 at 1:44 AM, Robbie Harwood <rharwood@redhat.com> wrote: >> Michael Paquier <michael.paquier@gmail.com> writes: >> >>> On Wed, Sep 9, 2015 at 4:12 AM, Robbie Harwood wrote: >>>> Michael Paquier writes: >>>> As promised, here's a V2 to address your issues with comments. I >>>> haven't heard back on the issues you found in testing, so no other >>>> changes are present. >>> >>> Well, the issue is still here: login through gssapi fails with your >>> patch, not with HEAD. This patch is next on my review list by the way >>> so I'll see what I can do about it soon even if I am in the US for >>> Postgres Open next week. Still, how did you test it? I am just >>> creating by myself a KDC, setting up a valid credential with kinit, >>> and after setting up Postgres for this purpose the protocol >>> communication just fails. >> >> My KDC is setup through freeIPA; I create a service for postgres, >> acquire a keytab, set it in the config file, and fire up the server. It >> should go without saying that this is working for me, which is why I >> asked you for more information so I could try to debug. I wrote a post >> on this back in June when this was still in development: >> http://mivehind.net/page/view-page-slug/16/postgres-kerberos > Hm. OK. I'll give it a try with freeipa and your patch with Fedora for > example. Could you as well try the configuration I have used? In any > case, it seems to me that we have a real problem with your patch: the > gss authentication protocol is broken with your patch and *not* HEAD > when using a custom kdc like the one I have set up manually on one of > my VMs. Looking more at this stuff. Your post assumes that you have an IPA server available (I am not really familiar with this software stack) already configured at hand so as you do not need to worry about any low-level configuration and a KDC is provided as well, among other things like ntpd or an apache instance. Well, the thing is that we just need is a KDC for this patch to have an environment suitable for testing, and some magic commands with kadmin.local, kinit, etc, and not the whole set of features that an IPA server provides (when kicking ipa-server-install one needs to provide a realm name, a KDC admin password, so that's basically just a wrapper setting up krb5.conf, which is handy when you want to have the full set in your hands actually, though just to test this patch it does not seem worth it). And I imagine that you do have an IPA server already set facilitating your work. Still, I gave it a try on a Fedora host, giving up after facing several failures when trying to install the server. because of several features. Note that by duckduckging around I have bumped into some more documentation to set up KDC with Postgres: http://gpdb.docs.pivotal.io/4320/admin_guide/kerberos.html This may be useful when testing this patch as well. Regards, -- Michael
Michael Paquier <michael.paquier@gmail.com> writes: > On Thu, Sep 10, 2015 at 4:27 PM, Michael Paquier <michael.paquier@gmail.com> wrote: >> On Thu, Sep 10, 2015 at 1:44 AM, Robbie Harwood <rharwood@redhat.com> wrote: >>> Michael Paquier <michael.paquier@gmail.com> writes: >>>> On Wed, Sep 9, 2015 at 4:12 AM, Robbie Harwood wrote: >>>>> Michael Paquier writes: >>>>> As promised, here's a V2 to address your issues with comments. I >>>>> haven't heard back on the issues you found in testing, so no other >>>>> changes are present. >>>> >>>> Well, the issue is still here: login through gssapi fails with your >>>> patch, not with HEAD. This patch is next on my review list by the >>>> way so I'll see what I can do about it soon even if I am in the US >>>> for Postgres Open next week. Still, how did you test it? I am just >>>> creating by myself a KDC, setting up a valid credential with kinit, >>>> and after setting up Postgres for this purpose the protocol >>>> communication just fails. >>> >>> My KDC is setup through freeIPA; I create a service for postgres, >>> acquire a keytab, set it in the config file, and fire up the server. >>> It should go without saying that this is working for me, which is >>> why I asked you for more information so I could try to debug. I >>> wrote a post on this back in June when this was still in >>> development: >>> http://mivehind.net/page/view-page-slug/16/postgres-kerberos >> >> Hm. OK. I'll give it a try with freeipa and your patch with Fedora >> for example. Could you as well try the configuration I have used? In >> any case, it seems to me that we have a real problem with your patch: >> the gss authentication protocol is broken with your patch and *not* >> HEAD when using a custom kdc like the one I have set up manually on >> one of my VMs. > > Looking more at this stuff. Your post assumes that you have an IPA > server available (I am not really familiar with this software stack) > already configured at hand so as you do not need to worry about any > low-level configuration and a KDC is provided as well, among other > things like ntpd or an apache instance. Well, the thing is that we > just need is a KDC for this patch to have an environment suitable for > testing, and some magic commands with kadmin.local, kinit, etc, and > not the whole set of features that an IPA server provides (when > kicking ipa-server-install one needs to provide a realm name, a KDC > admin password, so that's basically just a wrapper setting up > krb5.conf, which is handy when you want to have the full set in your > hands actually, though just to test this patch it does not seem worth > it). And I imagine that you do have an IPA server already set > facilitating your work. > > Still, I gave it a try on a Fedora host, giving up after facing > several failures when trying to install the server. because of several > features. I'm sorry to hear that FreeIPA didn't work for you. I'd be remiss if I didn't suggest you file bugs against the project for things that are broken, though that's somewhat orthogonal to the patchset at hand. I gave your setup a try; I spun up a fc22 machine (krb5v1.13.2), and installed the KDC as per your instructions. I only did two things differently: I'm using the default unit file for krb5kdc, and I'm using the postgres base dir /var/lib/pgsql/data (this is a Fedora default and I'm sure it doesn't matter for purposes of this). I have no issues, no sync loss; nothing is amiss as far as I can see. If there is actually a problem here, I need more information from you. At the very least, as previously mentioned, I need to know what messages went over the wire to/from the server before it occurred, and what command (if it it made it to command processing) it was in the midst of sending. Thanks, --Robbie
Robbie Harwood <rharwood@redhat.com> writes: >>>> Michael Paquier <michael.paquier@gmail.com> writes: >>>> >>>>> Well, the issue is still here: login through gssapi fails with >>>>> your patch, not with HEAD. This patch is next on my review list by >>>>> the way so I'll see what I can do about it soon even if I am in >>>>> the US for Postgres Open next week. Still, how did you test it? I >>>>> am just creating by myself a KDC, setting up a valid credential >>>>> with kinit, and after setting up Postgres for this purpose the >>>>> protocol communication just fails. > > I have no issues, no sync loss; nothing is amiss as far as I can see. > If there is actually a problem here, I need more information from you. > At the very least, as previously mentioned, I need to know what > messages went over the wire to/from the server before it occurred, and > what command (if it it made it to command processing) it was in the > midst of sending. Any follow-up on this? I'd really like my code to be bug-free.
Hi, I quickly read through the patch, trying to understand what exactly is happening here. To me the way the patch is split doesn't make much sense - I don't mind incremental patches, but right now the steps don't individually make sense. On 2015-07-02 14:22:13 -0400, Robbie Harwood wrote: > +#include <assert.h> postgres.h should be the first header included. > +size_t > +be_gss_encrypt(Port *port, char msgtype, const char **msgptr, size_t len) > +{ > + OM_uint32 major, minor; > + gss_buffer_desc input, output; > + uint32 len_n; > + int conf; > + char *ptr = *((char **)msgptr); trivial nitpick, missing spaces... > +int > +be_gss_inplace_decrypt(StringInfo inBuf) > +{ > + OM_uint32 major, minor; > + gss_buffer_desc input, output; > + int qtype, conf; > + size_t msglen = 0; > + > + input.length = inBuf->len; > + input.value = inBuf->data; > + output.length = 0; > + output.value = NULL; > + > + major = gss_unwrap(&minor, MyProcPort->gss->ctx, &input, &output, > + &conf, NULL); > + if (GSS_ERROR(major)) > + { > + pg_GSS_error(ERROR, gettext_noop("wrapping GSS message failed"), > + major, minor); > + return -1; > + } > + else if (conf == 0) > + { > + ereport(COMMERROR, > + (errcode(ERRCODE_PROTOCOL_VIOLATION), > + errmsg("Expected GSSAPI confidentiality but it was not received"))); > + return -1; > + } Hm. Aren't we leaking the gss buffer here? > + qtype = ((char *)output.value)[0]; /* first byte is message type */ > + inBuf->len = output.length - 5; /* message starts */ > + > + memcpy((char *)&msglen, ((char *)output.value) + 1, 4); > + msglen = ntohl(msglen); > + if (msglen - 4 != inBuf->len) > + { > + ereport(COMMERROR, > + (errcode(ERRCODE_PROTOCOL_VIOLATION), > + errmsg("Length value inside GSSAPI-encrypted packet was malformed"))); > + return -1; > + } and here? Arguably it doesn't matter that much, since we'll usually disconnect around here, but ... > + memcpy(inBuf->data, ((char *)output.value) + 5, inBuf->len); > + inBuf->data[inBuf->len] = '\0'; /* invariant */ > + gss_release_buffer(&minor, &output); > + > + return qtype; > +} > diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c > index a4b37ed..5a929a8 100644 > --- a/src/backend/libpq/pqcomm.c > +++ b/src/backend/libpq/pqcomm.c > @@ -1485,6 +1485,19 @@ socket_putmessage(char msgtype, const char *s, size_t len) > { > if (DoingCopyOut || PqCommBusy) > return 0; > + > +#ifdef ENABLE_GSS > + /* Do not wrap auth requests. */ > + if (MyProcPort->hba->auth_method == uaGSS && gss_encrypt && > + msgtype != 'R' && msgtype != 'g') > + { > + len = be_gss_encrypt(MyProcPort, msgtype, &s, len); > + if (len < 0) > + goto fail; > + msgtype = 'g'; > + } > +#endif Putting encryption specific code here feels rather wrong to me. > diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h > index 6171ef3..58712fc 100644 > --- a/src/include/libpq/libpq-be.h > +++ b/src/include/libpq/libpq-be.h > @@ -30,6 +30,8 @@ > #endif > > #ifdef ENABLE_GSS > +#include "lib/stringinfo.h" > + Conditionally including headers providing generic infrastructure strikes me as a recipe for build failures in different configurations. > /* 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 c408e5b..e788cc8 100644 > --- a/src/include/libpq/libpq.h > +++ b/src/include/libpq/libpq.h > @@ -99,4 +99,8 @@ extern char *SSLCipherSuites; > extern char *SSLECDHCurve; > extern bool SSLPreferServerCiphers; > > +#ifdef ENABLE_GSS > +extern bool gss_encrypt; > +#endif > --- a/src/backend/utils/misc/guc.c > +++ b/src/backend/utils/misc/guc.c > > +#ifdef ENABLE_GSS > +static void assign_gss_encrypt(bool newval, void *extra); > +#endif > + > > /* > * Options for enum values defined in this module. > @@ -1618,6 +1622,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, NULL, assign_gss_encrypt, NULL > + }, > + > /* End-of-list marker */ > { > {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL > @@ -10114,4 +10127,10 @@ show_log_file_mode(void) > return buf; > } The guc should always be present, not just when gss is built in. It should error out when not supported. As is you're going to get linker errors around gss_encrypt, assign_gss_encrypt. > From e55795e0638ca37447ef200f21f74db80b07ebf4 Mon Sep 17 00:00:00 2001 > From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> > Date: Fri, 12 Jun 2015 13:27:50 -0400 > Subject: Error when receiving plaintext on GSS-encrypted connections I don't see why this makes sense as a separate patch. > Subject: server: hba option for requiring GSSAPI encryption > > Also includes logic for failing clients that do not request encryption > in the startup packet when encryption is required. > --- > src/backend/libpq/hba.c | 9 +++++++++ > src/backend/utils/init/postinit.c | 7 ++++++- > src/backend/utils/misc/guc.c | 12 +++++++++++- > src/include/libpq/hba.h | 1 + > 4 files changed, 27 insertions(+), 2 deletions(-) > > diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c > index 23c8b5d..90fe57f 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; > + } So this is a new, undocumented, option that makes a connection require encryption? But despite the generic name, it's gss specific? > @@ -1628,7 +1629,7 @@ static struct config_bool ConfigureNamesBool[] = > NULL, > GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE > }, > - &gss_encrypt, false, NULL, assign_gss_encrypt, NULL > + &gss_encrypt, false, check_gss_encrypt, assign_gss_encrypt, NULL > }, > > /* End-of-list marker */ > @@ -10133,4 +10134,13 @@ 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; > +} Doing such checks in a guc assign hook seems like a horrible idea. Yes, there's some precedent, but still. Greetings, Andres Freund
On Sun, Oct 4, 2015 at 1:18 AM, Andres Freund <andres@anarazel.de> wrote: > Hi, > > I quickly read through the patch, trying to understand what exactly is > happening here. To me the way the patch is split doesn't make much sense > - I don't mind incremental patches, but right now the steps don't > individually make sense. I agree with Andres. While I looked a bit at this patch, I just had a look at them a whole block and not individually. > On 2015-07-02 14:22:13 -0400, Robbie Harwood wrote: > [Andres' comments] Here are some comments on top of what Andres has mentioned. --- 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) I think that using a new configure variable like that with a dedicated file fe-secure-gss.c and be-secure-gss.c has little sense done this way, and that it would be more adapted to get everything grouped in fe-auth.c for the frontend and auth.c for the backend, or move all the GSSAPI-related stuff in its own file. I can understand the move though: this is to imitate OpenSSL in a way somewhat similar to what has been done for it with a rather generic set if routines, but with GSSAPI that's a bit different, we do not have such a set of routines, hence based on this argument moving it to its own file has little sense. Now, a move that would make sense though is to move all the GSSAPI stuff in its own file, for example pg_GSS_recvauth and pg_GSS_error for the backend, and you should do the same for the frontend with all the pg_GSS_* routines. This should be as well a refactoring patch on top of the actual feature. diff --git a/src/interfaces/libpq/fe-secure-gss.c b/src/interfaces/libpq/fe-secure-gss.c new file mode 100644 index 0000000..afea9c3 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gss.c @@ -0,0 +1,92 @@ +#include <assert.h> You should add a proper header to those new files. -- Michael
Andres Freund <andres@anarazel.de> writes: > Hi, Hi, thanks for the review; I really appreciate your time in going through this. I have questions about some of your comments, so I'll wait a bit before sending a v3. (By the way, there is a v2 of this I've already posted, though you seem to have replied to the v1. The only difference is in documentation, though.) > I quickly read through the patch, trying to understand what exactly is > happening here. To me the way the patch is split doesn't make much sense > - I don't mind incremental patches, but right now the steps don't > individually make sense. That's fair. Can you suggest a better organization? > On 2015-07-02 14:22:13 -0400, Robbie Harwood wrote: >> +#include <assert.h> > > postgres.h should be the first header included. Okay, will fix. >> +size_t >> +be_gss_encrypt(Port *port, char msgtype, const char **msgptr, size_t len) >> +{ >> + OM_uint32 major, minor; >> + gss_buffer_desc input, output; >> + uint32 len_n; >> + int conf; >> + char *ptr = *((char **)msgptr); > > trivial nitpick, missing spaces... Got it. >> +int >> +be_gss_inplace_decrypt(StringInfo inBuf) >> +{ >> + OM_uint32 major, minor; >> + gss_buffer_desc input, output; >> + int qtype, conf; >> + size_t msglen = 0; >> + >> + input.length = inBuf->len; >> + input.value = inBuf->data; >> + output.length = 0; >> + output.value = NULL; >> + >> + major = gss_unwrap(&minor, MyProcPort->gss->ctx, &input, &output, >> + &conf, NULL); >> + if (GSS_ERROR(major)) >> + { >> + pg_GSS_error(ERROR, gettext_noop("wrapping GSS message failed"), >> + major, minor); >> + return -1; >> + } >> + else if (conf == 0) >> + { >> + ereport(COMMERROR, >> + (errcode(ERRCODE_PROTOCOL_VIOLATION), >> + errmsg("Expected GSSAPI confidentiality but it was not received"))); >> + return -1; >> + } > > Hm. Aren't we leaking the gss buffer here? > >> + qtype = ((char *)output.value)[0]; /* first byte is message type */ >> + inBuf->len = output.length - 5; /* message starts */ >> + >> + memcpy((char *)&msglen, ((char *)output.value) + 1, 4); >> + msglen = ntohl(msglen); >> + if (msglen - 4 != inBuf->len) >> + { >> + ereport(COMMERROR, >> + (errcode(ERRCODE_PROTOCOL_VIOLATION), >> + errmsg("Length value inside GSSAPI-encrypted packet was malformed"))); >> + return -1; >> + } > > and here? > > Arguably it doesn't matter that much, since we'll usually disconnect > around here, but ... Probably better to be cautious about it. Will fix. >> diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c >> index a4b37ed..5a929a8 100644 >> --- a/src/backend/libpq/pqcomm.c >> +++ b/src/backend/libpq/pqcomm.c >> @@ -1485,6 +1485,19 @@ socket_putmessage(char msgtype, const char *s, size_t len) >> { >> if (DoingCopyOut || PqCommBusy) >> return 0; >> + >> +#ifdef ENABLE_GSS >> + /* Do not wrap auth requests. */ >> + if (MyProcPort->hba->auth_method == uaGSS && gss_encrypt && >> + msgtype != 'R' && msgtype != 'g') >> + { >> + len = be_gss_encrypt(MyProcPort, msgtype, &s, len); >> + if (len < 0) >> + goto fail; >> + msgtype = 'g'; >> + } >> +#endif > > Putting encryption specific code here feels rather wrong to me. Do you have a suggestion about where this code *should* go? I need to filter on the message type since some can't be encrypted. I was unable to find another place, but I may have missed it. >> diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h >> index 6171ef3..58712fc 100644 >> --- a/src/include/libpq/libpq-be.h >> +++ b/src/include/libpq/libpq-be.h >> @@ -30,6 +30,8 @@ >> #endif >> >> #ifdef ENABLE_GSS >> +#include "lib/stringinfo.h" >> + > > Conditionally including headers providing generic infrastructure strikes > me as a recipe for build failures in different configurations. That's fair, will fix. >> /* 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 c408e5b..e788cc8 100644 >> --- a/src/include/libpq/libpq.h >> +++ b/src/include/libpq/libpq.h >> @@ -99,4 +99,8 @@ extern char *SSLCipherSuites; >> extern char *SSLECDHCurve; >> extern bool SSLPreferServerCiphers; >> >> +#ifdef ENABLE_GSS >> +extern bool gss_encrypt; >> +#endif > >> --- a/src/backend/utils/misc/guc.c >> +++ b/src/backend/utils/misc/guc.c >> >> +#ifdef ENABLE_GSS >> +static void assign_gss_encrypt(bool newval, void *extra); >> +#endif >> + >> >> /* >> * Options for enum values defined in this module. >> @@ -1618,6 +1622,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, NULL, assign_gss_encrypt, NULL >> + }, >> + >> /* End-of-list marker */ >> { >> {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL >> @@ -10114,4 +10127,10 @@ show_log_file_mode(void) >> return buf; >> } > > The guc should always be present, not just when gss is built in. It > should error out when not supported. As is you're going to get linker > errors around gss_encrypt, assign_gss_encrypt. If that is the style I will conform to it. >> From e55795e0638ca37447ef200f21f74db80b07ebf4 Mon Sep 17 00:00:00 2001 >> From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> >> Date: Fri, 12 Jun 2015 13:27:50 -0400 >> Subject: Error when receiving plaintext on GSS-encrypted connections > > I don't see why this makes sense as a separate patch. As previously stated, if there is another organization you prefer, please suggest it. As stated in my first email, I have attempted to err on the side of having too many patches since splitting changesets later is nontrivial, and squashing is easy. >> Subject: server: hba option for requiring GSSAPI encryption >> >> Also includes logic for failing clients that do not request encryption >> in the startup packet when encryption is required. >> --- >> src/backend/libpq/hba.c | 9 +++++++++ >> src/backend/utils/init/postinit.c | 7 ++++++- >> src/backend/utils/misc/guc.c | 12 +++++++++++- >> src/include/libpq/hba.h | 1 + >> 4 files changed, 27 insertions(+), 2 deletions(-) >> >> diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c >> index 23c8b5d..90fe57f 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; >> + } > > So this is a new, undocumented, option that makes a connection require > encryption? But despite the generic name, it's gss specific? It was not my intent to leave it undocumented; I believe I documented it as part of my changes. If there is a place I have missed where it should be documented, please tell me and I will happily document it there. >> @@ -1628,7 +1629,7 @@ static struct config_bool ConfigureNamesBool[] = >> NULL, >> GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE >> }, >> - &gss_encrypt, false, NULL, assign_gss_encrypt, NULL >> + &gss_encrypt, false, check_gss_encrypt, assign_gss_encrypt, NULL >> }, >> >> /* End-of-list marker */ >> @@ -10133,4 +10134,13 @@ 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; >> +} > > Doing such checks in a guc assign hook seems like a horrible idea. Yes, > there's some precedent, but still. Where would you prefer they go? Isn't this what check hooks are for - checking that it's valid to assign? Thanks! --Robbie
Michael Paquier <michael.paquier@gmail.com> writes: > On Sun, Oct 4, 2015 at 1:18 AM, Andres Freund <andres@anarazel.de> wrote: >> Hi, >> >> I quickly read through the patch, trying to understand what exactly is >> happening here. To me the way the patch is split doesn't make much sense >> - I don't mind incremental patches, but right now the steps don't >> individually make sense. > > I agree with Andres. While I looked a bit at this patch, I just had a > look at them a whole block and not individually. I'm hearing block from both of you! Okay, if block is desired, I'll squish for v3. Sorry for the inconvenience. >> On 2015-07-02 14:22:13 -0400, Robbie Harwood wrote: >> [Andres' comments] > > Here are some comments on top of what Andres has mentioned. > > --- 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) > > I think that using a new configure variable like that with a dedicated > file fe-secure-gss.c and be-secure-gss.c has little sense done this > way, and that it would be more adapted to get everything grouped in > fe-auth.c for the frontend and auth.c for the backend, or move all the > GSSAPI-related stuff in its own file. I can understand the move > though: this is to imitate OpenSSL in a way somewhat similar to what > has been done for it with a rather generic set if routines, but with > GSSAPI that's a bit different, we do not have such a set of routines, > hence based on this argument moving it to its own file has little > sense. Now, a move that would make sense though is to move all the > GSSAPI stuff in its own file, for example pg_GSS_recvauth and > pg_GSS_error for the backend, and you should do the same for the > frontend with all the pg_GSS_* routines. This should be as well a > refactoring patch on top of the actual feature. My understanding is that frontend and backend code need to be separate (for linking), so it's automatically in two places. I really don't want to put encryption-related code in files called "auth.c" and "fe-auth.c" since those files are presumably for authentication, not encryption. I'm not sure what you mean about "rather generic set if routines"; GSSAPI is a RFC-standardized interface. I think I also don't understand the last half of your above paragraph. > diff --git a/src/interfaces/libpq/fe-secure-gss.c > b/src/interfaces/libpq/fe-secure-gss.c > new file mode 100644 > index 0000000..afea9c3 > --- /dev/null > +++ b/src/interfaces/libpq/fe-secure-gss.c > @@ -0,0 +1,92 @@ > +#include <assert.h> > You should add a proper header to those new files. Sorry, what?
On Sat, Oct 10, 2015 at 3:10 AM, Robbie Harwood wrote: > Michael Paquier writes: >>> On 2015-07-02 14:22:13 -0400, Robbie Harwood wrote: >>> [Andres' comments] >> >> Here are some comments on top of what Andres has mentioned. >> >> --- 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) >> >> I think that using a new configure variable like that with a dedicated >> file fe-secure-gss.c and be-secure-gss.c has little sense done this >> way, and that it would be more adapted to get everything grouped in >> fe-auth.c for the frontend and auth.c for the backend, or move all the >> GSSAPI-related stuff in its own file. I can understand the move >> though: this is to imitate OpenSSL in a way somewhat similar to what >> has been done for it with a rather generic set if routines, but with >> GSSAPI that's a bit different, we do not have such a set of routines, >> hence based on this argument moving it to its own file has little >> sense. Now, a move that would make sense though is to move all the >> GSSAPI stuff in its own file, for example pg_GSS_recvauth and >> pg_GSS_error for the backend, and you should do the same for the >> frontend with all the pg_GSS_* routines. This should be as well a >> refactoring patch on top of the actual feature. > > My understanding is that frontend and backend code need to be separate > (for linking), so it's automatically in two places. I really don't want > to put encryption-related code in files called "auth.c" and "fe-auth.c" > since those files are presumably for authentication, not encryption. > > I'm not sure what you mean about "rather generic set if routines"; > GSSAPI is a RFC-standardized interface. I think I also don't understand > the last half of your above paragraph. src/interfaces/libpq/fe-auth.c contains the following set of routines related to GSS (frontend code in libpq): - pg_GSS_error_int - pg_GSS_error - pg_GSS_continue - pg_GSS_startup src/backend/libpq/auth.c contains the following routines related to GSS (backend code): - pg_GSS_recvauth - pg_GSS_error My point would be simply to move all those routines in two new files dedicated to GSS, then add your new routines for encryption in it. Still, the only reason why the OpenSSL routines have been moved out of be-secure.c to be-secure-openssl.c is to allow other libraries to be plugged into that, the primary target being SChannel on Windows. And that's not the case of GSS, so I think that the separation done as in your patch is not adapted. >> diff --git a/src/interfaces/libpq/fe-secure-gss.c >> b/src/interfaces/libpq/fe-secure-gss.c >> new file mode 100644 >> index 0000000..afea9c3 >> --- /dev/null >> +++ b/src/interfaces/libpq/fe-secure-gss.c >> @@ -0,0 +1,92 @@ >> +#include <assert.h> >> You should add a proper header to those new files. > > Sorry, what? All the files in the source tree need to have a header like that: /*-------------------------------------------------------------------------** file_name.c* Description** Portions Copyright(c) 2015, PostgreSQL Global Development Group** IDENTIFICATION* path/to/file/file_name.c**-------------------------------------------------------------------------*/ -- Michael
Alright, here's v3. As requested, it's one patch now. Other things addressed herein include: - postgres.h/assert.h ordering fix - spacing around casts - leaking of GSS buffer in be_gss_inplace_decrypt - libpq-be.h not having a conditional internal include - always exposing guc veriable gss_encrypt - copyright/description headers on all new files - movement of GSSAPI methods from fe-auth.c and auth.c to fe-gss.c and be-gss.c respectively - renaming GSSAPI files to fe-gss.c and be-gss.c (drops -secure) Andres, one thing you mentioned as "feels rather wrong" was the GSSAPI-specific code in pqcomm.c; while looking at that again, I have a slightly better explanation than what I said previously. Essentially, the problem is that socket_putmessage_noblock() needs to know the size of the message to put in the buffer but we can't know that until we've encrypted the message. socket_putmessage_noblock() calls socket_putmessage() after ensuring the call will not block; however, other code paths simply call directly into socket_putmessage() and so socket_putmessage() needs to have a path to encryption as well. If you have other potential solutions to this problem, I would love to hear them; right now though I don't see a better way. Patch follows. Thanks! From 6710d5ad0226ea3a5ea8e35d6dc54b4500f1d3e0 Mon Sep 17 00:00:00 2001 From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com> Date: Mon, 8 Jun 2015 19:27:45 -0400 Subject: [PATCH] GSSAPI encryption support Encryption is opportuinistic by default for backward compatability, but can be forced using a server HBA parameter or a client connection URI parameter. --- configure | 2 + configure.in | 1 + doc/src/sgml/client-auth.sgml | 19 +- doc/src/sgml/libpq.sgml | 12 ++ doc/src/sgml/protocol.sgml | 82 +++++++- doc/src/sgml/runtime.sgml | 20 +- src/Makefile.global.in | 1 + src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 338 +----------------------------- src/backend/libpq/be-gss.c | 397 ++++++++++++++++++++++++++++++++++++ src/backend/libpq/hba.c | 9 + src/backend/libpq/pqcomm.c | 39 ++++ src/backend/tcop/postgres.c | 30 ++- src/backend/utils/init/postinit.c | 7 +- src/backend/utils/misc/guc.c | 30 +++ src/include/libpq/auth.h | 2 + src/include/libpq/hba.h | 1 + src/include/libpq/libpq-be.h | 26 +++ src/include/libpq/libpq.h | 2 + src/interfaces/libpq/Makefile | 4 + src/interfaces/libpq/fe-auth.c | 182 ----------------- src/interfaces/libpq/fe-connect.c | 56 ++++- src/interfaces/libpq/fe-gss.c | 280 +++++++++++++++++++++++++ src/interfaces/libpq/fe-misc.c | 5 + src/interfaces/libpq/fe-protocol3.c | 60 ++++++ src/interfaces/libpq/libpq-int.h | 16 ++ 26 files changed, 1097 insertions(+), 528 deletions(-) create mode 100644 src/backend/libpq/be-gss.c create mode 100644 src/interfaces/libpq/fe-gss.c diff --git a/configure b/configure index b771a83..a542577 100755 --- a/configure +++ b/configure @@ -712,6 +712,7 @@ with_uuid with_selinux with_openssl krb_srvtab +with_gssapi with_python with_perl with_tcl @@ -5488,6 +5489,7 @@ $as_echo "$with_gssapi" >&6; } + # # Kerberos configuration parameters # diff --git a/configure.in b/configure.in index b5868b0..fccf542 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/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 3b2935c..e6456a1 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -913,9 +913,10 @@ omicron bryanh guest1 <productname>GSSAPI</productname> with <productname>Kerberos</productname> authentication according to RFC 1964. <productname>GSSAPI</productname> 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. + that support it. The authentication itself is secure, and GSSAPI can be + used for connection encryption as well (see the + <literal>require_encrypt</literal> parameter below); <acronym>SSL</acronym> + can also be used for connection security. </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 + compatibility 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 0ee018e..32b4f1e 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/protocol.sgml b/doc/src/sgml/protocol.sgml index 34da859..cc65ee6 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1295,6 +1295,42 @@ of authentication checking. </para> </sect2> + + <sect2> + <title><acronym>GSSAPI</acronym> Session Encryption</title> + + <para> + If <productname>PostgreSQL</> was built with + <acronym>GSSAPI</acronym> and <acronym>GSSAPI</acronym> support, traffic + can also be encrypted using <acronym>GSSAPI</acronym>. To force encryption + using <acronym>GSSAPI</acronym>, set require_encrypt in + <filename>pg_hba.conf</filename>. + </para> + + <para> + In order to probe for <acronym>GSSAPI</acronym> support, the client will + include in their StartupMessage the parameter gss_encrypt. If the server + does not support <acronym>GSSAPI</acronym> or <acronym>GSSAPI</acronym> + encryption, the server will error the connection; otherwise, it continues + as normal. The client may retry the connection + without <acronym>GSSAPI</acronym> encryption support depending on its + settings. If the client does not probe support, depending on settings + in <filename>pg_hba.conf</filename>, the server may drop + <acronym>GSSAPI</acronym>-authenticated connections without encryption. + </para> + + <para> + If the client has probed <acronym>GSSAPI</acronym> encryption support and + the connection is <acronym>GSSAPI</acronym>-authenticated, then after the + server sends AuthenticationOk, all traffic between the client and server + will be <acronym>GSSAPI</acronym>-encrypted. See message formats above. + </para> + + <para> + It is valid to use <acronym>GSSAPI</acronym> encryption over + <acronym>SSL</acronym>-encrypted connections. + </para> + </sect2> </sect1> <sect1 id="protocol-replication"> @@ -2202,7 +2238,9 @@ Notice that although each message includes a byte count at the beginning, the message format is defined so that the message end can be found without reference to the byte count. This aids validity checking. (The CopyData message is an exception, because it forms part of a data stream; the contents -of any individual CopyData message cannot be interpretable on their own.) +of any individual CopyData message cannot be interpretable on their own. The +GSSAPI message type is another exception because GSSAPI does not require +payloads to include length information.) </para> <variablelist> @@ -3932,6 +3970,48 @@ FunctionCallResponse (B) </listitem> </varlistentry> +<varlistentry> +<term> +GSSAPI (F & B) +</term> +<listitem> +<para> +<variablelist> +<varlistentry> +<term> + Byte1('g') +</term> +<listitem> +<para> + Identifies the message as GSSAPI-encrypted data. +</para> +</listitem> +</varlistentry> +<varlistentry> +<term> + Int32 +</term> +<listitem> +<para> + Length of message contents in bytes, including self. +</para> +</listitem> +</varlistentry> +<varlistentry> +<term> + Byte<replaceable>n</replaceable> +</term> +<listitem> +<para> + GSSAPI-encrypted data. Once decrypted, will be in a valid + message format. +</para> +</listitem> +</varlistentry> +</variablelist> +</para> +</listitem> +</varlistentry> <varlistentry> <term> diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 6d5b108..0d65fd6 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1868,7 +1868,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 @@ -1878,6 +1878,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 GSS + connections. Then, using Kerberos, the client and server will mutually + authenticate, and the connection will be encrypted once the authentication + step is complete. + </para> </sect1> <sect1 id="encryption-options"> @@ -1993,6 +2002,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/Makefile.global.in b/src/Makefile.global.in index 51f4797..5814440 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_libxml = @with_libxml@ with_libxslt = @with_libxslt@ diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 09410c4..ee69b82 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-gss.o +endif + include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index aca4ffe..83ff56f 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -36,7 +36,6 @@ * Global authentication functions *---------------------------------------------------------------- */ -static void sendAuthRequest(Port *port, AuthRequest areq); static void auth_failed(Port *port, int status, char *logdetail); static char *recv_password_packet(Port *port); static int recv_and_check_password_packet(Port *port, char **logdetail); @@ -122,30 +121,6 @@ static int CheckLDAPAuth(Port *port); static int CheckCertAuth(Port *port); #endif - -/*---------------------------------------------------------------- - * Kerberos and GSSAPI GUCs - *---------------------------------------------------------------- - */ -char *pg_krb_server_keyfile; -bool pg_krb_caseins_users; - - -/*---------------------------------------------------------------- - * GSSAPI Authentication - *---------------------------------------------------------------- - */ -#ifdef ENABLE_GSS -#if defined(HAVE_GSSAPI_H) -#include <gssapi.h> -#else -#include <gssapi/gssapi.h> -#endif - -static int pg_GSS_recvauth(Port *port); -#endif /* ENABLE_GSS */ - - /*---------------------------------------------------------------- * SSPI Authentication *---------------------------------------------------------------- @@ -166,23 +141,6 @@ static int pg_SSPI_recvauth(Port *port); #endif static int CheckRADIUSAuth(Port *port); - -/* - * Maximum accepted size of GSS and SSPI authentication tokens. - * - * Kerberos tickets are usually quite small, but the TGTs issued by Windows - * domain controllers include an authorization field known as the Privilege - * Attribute Certificate (PAC), which contains the user's Windows permissions - * (group memberships etc.). The PAC is copied into all tickets obtained on - * the basis of this TGT (even those issued by Unix realms which the Windows - * realm trusts), and can be several kB in size. The maximum token size - * accepted by Windows systems is determined by the MaxAuthToken Windows - * registry setting. Microsoft recommends that it is not set higher than - * 65535 bytes, so that seems like a reasonable limit for us as well. - */ -#define PG_MAX_AUTH_TOKEN_LENGTH 65535 - - /*---------------------------------------------------------------- * Global authentication functions *---------------------------------------------------------------- @@ -565,7 +523,7 @@ ClientAuthentication(Port *port) /* * Send an authentication request packet to the frontend. */ -static void +void sendAuthRequest(Port *port, AuthRequest areq) { StringInfoData buf; @@ -708,300 +666,6 @@ recv_and_check_password_packet(Port *port, char **logdetail) return result; } - - -/*---------------------------------------------------------------- - * GSSAPI authentication system - *---------------------------------------------------------------- - */ -#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) -{ - OM_uint32 maj_stat, - min_stat, - lmin_s, - gflags; - int mtype; - int ret; - StringInfoData buf; - gss_buffer_desc gbuf; - - /* - * GSS auth is not supported for protocol versions before 3, because it - * relies on the overall message length word to determine the GSS payload - * size in AuthenticationGSSContinue and PasswordMessage messages. (This - * is, in fact, a design error in our GSS support, because protocol - * messages are supposed to be parsable without relying on the length - * word; but it's not worth changing it now.) - */ - if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) - ereport(FATAL, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("GSSAPI is not supported in protocol version 2"))); - - if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0) - { - /* - * Set default Kerberos keytab file for the Krb5 mechanism. - * - * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv() - * not always available. - */ - if (getenv("KRB5_KTNAME") == NULL) - { - size_t kt_len = strlen(pg_krb_server_keyfile) + 14; - char *kt_path = malloc(kt_len); - - if (!kt_path || - snprintf(kt_path, kt_len, "KRB5_KTNAME=%s", - pg_krb_server_keyfile) != kt_len - 2 || - putenv(kt_path) != 0) - { - ereport(LOG, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("out of memory"))); - return STATUS_ERROR; - } - } - } - - /* - * We accept any service principal that's present in our keytab. This - * increases interoperability between kerberos implementations that see - * for example case sensitivity differently, while not really opening up - * any vector of attack. - */ - port->gss->cred = GSS_C_NO_CREDENTIAL; - - /* - * Initialize sequence with an empty context - */ - port->gss->ctx = GSS_C_NO_CONTEXT; - - /* - * Loop through GSSAPI message exchange. This exchange can consist of - * multiple messags sent in both directions. First message is always from - * the client. All messages from client to server are password packets - * (type 'p'). - */ - do - { - pq_startmsgread(); - - CHECK_FOR_INTERRUPTS(); - - mtype = pq_getbyte(); - if (mtype != 'p') - { - /* Only log error if client didn't disconnect. */ - if (mtype != EOF) - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("expected GSS response, got message type %d", - mtype))); - return STATUS_ERROR; - } - - /* Get the actual GSS token */ - initStringInfo(&buf); - if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH)) - { - /* EOF - pq_getmessage already logged error */ - pfree(buf.data); - return STATUS_ERROR; - } - - /* Map to GSSAPI style buffer */ - gbuf.length = buf.len; - gbuf.value = buf.data; - - elog(DEBUG4, "Processing received GSS token of length %u", - (unsigned int) gbuf.length); - - maj_stat = gss_accept_sec_context( - &min_stat, - &port->gss->ctx, - port->gss->cred, - &gbuf, - GSS_C_NO_CHANNEL_BINDINGS, - &port->gss->name, - NULL, - &port->gss->outbuf, - &gflags, - NULL, - NULL); - - /* gbuf no longer used */ - pfree(buf.data); - - elog(DEBUG5, "gss_accept_sec_context major: %d, " - "minor: %d, outlen: %u, outflags: %x", - maj_stat, min_stat, - (unsigned int) port->gss->outbuf.length, gflags); - - CHECK_FOR_INTERRUPTS(); - - if (port->gss->outbuf.length != 0) - { - /* - * Negotiation generated data to be sent to the client. - */ - elog(DEBUG4, "sending GSS response token of length %u", - (unsigned int) port->gss->outbuf.length); - - sendAuthRequest(port, AUTH_REQ_GSS_CONT); - - gss_release_buffer(&lmin_s, &port->gss->outbuf); - } - - if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) - { - gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER); - pg_GSS_error(ERROR, - gettext_noop("accepting GSS security context failed"), - maj_stat, min_stat); - } - - if (maj_stat == GSS_S_CONTINUE_NEEDED) - elog(DEBUG4, "GSS continue needed"); - - } while (maj_stat == GSS_S_CONTINUE_NEEDED); - - if (port->gss->cred != GSS_C_NO_CREDENTIAL) - { - /* - * Release service principal credentials - */ - gss_release_cred(&min_stat, &port->gss->cred); - } - - /* - * GSS_S_COMPLETE indicates that authentication is now complete. - * - * Get the name of the user that authenticated, and compare it to the pg - * username that was specified for the connection. - */ - maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL); - if (maj_stat != GSS_S_COMPLETE) - pg_GSS_error(ERROR, - gettext_noop("retrieving GSS user name failed"), - maj_stat, min_stat); - - /* - * Split the username at the realm separator - */ - if (strchr(gbuf.value, '@')) - { - char *cp = strchr(gbuf.value, '@'); - - /* - * If we are not going to include the realm in the username that is - * passed to the ident map, destructively modify it here to remove the - * realm. Then advance past the separator to check the realm. - */ - if (!port->hba->include_realm) - *cp = '\0'; - cp++; - - if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm)) - { - /* - * Match the realm part of the name first - */ - if (pg_krb_caseins_users) - ret = pg_strcasecmp(port->hba->krb_realm, cp); - else - ret = strcmp(port->hba->krb_realm, cp); - - if (ret) - { - /* GSS realm does not match */ - elog(DEBUG2, - "GSSAPI realm (%s) and configured realm (%s) don't match", - cp, port->hba->krb_realm); - gss_release_buffer(&lmin_s, &gbuf); - return STATUS_ERROR; - } - } - } - else if (port->hba->krb_realm && strlen(port->hba->krb_realm)) - { - elog(DEBUG2, - "GSSAPI did not return realm but realm matching was requested"); - - gss_release_buffer(&lmin_s, &gbuf); - return STATUS_ERROR; - } - - ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, - pg_krb_caseins_users); - - gss_release_buffer(&lmin_s, &gbuf); - - return ret; -} -#endif /* ENABLE_GSS */ - - /*---------------------------------------------------------------- * SSPI authentication system *---------------------------------------------------------------- diff --git a/src/backend/libpq/be-gss.c b/src/backend/libpq/be-gss.c new file mode 100644 index 0000000..2c3e58e --- /dev/null +++ b/src/backend/libpq/be-gss.c @@ -0,0 +1,397 @@ +/*------------------------------------------------------------------------- + * + * be-gss.c + * functions for GSSAPI support in the backend. + * + * Portions Copyright (c) 2015, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/libpq/be-gss.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "libpq/libpq.h" +#include "libpq/auth.h" +#include "miscadmin.h" + +#include <assert.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))); +} + +int +pg_GSS_recvauth(Port *port) +{ + OM_uint32 maj_stat, + min_stat, + lmin_s, + gflags; + int mtype; + int ret; + StringInfoData buf; + gss_buffer_desc gbuf; + + /* + * GSS auth is not supported for protocol versions before 3, because it + * relies on the overall message length word to determine the GSS payload + * size in AuthenticationGSSContinue and PasswordMessage messages. (This + * is, in fact, a design error in our GSS support, because protocol + * messages are supposed to be parsable without relying on the length + * word; but it's not worth changing it now.) + */ + if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) + ereport(FATAL, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("GSSAPI is not supported in protocol version 2"))); + + if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0) + { + /* + * Set default Kerberos keytab file for the Krb5 mechanism. + * + * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv() + * not always available. + */ + if (getenv("KRB5_KTNAME") == NULL) + { + size_t kt_len = strlen(pg_krb_server_keyfile) + 14; + char *kt_path = malloc(kt_len); + + if (!kt_path || + snprintf(kt_path, kt_len, "KRB5_KTNAME=%s", + pg_krb_server_keyfile) != kt_len - 2 || + putenv(kt_path) != 0) + { + ereport(LOG, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + return STATUS_ERROR; + } + } + } + + /* + * We accept any service principal that's present in our keytab. This + * increases interoperability between kerberos implementations that see + * for example case sensitivity differently, while not really opening up + * any vector of attack. + */ + port->gss->cred = GSS_C_NO_CREDENTIAL; + + /* + * Initialize sequence with an empty context + */ + port->gss->ctx = GSS_C_NO_CONTEXT; + + /* + * Loop through GSSAPI message exchange. This exchange can consist of + * multiple messags sent in both directions. First message is always from + * the client. All messages from client to server are password packets + * (type 'p'). + */ + do + { + pq_startmsgread(); + + CHECK_FOR_INTERRUPTS(); + + mtype = pq_getbyte(); + if (mtype != 'p') + { + /* Only log error if client didn't disconnect. */ + if (mtype != EOF) + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("expected GSS response, got message type %d", + mtype))); + return STATUS_ERROR; + } + + /* Get the actual GSS token */ + initStringInfo(&buf); + if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH)) + { + /* EOF - pq_getmessage already logged error */ + pfree(buf.data); + return STATUS_ERROR; + } + + /* Map to GSSAPI style buffer */ + gbuf.length = buf.len; + gbuf.value = buf.data; + + elog(DEBUG4, "Processing received GSS token of length %u", + (unsigned int) gbuf.length); + + maj_stat = gss_accept_sec_context( + &min_stat, + &port->gss->ctx, + port->gss->cred, + &gbuf, + GSS_C_NO_CHANNEL_BINDINGS, + &port->gss->name, + NULL, + &port->gss->outbuf, + &gflags, + NULL, + NULL); + + /* gbuf no longer used */ + pfree(buf.data); + + elog(DEBUG5, "gss_accept_sec_context major: %d, " + "minor: %d, outlen: %u, outflags: %x", + maj_stat, min_stat, + (unsigned int) port->gss->outbuf.length, gflags); + + CHECK_FOR_INTERRUPTS(); + + if (port->gss->outbuf.length != 0) + { + /* + * Negotiation generated data to be sent to the client. + */ + elog(DEBUG4, "sending GSS response token of length %u", + (unsigned int) port->gss->outbuf.length); + + sendAuthRequest(port, AUTH_REQ_GSS_CONT); + + gss_release_buffer(&lmin_s, &port->gss->outbuf); + } + + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) + { + gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER); + pg_GSS_error(ERROR, + gettext_noop("accepting GSS security context failed"), + maj_stat, min_stat); + } + + if (maj_stat == GSS_S_CONTINUE_NEEDED) + elog(DEBUG4, "GSS continue needed"); + + } while (maj_stat == GSS_S_CONTINUE_NEEDED); + + if (port->gss->cred != GSS_C_NO_CREDENTIAL) + { + /* + * Release service principal credentials + */ + gss_release_cred(&min_stat, &port->gss->cred); + } + + /* + * GSS_S_COMPLETE indicates that authentication is now complete. + * + * Get the name of the user that authenticated, and compare it to the pg + * username that was specified for the connection. + */ + maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL); + if (maj_stat != GSS_S_COMPLETE) + pg_GSS_error(ERROR, + gettext_noop("retrieving GSS user name failed"), + maj_stat, min_stat); + + /* + * Split the username at the realm separator + */ + if (strchr(gbuf.value, '@')) + { + char *cp = strchr(gbuf.value, '@'); + + /* + * If we are not going to include the realm in the username that is + * passed to the ident map, destructively modify it here to remove the + * realm. Then advance past the separator to check the realm. + */ + if (!port->hba->include_realm) + *cp = '\0'; + cp++; + + if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm)) + { + /* + * Match the realm part of the name first + */ + if (pg_krb_caseins_users) + ret = pg_strcasecmp(port->hba->krb_realm, cp); + else + ret = strcmp(port->hba->krb_realm, cp); + + if (ret) + { + /* GSS realm does not match */ + elog(DEBUG2, + "GSSAPI realm (%s) and configured realm (%s) don't match", + cp, port->hba->krb_realm); + gss_release_buffer(&lmin_s, &gbuf); + return STATUS_ERROR; + } + } + } + else if (port->hba->krb_realm && strlen(port->hba->krb_realm)) + { + elog(DEBUG2, + "GSSAPI did not return realm but realm matching was requested"); + + gss_release_buffer(&lmin_s, &gbuf); + return STATUS_ERROR; + } + + ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, + pg_krb_caseins_users); + + gss_release_buffer(&lmin_s, &gbuf); + + return ret; +} + +size_t +be_gss_encrypt(Port *port, char msgtype, const char **msgptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + uint32 len_n; + int conf; + char *ptr = *((char **) msgptr); + char *newbuf = palloc(len + 5); + + len += 4; + len_n = htonl(len); + + newbuf[0] = msgtype; + memcpy(newbuf + 1, &len_n, 4); + memcpy(newbuf + 5, ptr, len - 4); + + input.length = len + 1; /* include type */ + input.value = newbuf; + output.length = 0; + output.value = NULL; + + 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("unwrapping GSS message failed"), + major, minor); + return -1; + } + assert(conf); + + newbuf = repalloc(newbuf, output.length); + memcpy(newbuf, output.value, output.length); + + len = output.length; + *msgptr = newbuf; + gss_release_buffer(&minor, &output); + + return len; +} + +int +be_gss_inplace_decrypt(StringInfo inBuf) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + int qtype, conf; + size_t msglen = 0; + + input.length = inBuf->len; + input.value = inBuf->data; + output.length = 0; + output.value = NULL; + + major = gss_unwrap(&minor, MyProcPort->gss->ctx, &input, &output, + &conf, NULL); + if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, gettext_noop("wrapping GSS message failed"), + major, minor); + return -1; + } + else if (conf == 0) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("Expected GSSAPI confidentiality but it was not received"))); + gss_release_buffer(&minor, &output); + return -1; + } + + qtype = ((char *)output.value)[0]; /* first byte is message type */ + inBuf->len = output.length - 5; /* message starts */ + + memcpy((char *)&msglen, ((char *)output.value) + 1, 4); + msglen = ntohl(msglen); + if (msglen - 4 != inBuf->len) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("Length value inside GSSAPI-encrypted packet was malformed"))); + gss_release_buffer(&minor, &output); + return -1; + } + + memcpy(inBuf->data, ((char *)output.value) + 5, inBuf->len); + inBuf->data[inBuf->len] = '\0'; /* invariant */ + gss_release_buffer(&minor, &output); + + return qtype; +} diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 23c8b5d..90fe57f 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/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index 63673b1..2603951 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -1513,6 +1513,19 @@ socket_putmessage(char msgtype, const char *s, size_t len) { if (DoingCopyOut || PqCommBusy) return 0; + +#ifdef ENABLE_GSS + /* Do not wrap auth requests. */ + if (MyProcPort->hba->auth_method == uaGSS && gss_encrypt && + msgtype != 'R' && msgtype != 'g') + { + len = be_gss_encrypt(MyProcPort, msgtype, &s, len); + if (len < 0) + goto fail; + msgtype = 'g'; + } +#endif + PqCommBusy = true; if (msgtype) if (internal_putbytes(&msgtype, 1)) @@ -1528,10 +1541,20 @@ socket_putmessage(char msgtype, const char *s, size_t len) if (internal_putbytes(s, len)) goto fail; PqCommBusy = false; +#ifdef ENABLE_GSS + /* if we're GSSAPI encrypting, s was allocated in be_gss_encrypt */ + if (msgtype == 'g') + pfree((char *)s); +#endif return 0; fail: PqCommBusy = false; +#ifdef ENABLE_GSS + /* if we're GSSAPI encrypting, s was allocated in be_gss_encrypt */ + if (msgtype == 'g') + pfree((char *)s); +#endif return EOF; } @@ -1547,6 +1570,22 @@ socket_putmessage_noblock(char msgtype, const char *s, size_t len) int res PG_USED_FOR_ASSERTS_ONLY; int required; +#ifdef ENABLE_GSS + /* + * Because socket_putmessage is also a front-facing function, we need the + * ability to GSSAPI encrypt from either. Since socket_putmessage_noblock + * calls into socket_putmessage, socket_putmessage will handle freeing the + * allocated string. + */ + if (gss_encrypt && msgtype != 'R' && msgtype != 'g') + { + len = be_gss_encrypt(MyProcPort, msgtype, &s, len); + if (len < 0) + return; + msgtype = 'g'; + } +#endif + /* * Ensure we have enough space in the output buffer for the message header * as well as the message itself. diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index d30fe35..0dc0376 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -336,6 +336,7 @@ static int SocketBackend(StringInfo inBuf) { int qtype; + bool msg_got = false; /* * Get message type code from the frontend. @@ -365,6 +366,33 @@ SocketBackend(StringInfo inBuf) return qtype; } +#ifdef ENABLE_GSS + else if (qtype == 'g' && gss_encrypt && + MyProcPort->hba->auth_method == uaGSS) + { + /* GSSAPI wrapping implies protocol >= 3 */ + if (pq_getmessage(inBuf, 0)) + return EOF; + msg_got = true; + + qtype = be_gss_inplace_decrypt(inBuf); + if (qtype < 0) + return EOF; + } + else if (gss_encrypt && MyProcPort->hba->auth_method == uaGSS && + qtype != 'g' && qtype != 'R' ) + { + /* + * Either something malicious is occuring, or we have lost + * synchronization. + */ + ereport(FATAL, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("invalid frontend message type %d", qtype))); + return EOF; + } +#endif + /* * Validate message type code before trying to read body; if we have lost * sync, better to say "command unknown" than to run out of memory because @@ -490,7 +518,7 @@ SocketBackend(StringInfo inBuf) * after the type code; we can read the message contents independently of * the type. */ - if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) + if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3 && !msg_got) { if (pq_getmessage(inBuf, 0)) return EOF; /* suitable message already logged */ diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 7b19714..201c831 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,11 @@ 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 71090f2..b422fd1 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -186,6 +186,8 @@ 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. @@ -477,6 +479,10 @@ static bool assert_enabled; /* should be static, but commands/variable.c needs to get at this */ char *role_string; +/* Kerberos and GSSAPI GUCs */ +char *pg_krb_server_keyfile; +bool pg_krb_caseins_users; +bool gss_encrypt; /* * Displayable names for context types (enum GucContext) @@ -1610,6 +1616,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 @@ -10108,4 +10123,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/auth.h b/src/include/libpq/auth.h index 80f26a8..ba197c6 100644 --- a/src/include/libpq/auth.h +++ b/src/include/libpq/auth.h @@ -26,4 +26,6 @@ extern void ClientAuthentication(Port *port); typedef void (*ClientAuthentication_hook_type) (Port *, int); extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook; +extern void sendAuthRequest(Port *port, AuthRequest areq); + #endif /* AUTH_H */ 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 caaa8b5..98839a0 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -30,6 +30,7 @@ #endif #ifdef ENABLE_GSS + #if defined(HAVE_GSSAPI_H) #include <gssapi.h> #else @@ -68,7 +69,22 @@ typedef struct #include "datatype/timestamp.h" #include "libpq/hba.h" #include "libpq/pqcomm.h" +#include "lib/stringinfo.h" +/* + * Maximum accepted size of GSS and SSPI authentication tokens. + * + * Kerberos tickets are usually quite small, but the TGTs issued by Windows + * domain controllers include an authorization field known as the Privilege + * Attribute Certificate (PAC), which contains the user's Windows permissions + * (group memberships etc.). The PAC is copied into all tickets obtained on + * the basis of this TGT (even those issued by Unix realms which the Windows + * realm trusts), and can be several kB in size. The maximum token size + * accepted by Windows systems is determined by the MaxAuthToken Windows + * registry setting. Microsoft recommends that it is not set higher than + * 65535 bytes, so that seems like a reasonable limit for us as well. + */ +#define PG_MAX_AUTH_TOKEN_LENGTH 65535 typedef enum CAC_state { @@ -214,6 +230,16 @@ 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 +/* These functions are implemented in be-gss.c */ +extern int pg_GSS_recvauth(Port *port); +extern size_t +be_gss_encrypt(Port *port, char msgtype, const char **msgptr, size_t len); +extern int be_gss_inplace_decrypt(StringInfo inBuf); +extern void pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, + OM_uint32 min_stat); +#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 efb2dac..3fd6e7d 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 c2105f1..807db20 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-gss.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 5891c75..c606c90 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -42,188 +42,6 @@ #include "fe-auth.h" #include "libpq/md5.h" - -#ifdef ENABLE_GSS -/* - * 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); -} - -/* - * Continue GSS authentication with next token as needed. - */ -static int -pg_GSS_continue(PGconn *conn) -{ - OM_uint32 maj_stat, - min_stat, - lmin_s; - - 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, - 0, - GSS_C_NO_CHANNEL_BINDINGS, - (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf, - NULL, - &conn->goutbuf, - NULL, - NULL); - - if (conn->gctx != GSS_C_NO_CONTEXT) - { - free(conn->ginbuf.value); - conn->ginbuf.value = NULL; - conn->ginbuf.length = 0; - } - - if (conn->goutbuf.length != 0) - { - /* - * GSS generated data to send to the server. We don't care if it's the - * first or subsequent packet, just send the same kind of password - * packet. - */ - if (pqPacketSend(conn, 'p', - conn->goutbuf.value, conn->goutbuf.length) - != STATUS_OK) - { - gss_release_buffer(&lmin_s, &conn->goutbuf); - return STATUS_ERROR; - } - } - gss_release_buffer(&lmin_s, &conn->goutbuf); - - if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) - { - pg_GSS_error(libpq_gettext("GSSAPI continuation error"), - conn, - maj_stat, min_stat); - gss_release_name(&lmin_s, &conn->gtarg_nam); - if (conn->gctx) - gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER); - return STATUS_ERROR; - } - - if (maj_stat == GSS_S_COMPLETE) - gss_release_name(&lmin_s, &conn->gtarg_nam); - - return STATUS_OK; -} - -/* - * Send initial GSS authentication token - */ -static int -pg_GSS_startup(PGconn *conn) -{ - OM_uint32 maj_stat, - min_stat; - int maxlen; - gss_buffer_desc temp_gbuf; - - if (!(conn->pghost && conn->pghost[0] != '\0')) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("host name must be specified\n")); - return STATUS_ERROR; - } - - if (conn->gctx) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("duplicate GSS authentication request\n")); - return STATUS_ERROR; - } - - /* - * Import service principal name so the proper ticket can be acquired by - * the GSSAPI system. - */ - maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; - temp_gbuf.value = (char *) malloc(maxlen); - if (!temp_gbuf.value) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory\n")); - return STATUS_ERROR; - } - snprintf(temp_gbuf.value, maxlen, "%s@%s", - conn->krbsrvname, conn->pghost); - temp_gbuf.length = strlen(temp_gbuf.value); - - maj_stat = gss_import_name(&min_stat, &temp_gbuf, - GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); - free(temp_gbuf.value); - - if (maj_stat != GSS_S_COMPLETE) - { - pg_GSS_error(libpq_gettext("GSSAPI name import error"), - conn, - maj_stat, min_stat); - return STATUS_ERROR; - } - - /* - * Initial packet is the same as a continuation packet with no initial - * context. - */ - conn->gctx = GSS_C_NO_CONTEXT; - - return pg_GSS_continue(conn); -} -#endif /* ENABLE_GSS */ - - #ifdef ENABLE_SSPI /* * SSPI authentication system (Windows only) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index f3030fb..4fcffa3 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -91,8 +91,9 @@ 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 GSS 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 +297,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)}, @@ -2512,6 +2519,11 @@ 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; +#endif + /* * Set asyncStatus so that PQgetResult will think that * what comes back next is the result of a query. See @@ -2552,6 +2564,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 GSS encryption, but the server + * doesn't support it. Retries are permitted here, so + * hang up and try again. A connection that doesn't + * support 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); + 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)) { @@ -2569,7 +2612,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; @@ -2579,6 +2622,15 @@ 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 GSS encryption\n")); +#endif /* * if the resultStatus is FATAL, then conn->errorMessage diff --git a/src/interfaces/libpq/fe-gss.c b/src/interfaces/libpq/fe-gss.c new file mode 100644 index 0000000..4ac52e5 --- /dev/null +++ b/src/interfaces/libpq/fe-gss.c @@ -0,0 +1,280 @@ +/*------------------------------------------------------------------------- + * + * fe-gss.c + * functions for GSSAPI support in the frontend. + * + * Portions Copyright (c) 2015, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/libpq/fe-gss.c + * + *------------------------------------------------------------------------- + */ + +#include "libpq-fe.h" +#include "postgres_fe.h" +#include "fe-auth.h" +#include "libpq-int.h" + +#include <assert.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); +} + +/* + * Continue GSS authentication with next token as needed. + */ +int +pg_GSS_continue(PGconn *conn) +{ + OM_uint32 maj_stat, + min_stat, + lmin_s; + + 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, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf, + NULL, + &conn->goutbuf, + NULL, + NULL); + + if (conn->gctx != GSS_C_NO_CONTEXT) + { + free(conn->ginbuf.value); + conn->ginbuf.value = NULL; + conn->ginbuf.length = 0; + } + + if (conn->goutbuf.length != 0) + { + /* + * GSS generated data to send to the server. We don't care if it's the + * first or subsequent packet, just send the same kind of password + * packet. + */ + if (pqPacketSend(conn, 'p', + conn->goutbuf.value, conn->goutbuf.length) + != STATUS_OK) + { + gss_release_buffer(&lmin_s, &conn->goutbuf); + return STATUS_ERROR; + } + } + gss_release_buffer(&lmin_s, &conn->goutbuf); + + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) + { + pg_GSS_error(libpq_gettext("GSSAPI continuation error"), + conn, + maj_stat, min_stat); + gss_release_name(&lmin_s, &conn->gtarg_nam); + if (conn->gctx) + gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER); + return STATUS_ERROR; + } + + if (maj_stat == GSS_S_COMPLETE) + gss_release_name(&lmin_s, &conn->gtarg_nam); + + return STATUS_OK; +} + +/* + * Send initial GSS authentication token + */ +int +pg_GSS_startup(PGconn *conn) +{ + OM_uint32 maj_stat, + min_stat; + int maxlen; + gss_buffer_desc temp_gbuf; + + if (!(conn->pghost && conn->pghost[0] != '\0')) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("host name must be specified\n")); + return STATUS_ERROR; + } + + if (conn->gctx) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("duplicate GSS authentication request\n")); + return STATUS_ERROR; + } + + /* + * Import service principal name so the proper ticket can be acquired by + * the GSSAPI system. + */ + maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; + temp_gbuf.value = (char *) malloc(maxlen); + if (!temp_gbuf.value) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return STATUS_ERROR; + } + snprintf(temp_gbuf.value, maxlen, "%s@%s", + conn->krbsrvname, conn->pghost); + temp_gbuf.length = strlen(temp_gbuf.value); + + maj_stat = gss_import_name(&min_stat, &temp_gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); + free(temp_gbuf.value); + + if (maj_stat != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI name import error"), + conn, + maj_stat, min_stat); + return STATUS_ERROR; + } + + /* + * Initial packet is the same as a continuation packet with no initial + * context. + */ + conn->gctx = GSS_C_NO_CONTEXT; + + return pg_GSS_continue(conn); +} + +ssize_t +pggss_inplace_decrypt(PGconn *conn, int gsslen) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t n; + int conf; + + input.length = gsslen; + input.value = conn->inBuffer + conn->inCursor; + output.length = 0; + output.value = NULL; + + major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL); + if (GSS_ERROR(major)) + { + pg_GSS_error("GSSAPI unwrap error", conn, major, minor); + return -1; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "received GSSAPI message without confidentiality\n")); + return -1; + } + + memcpy(conn->inBuffer + conn->inStart, output.value, output.length); + n = output.length; + gss_release_buffer(&minor, &output); + return n; +} + +int +pggss_encrypt(PGconn *conn) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + int msgLen, conf; + uint32 len_n; + + if (conn->gss_disable_enc || !conn->gctx || !conn->gss_auth_done) + return 0; + assert(conn->outMsgStart > 0); + + /* We need to encrypt message type as well */ + conn->outMsgStart -= 1; + msgLen = conn->outMsgEnd - conn->outMsgStart; + + input.value = conn->outBuffer + conn->outMsgStart; + input.length = msgLen; + output.length = 0; + output.value = NULL; + + major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT, &input, &conf, + &output); + if (GSS_ERROR(major)) + { + pg_GSS_error("GSSAPI wrap error", conn, major, minor); + return -1; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "Failed to obtain confidentiality for outgoing GSSAPI message\n")); + return -1; + } + + msgLen = output.length + 4; + if (pqCheckOutBufferSpace(conn->outMsgStart + msgLen + 1, conn)) + return -1; + + conn->outBuffer[conn->outMsgStart] = 'g'; /* GSSAPI message */ + + len_n = htonl(msgLen); + memcpy(conn->outBuffer + conn->outMsgStart + 1, &len_n, 4); + + memcpy(conn->outBuffer + conn->outMsgStart + 1 + 4, + output.value, output.length); + conn->outMsgEnd = conn->outMsgStart + msgLen + 1; + + gss_release_buffer(&minor, &output); + return msgLen + 1; +} diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c index 0dbcf73..2379aff 100644 --- a/src/interfaces/libpq/fe-misc.c +++ b/src/interfaces/libpq/fe-misc.c @@ -604,6 +604,11 @@ pqPutMsgEnd(PGconn *conn) memcpy(conn->outBuffer + conn->outMsgStart, &msgLen, 4); } +#ifdef ENABLE_GSS + if (pggss_encrypt(conn) < 0) + return EOF; +#endif + /* Make message eligible to send */ conn->outCount = conn->outMsgEnd; diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 641804c..6127b91 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -129,6 +129,58 @@ pqParseInput3(PGconn *conn) return; } +#ifdef ENABLE_GSS + /* We want to be ready in both IDLE and BUSY states for encryption */ + if (id == 'g' && !conn->gss_disable_enc && conn->gctx) + { + ssize_t encEnd, next; + + encEnd = pggss_inplace_decrypt(conn, msgLength); + if (encEnd <= 0) + { + /* error message placed by pggss_inplace_decrypt() */ + pqSaveErrorResult(conn); + conn->asyncStatus = PGASYNC_READY; + pqDropConnection(conn); + conn->status = CONNECTION_BAD; + return; + } + + /* shift contents of buffer to account for slack */ + encEnd += conn->inStart; + next = conn->inStart + msgLength + 5; + memmove(conn->inBuffer + encEnd, conn->inBuffer + next, + conn->inEnd - next); + conn->inEnd = (conn->inEnd - next) + encEnd; + + conn->inCursor = conn->inStart; + (void) pqGetc(&id, conn); + (void) pqGetInt(&msgLength, 4, conn); + msgLength -= 4; + if (msgLength != encEnd - conn->inCursor) + { + /* This isn't a sync error because decrypt was successful */ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext( + "server lied about message length: got message length %ld, but expected legnth %d\n"), + encEnd - conn->inCursor, msgLength); + /* build an error result holding the error message */ + pqSaveErrorResult(conn); + /* drop out of GetResult wait loop */ + conn->asyncStatus = PGASYNC_READY; + + pqDropConnection(conn); + /* No more connection to backend */ + conn->status = CONNECTION_BAD; + } + conn->gss_decrypted_cur = true; + } + else if (!conn->gss_disable_enc && conn->gss_auth_done && + !conn->gss_decrypted_cur && id != 'E') + /* This could be a sync error, so let's handle it as such. */ + handleSyncLoss(conn, id, msgLength); +#endif + /* * NOTIFY and NOTICE messages can happen in any state; always process * them right away. @@ -415,6 +467,9 @@ pqParseInput3(PGconn *conn) { /* Normal case: parsing agrees with specified length */ conn->inStart = conn->inCursor; +#ifdef ENABLE_GSS + conn->gss_decrypted_cur = false; +#endif } else { @@ -2083,6 +2138,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/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 04d056e..cca90c3 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -446,6 +446,10 @@ 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 */ + bool gss_disable_enc; /* Does server recognize gss_encrypt? */ + bool gss_auth_done; /* Did we finish the AUTH step? */ + bool gss_decrypted_cur; /* Is first message in buffer decrypted? */ + char *gss_enc_require; /* Can we downgrade to plaintext? */ #endif #ifdef ENABLE_SSPI @@ -643,6 +647,18 @@ extern bool pgtls_read_pending(PGconn *conn); extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len); /* + * GSSAPI functions defined in fe-gss.c + */ +#ifdef ENABLE_GSS +extern ssize_t pggss_inplace_decrypt(PGconn *conn, int gsslen); +extern int pggss_encrypt(PGconn *conn); +extern void pg_GSS_error(const char *mprefix, PGconn *conn, OM_uint32 maj_stat, + OM_uint32 min_stat); +extern int pg_GSS_startup(PGconn *conn); +extern int pg_GSS_continue(PGconn *conn); +#endif /* ENABLE_GSS */ + +/* * this is so that we can check if a connection is non-blocking internally * without the overhead of a function call */ -- 2.6.1
Attachment
On Wed, Oct 14, 2015 at 7:34 AM, Robbie Harwood <rharwood@redhat.com> wrote: > Alright, here's v3. As requested, it's one patch now. Other things > addressed herein include: > Essentially, the problem is that socket_putmessage_noblock() needs to > know the size of the message to put in the buffer but we can't know > that until we've encrypted the message. socket_putmessage_noblock() > calls socket_putmessage() after ensuring the call will not block; > however, other code paths simply call directly into socket_putmessage() > and so socket_putmessage() needs to have a path to encryption as well. > > If you have other potential solutions to this problem, I would love to > hear them; right now though I don't see a better way. > > Patch follows. Thanks! After giving a quick shot at this patch, I am still seeing the same problem: psql: lost synchronization with server: got message type "S", length 22 (And unpatched works, I will try to put my head into your code...) Regards, -- Michael
On 14 October 2015 at 06:34, Robbie Harwood <rharwood@redhat.com> wrote: > Alright, here's v3. As requested, it's one patch now. I hate to ask, but have you looked at how this interacts with Windows? We support Windows SSPI (on a domain-member host) authenticating to a PostgreSQL server using gssapi with spnego. We also support a PostgreSQL client on *nix authenticating using gssapi with spnego to a PostgreSQL server that's requesting sspi mode. The relevant code is all a bit tangled, since there's support in there for using Kerberos libraries on Windows instead of SSPI too. I doubt anybody uses that last one, tests it, or cares about it, though, given the painful hoop-jumping, registry key permission changes, etc required to make it work. For bonus fun, RC4, DES, AES128 or AES256 are available/used for Kerberos encryption on Windows. See http://blogs.msdn.com/b/openspecification/archive/2011/05/31/windows-configurations-for-kerberos-supported-encryption-type.aspx . Though given that Win7 defaults to AES256 it's probably reasonable to simply not care about anything else. -- Craig Ringer http://www.2ndQuadrant.com/PostgreSQL Development, 24x7 Support, Training & Services
Craig Ringer <craig@2ndquadrant.com> writes: > On 14 October 2015 at 06:34, Robbie Harwood <rharwood@redhat.com> wrote: >> Alright, here's v3. As requested, it's one patch now. > > I hate to ask, but have you looked at how this interacts with Windows? > > We support Windows SSPI (on a domain-member host) authenticating to a > PostgreSQL server using gssapi with spnego. > > We also support a PostgreSQL client on *nix authenticating using > gssapi with spnego to a PostgreSQL server that's requesting sspi mode. > > The relevant code is all a bit tangled, since there's support in there > for using Kerberos libraries on Windows instead of SSPI too. I doubt > anybody uses that last one, tests it, or cares about it, though, given > the painful hoop-jumping, registry key permission changes, etc > required to make it work. > > For bonus fun, RC4, DES, AES128 or AES256 are available/used for > Kerberos encryption on Windows. See > http://blogs.msdn.com/b/openspecification/archive/2011/05/31/windows-configurations-for-kerberos-supported-encryption-type.aspx > . Though given that Win7 defaults to AES256 it's probably reasonable > to simply not care about anything else. The short - and probably most important - answer is that no, I haven't tested it, and it would be difficult for me to do so quickly. In more depth: Looking at http://www.postgresql.org/docs/9.4/static/protocol-message-formats.html suggests that SSPI follows a separate codepath from the GSS code; certainly it's a different auth request, which means it shouldn't trigger the encryption path. There is no reason that using GSSAPI from, e.g., MIT Kerberos for Windows, should not work here. Even more broadly than that, GSSAPI is a RFC-standardized protocol with rigorously tested interop etc. etc., though whether anyone outside of MIT cares about MIT Kerberos for Windows I have no idea. As for encryption types, MIT out-of-the-box supports aes256 + aes128 (in several variants), rc4-hmac and friends, camellia in several variants, and des3-cbc-sha1. A much wider selection is available on setting the appropriately named "allow_weak_crypto" in krb5.conf, though I would hope that would not be needed. I think the important takeaway here is that I haven't actually changed how *authentication* works; these changes only affect GSSAPI post-authentication add encryption functions as defined by that specification. So if the authentication was working before, and the GSSAPI implementation is following RFC, I would hope that this would work still. Thanks, --Robbie
On 16 October 2015 at 01:07, Robbie Harwood <rharwood@redhat.com> wrote: > The short - and probably most important - answer is that no, I haven't > tested it, and it would be difficult for me to do so quickly. IIRC it's pretty easy to fire up AWS instances that're primary domain controllers, and then join a Pg box to them with Kerberos. I realise that's different to having the time and desire to do it, though. > Looking at > http://www.postgresql.org/docs/9.4/static/protocol-message-formats.html > suggests that SSPI follows a separate codepath from the GSS code; > certainly it's a different auth request, which means it shouldn't > trigger the encryption path. It's a different auth request, but the handling in be-auth.c is co-mingled to handle the cases: * We're compiled with Kerberos, handling SSPI from Windows * We're compiled with Kerberos, handling a GSSAPI request * We're compiled with SSPI, and we're using Windows SSPI to handle a GSSAPI auth request * We're compiled with SSPI, and we're using it to handle a SSP client request SSPI is a wrapper. For Windows Domain members it carries GSSAPI/Kerberos data with the spnego extension. (For local loopback it does NTLM, but you don't need to care about that). > There is no reason that using GSSAPI from, e.g., MIT Kerberos for > Windows, should not work here. Except that *nobody* does it. The EDB installer builds Pg with SSPI, and that's what the overwhelming majority of Windows users use. Personally I don't care if this patch doesn't add support for GSSAPI encryption for sspi connections, only that it doesn't break them. > Even more broadly than that, GSSAPI is a > RFC-standardized protocol with rigorously tested interop etc. etc., > though whether anyone outside of MIT cares about MIT Kerberos for > Windows I have no idea. It's maintained enough that AFAIK it works, and Pg supports compiling with it. No idea if anyone tests it with Pg anymore. > I think the important takeaway here is that I haven't actually changed > how *authentication* works; these changes only affect GSSAPI > post-authentication add encryption functions as defined by that > specification. So if the authentication was working before, and the > GSSAPI implementation is following RFC, I would hope that this would > work still. Hope so. I'll put this on my "hope to test it at some stage" list, but I don't have a prebuilt environment for it and have a lot to get ready for the next CF, so it'll be a while... -- Craig Ringer http://www.2ndQuadrant.com/PostgreSQL Development, 24x7 Support, Training & Services
* Craig Ringer (craig@2ndquadrant.com) wrote: > On 16 October 2015 at 01:07, Robbie Harwood <rharwood@redhat.com> wrote: > > Looking at > > http://www.postgresql.org/docs/9.4/static/protocol-message-formats.html > > suggests that SSPI follows a separate codepath from the GSS code; > > certainly it's a different auth request, which means it shouldn't > > trigger the encryption path. > > It's a different auth request, but the handling in be-auth.c is > co-mingled to handle the cases: be-auth.c? You mean src/backend/libpq/auth.c? > * We're compiled with Kerberos, handling SSPI from Windows > * We're compiled with Kerberos, handling a GSSAPI request Eh, *kerberos* was removed with 9.4.0. I'm guessing you mean GSSAPI above. > * We're compiled with SSPI, and we're using Windows SSPI to handle a > GSSAPI auth request > * We're compiled with SSPI, and we're using it to handle a SSP client request > > SSPI is a wrapper. For Windows Domain members it carries > GSSAPI/Kerberos data with the spnego extension. (For local loopback it > does NTLM, but you don't need to care about that). I have to admit that I'm not following what you're getting at here. src/backend/libpq/auth.c and src/interfaces/libpq/fe-auth.c have pretty clearly independent paths for SSPI and GSSAPI and which is used (server-side) is based on what the auth method in pg_hba.conf is. In a properly set up environment, the client could be using either, but it's not like we try to figure out (or care) which the client is using from the server's perspective. Now, if you set up GSSAPI on the client side and tell it to require encryption and you're using SSPI on the server side (or the other way around) it's not likely to work without changes to the SSPI code paths, which we should be looking at doing. I've not looked at the patch in much depth yet, but hopefully there's a straight-forward way to say "we're using SSPI on (client/server) and that doesn't support encryption yet, so don't try to require it" and throw an error if someone does. I don't think we necessairly have to have encryption with SSPI working initially (and that's assuming SSPI can actually do encryption the way GSSAPI can, I don't know that it can). > > There is no reason that using GSSAPI from, e.g., MIT Kerberos for > > Windows, should not work here. > > Except that *nobody* does it. The EDB installer builds Pg with SSPI, > and that's what the overwhelming majority of Windows users use. Apparently *somebody* uses MIT KfW; I doubt MIT would have put out KfW 4.0 if no one wanted it. I agree that it's clearly a small number of environments (I don't know of any currently) and it's entirely possible that none of them use PG with GSSAPI on Windows for authentication. > Personally I don't care if this patch doesn't add support for GSSAPI > encryption for sspi connections, only that it doesn't break them. Agreed and we should certainly test it, but I'm not seeing what you're getting at as the concern. > > Even more broadly than that, GSSAPI is a > > RFC-standardized protocol with rigorously tested interop etc. etc., > > though whether anyone outside of MIT cares about MIT Kerberos for > > Windows I have no idea. > > It's maintained enough that AFAIK it works, and Pg supports compiling > with it. No idea if anyone tests it with Pg anymore. The EDB installers were changed to use SSPI instead of MIT KfW (or perhaps just dropped building with GSSAPI, Dave would probably remember better than I do) because KfW wasn't being maintained or updated any more and the latest version had known security issues. That was quite a while ago and it looks like MIT has decided to revive KfW (note the drought between about 2007 and 2012 or so though..) with KfW 4.0. I doubt anyone's tried building or running PG with GSSAPI under Windows though. I agree it'd be good to test and see and perhaps even the EDB installers could be changed to add support for it back in (not sure if Dave really wants to though as it was a bit of a pain..). However, presumably MIT has updated KfW and contains to maintain it because someone is using it. Really, though, assuming that GSSAPI as provided by KfW works per the spec-defined API, I don't see why it wouldn't work under Windows just the same as it does under *nix, it's not like we have any Windows-specific code in backend/libpq/auth.c or interfaces/libpq/fe-auth.c for GSSAPI except a MingW-specific symbol definition. > > I think the important takeaway here is that I haven't actually changed > > how *authentication* works; these changes only affect GSSAPI > > post-authentication add encryption functions as defined by that > > specification. So if the authentication was working before, and the > > GSSAPI implementation is following RFC, I would hope that this would > > work still. > > Hope so. > > I'll put this on my "hope to test it at some stage" list, but I don't > have a prebuilt environment for it and have a lot to get ready for the > next CF, so it'll be a while... Ditto. Thanks! Stephen
On 16 October 2015 at 21:34, Stephen Frost <sfrost@snowman.net> wrote: > * Craig Ringer (craig@2ndquadrant.com) wrote: >> On 16 October 2015 at 01:07, Robbie Harwood <rharwood@redhat.com> wrote: >> > Looking at >> > http://www.postgresql.org/docs/9.4/static/protocol-message-formats.html >> > suggests that SSPI follows a separate codepath from the GSS code; >> > certainly it's a different auth request, which means it shouldn't >> > trigger the encryption path. >> >> It's a different auth request, but the handling in be-auth.c is >> co-mingled to handle the cases: > > be-auth.c? You mean src/backend/libpq/auth.c? Ahem. Yes. Also, I was actually thinking of the libpq front-end code, as it turns out. The backend's handling of each method is much better separated. >> * We're compiled with Kerberos, handling SSPI from Windows >> * We're compiled with Kerberos, handling a GSSAPI request > > Eh, *kerberos* was removed with 9.4.0. I'm guessing you mean GSSAPI > above. Yes, being sloppy. > src/backend/libpq/auth.c and src/interfaces/libpq/fe-auth.c have pretty > clearly independent paths for SSPI and GSSAPI fe-auth.c doesn't. See pg_fe_sendauth(...)'s case AUTH_REQ_GSS: and AUTH_REQ_SSPI: Not what I call clearly independent. I've had to deal with that tangle of joy a few times... > and which is used > (server-side) is based on what the auth method in pg_hba.conf is. Yep, agreed there. I was thinking that the backend overlapped more, like the frontend does. > In a > properly set up environment, the client could be using either, but it's > not like we try to figure out (or care) which the client is using from > the server's perspective. Exactly. I want to make sure we still don't have to care. > Now, if you set up GSSAPI on the client side and tell it to require > encryption and you're using SSPI on the server side (or the other way > around) it's not likely to work without changes to the SSPI code paths, > which we should be looking at doing. So long as setting up gssapi auth on the backend without requiring encryption still works with sspi clients, like it did before, I'm happy. My concern is avoiding a regression in what works, not requiring that the new functionality be supported everywhere. >> > There is no reason that using GSSAPI from, e.g., MIT Kerberos for >> > Windows, should not work here. >> >> Except that *nobody* does it. The EDB installer builds Pg with SSPI, >> and that's what the overwhelming majority of Windows users use. > > Apparently *somebody* uses MIT KfW Yeah, I meant nobody uses it with Pg on Windows. > I doubt anyone's tried building or running PG with GSSAPI under Windows > though. I agree it'd be good to test and see and perhaps even the EDB > installers could be changed to add support for it back in (not sure if > Dave really wants to though as it was a bit of a pain..). It's a lot of a pain, and not just because of maintenance. Kerberos on Windows needs access to various innards that you don't need when just using SSPI to delegate SSO to the OS. It's less secure, fiddly, and annoying. At least it was last time I had to deal with it, when I was working around some SSPI bugs in psqlODBC before landing up patching them in the driver instead. -- Craig Ringer http://www.2ndQuadrant.com/PostgreSQL Development, 24x7 Support, Training & Services
* Craig Ringer (craig@2ndquadrant.com) wrote: > On 16 October 2015 at 21:34, Stephen Frost <sfrost@snowman.net> wrote: > >> It's a different auth request, but the handling in be-auth.c is > >> co-mingled to handle the cases: > > > > be-auth.c? You mean src/backend/libpq/auth.c? > > Ahem. Yes. No worries. :) > Also, I was actually thinking of the libpq front-end code, as it turns > out. The backend's handling of each method is much better separated. Ok. > > src/backend/libpq/auth.c and src/interfaces/libpq/fe-auth.c have pretty > > clearly independent paths for SSPI and GSSAPI > > fe-auth.c doesn't. See pg_fe_sendauth(...)'s case AUTH_REQ_GSS: and > AUTH_REQ_SSPI: > > Not what I call clearly independent. I've had to deal with that tangle > of joy a few times... Hmm, yea, the actual choice of how to respond to the auth method the server wants us to use is kind of tricky, but once we've settled on one or the other, the rest of the code is pretty independent. Still, I agree with your point that we need to be careful to not break what we have there by, say, trying to move to encrypted GSSAPI with an SSPI server. I'm not sure that I want to really change that fe-auth.c code but I really think there may be a simpler way to have the same behavior. That mishmash of #ifdef's and swith/case statements strikes me as possibly being overly cute. Clear blocks of "we got this message, this is what we do when we're compiled with X, or Y, or X+Y" would probably be a lot easier to follow. > > In a > > properly set up environment, the client could be using either, but it's > > not like we try to figure out (or care) which the client is using from > > the server's perspective. > > Exactly. I want to make sure we still don't have to care. Agreed. > > Now, if you set up GSSAPI on the client side and tell it to require > > encryption and you're using SSPI on the server side (or the other way > > around) it's not likely to work without changes to the SSPI code paths, > > which we should be looking at doing. > > So long as setting up gssapi auth on the backend without requiring > encryption still works with sspi clients, like it did before, I'm > happy. My concern is avoiding a regression in what works, not > requiring that the new functionality be supported everywhere. Great, we're definitely agreed on that. > >> > There is no reason that using GSSAPI from, e.g., MIT Kerberos for > >> > Windows, should not work here. > >> > >> Except that *nobody* does it. The EDB installer builds Pg with SSPI, > >> and that's what the overwhelming majority of Windows users use. > > > > Apparently *somebody* uses MIT KfW > > Yeah, I meant nobody uses it with Pg on Windows. It'd be nice if it did, in case any of those users *do* end up wanting to use PG. > > I doubt anyone's tried building or running PG with GSSAPI under Windows > > though. I agree it'd be good to test and see and perhaps even the EDB > > installers could be changed to add support for it back in (not sure if > > Dave really wants to though as it was a bit of a pain..). > > It's a lot of a pain, and not just because of maintenance. Kerberos on > Windows needs access to various innards that you don't need when just > using SSPI to delegate SSO to the OS. It's less secure, fiddly, and > annoying. At least it was last time I had to deal with it, when I was > working around some SSPI bugs in psqlODBC before landing up patching > them in the driver instead. Yeah, I remember working with KfW back-in-the-day. I never played with the new 4.0 version, so perhaps it's better, but I'm not particularly anxious to get into that mess again.. As for this patch, the reason I've not been as involved (beyond being ridiculously busy) is that Michael's environment, which at least appears perfectly reasonable (and works with PG unpatched) isn't working. If we can get that working (and I've not looked at what's happening, so I have no idea how easy or hard that would be), then I'd be a lot more excited to spend time doing review of the patch. Thanks! Stephen
Stephen Frost <sfrost@snowman.net> writes: > As for this patch, the reason I've not been as involved (beyond being > ridiculously busy) is that Michael's environment, which at least appears > perfectly reasonable (and works with PG unpatched) isn't working. If we > can get that working (and I've not looked at what's happening, so I have > no idea how easy or hard that would be), then I'd be a lot more excited > to spend time doing review of the patch. I would also really like to see this fixed. Unfortunately, I haven't been able to replicate; I followed Michael's (very nicely written) writeup and didn't see the issues that Michael did. The only thing I know about the problem is that it logs: > psql: lost synchronization with server: got message type "S", length 22 which unfortunately could be a great many things. I've said this a couple times now, but I really do need more information - a traffic dump, a list of commands that were run, etc.; unfortunately, the surface here is pretty large, and while I totally am willing to believe there are bugs in the code I've written, I do not yet see them.
On Tue, Oct 20, 2015 at 3:01 AM, Robbie Harwood wrote: > Stephen Frost <sfrost@snowman.net> writes: >> psql: lost synchronization with server: got message type "S", length 22 > > which unfortunately could be a great many things. I've said this a > couple times now, but I really do need more information - a traffic > dump, a list of commands that were run, etc.; unfortunately, the surface > here is pretty large, and while I totally am willing to believe there > are bugs in the code I've written, I do not yet see them. --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -129,6 +129,58 @@ pqParseInput3(PGconn *conn) return; } +#ifdef ENABLE_GSS + /* We want to be ready in both IDLE and BUSY states for encryption */ + if (id == 'g' && !conn->gss_disable_enc && conn->gctx) + { + ssize_t encEnd, next; [...] + } + else if (!conn->gss_disable_enc && conn->gss_auth_done && + !conn->gss_decrypted_cur && id != 'E') + /* This could be a sync error, so let's handle it as such. */ + handleSyncLoss(conn, id, msgLength); +#endif Hm. The out-of-sync error I am seeing in my environment is caused by this block when parsing 'g' messages coming from the backend that are considered as being GSSAPI-encrypted messages. I am still looking at that... -- Michael
Robbie, On Wed, Oct 21, 2015 at 3:54 PM, Michael Paquier <michael.paquier@gmail.com> wrote: > On Tue, Oct 20, 2015 at 3:01 AM, Robbie Harwood wrote: >> Stephen Frost <sfrost@snowman.net> writes: >>> psql: lost synchronization with server: got message type "S", length 22 >> >> which unfortunately could be a great many things. I've said this a >> couple times now, but I really do need more information - a traffic >> dump, a list of commands that were run, etc.; unfortunately, the surface >> here is pretty large, and while I totally am willing to believe there >> are bugs in the code I've written, I do not yet see them. > > --- a/src/interfaces/libpq/fe-protocol3.c > +++ b/src/interfaces/libpq/fe-protocol3.c > @@ -129,6 +129,58 @@ pqParseInput3(PGconn *conn) > return; > } > > +#ifdef ENABLE_GSS > + /* We want to be ready in both IDLE and BUSY states > for encryption */ > + if (id == 'g' && !conn->gss_disable_enc && conn->gctx) > + { > + ssize_t encEnd, next; > [...] > + } > + else if (!conn->gss_disable_enc && conn->gss_auth_done && > + !conn->gss_decrypted_cur && id != 'E') > + /* This could be a sync error, so let's handle > it as such. */ > + handleSyncLoss(conn, id, msgLength); > +#endif > > Hm. The out-of-sync error I am seeing in my environment is caused by > this block when parsing 'g' messages coming from the backend that are > considered as being GSSAPI-encrypted messages. I am still looking at > that... @@ -604,6 +604,11 @@ pqPutMsgEnd(PGconn *conn) memcpy(conn->outBuffer + conn->outMsgStart, &msgLen, 4); } +#ifdef ENABLE_GSS + if (pggss_encrypt(conn) < 0) + return EOF; +#endif @@ -1528,10 +1541,20 @@ socket_putmessage(char msgtype, const char *s, size_t len) if (internal_putbytes(s, len)) goto fail; PqCommBusy = false; +#ifdef ENABLE_GSS + /* if we're GSSAPI encrypting, s was allocated in be_gss_encrypt */ + if (msgtype == 'g') + pfree((char *)s); +#endif Looking at this patch in more details... Why is it necessary to wrap all the encrypted messages into a dedicated message type 'g'? Cannot we rely on the context on client and backend that should be set up after authentication to perform the encryption and decryption operations? This patch is enforcing the message type in socket_putmessage for backend and pggss_encrypt/pqPutMsgEnd for frontend, this just feels wrong and I think that the patch could be really simplified, this includes the crafting added in fe-protocol3.c that should be IMO reserved only for messages received from the backend and should not be made aware of any protocol-level logic. -- Michael
Michael Paquier <michael.paquier@gmail.com> writes: > Robbie, > > +#ifdef ENABLE_GSS > + if (pggss_encrypt(conn) < 0) > + return EOF; > +#endif > > @@ -1528,10 +1541,20 @@ socket_putmessage(char msgtype, const char *s, > size_t len) > if (internal_putbytes(s, len)) > goto fail; > PqCommBusy = false; > +#ifdef ENABLE_GSS > + /* if we're GSSAPI encrypting, s was allocated in be_gss_encrypt */ > + if (msgtype == 'g') > + pfree((char *)s); > +#endif > > Looking at this patch in more details... Why is it necessary to wrap > all the encrypted messages into a dedicated message type 'g'? Cannot > we rely on the context on client and backend that should be set up > after authentication to perform the encryption and decryption > operations? This patch is enforcing the message type in > socket_putmessage for backend and pggss_encrypt/pqPutMsgEnd for > frontend, this just feels wrong and I think that the patch could be > really simplified, this includes the crafting added in fe-protocol3.c > that should be IMO reserved only for messages received from the > backend and should not be made aware of any protocol-level logic. Well, it's not strictly necessary in the general case, but it makes debugging a *lot* easier to have it present, and it simplifies some of the handling logic. For instance, in the part quoted above, with socket_putmessage() and socket_putmessage_noblock(), we need some way to tell whether a message blob has already been encrypted. Some enforcement of message type *will need to be carried out* anyway to avoid security issues with tampering on the wire, whether that be by sanity-checking the stated message type and then handling accordingly, or trying to decrypt and detonating the connection if it fails. GSSAPI does not define a wire protocol for the transport of messages the way, e.g., TLS does, so there must be some handling of incomplete messages, multiple messages at once, etc. There is already adequate handling of these present in postgres already, so I have structured the code to take advantage of it. That said, I could absolutely reimplement them - but it would not be simpler, I'm reasonably sure.
Michael Paquier <michael.paquier@gmail.com> writes: > On Tue, Oct 20, 2015 at 3:01 AM, Robbie Harwood wrote: >> Stephen Frost <sfrost@snowman.net> writes: >>> psql: lost synchronization with server: got message type "S", length 22 >> >> which unfortunately could be a great many things. I've said this a >> couple times now, but I really do need more information - a traffic >> dump, a list of commands that were run, etc.; unfortunately, the surface >> here is pretty large, and while I totally am willing to believe there >> are bugs in the code I've written, I do not yet see them. > > --- a/src/interfaces/libpq/fe-protocol3.c > +++ b/src/interfaces/libpq/fe-protocol3.c > @@ -129,6 +129,58 @@ pqParseInput3(PGconn *conn) > return; > } > > +#ifdef ENABLE_GSS > + /* We want to be ready in both IDLE and BUSY states > for encryption */ > + if (id == 'g' && !conn->gss_disable_enc && conn->gctx) > + { > + ssize_t encEnd, next; > [...] > + } > + else if (!conn->gss_disable_enc && conn->gss_auth_done && > + !conn->gss_decrypted_cur && id != 'E') > + /* This could be a sync error, so let's handle > it as such. */ > + handleSyncLoss(conn, id, msgLength); > +#endif > > Hm. The out-of-sync error I am seeing in my environment is caused by > this block when parsing 'g' messages coming from the backend that are > considered as being GSSAPI-encrypted messages. I am still looking at > that... If you're hitting the else-block, that suggests a GSSAPI context is not present at the time a GSSAPI message was received, I think.
On Thu, Oct 22, 2015 at 1:28 AM, Robbie Harwood <rharwood@redhat.com> wrote: > Michael Paquier <michael.paquier@gmail.com> writes: > >> Robbie, >> >> +#ifdef ENABLE_GSS >> + if (pggss_encrypt(conn) < 0) >> + return EOF; >> +#endif >> >> @@ -1528,10 +1541,20 @@ socket_putmessage(char msgtype, const char *s, >> size_t len) >> if (internal_putbytes(s, len)) >> goto fail; >> PqCommBusy = false; >> +#ifdef ENABLE_GSS >> + /* if we're GSSAPI encrypting, s was allocated in be_gss_encrypt */ >> + if (msgtype == 'g') >> + pfree((char *)s); >> +#endif >> >> Looking at this patch in more details... Why is it necessary to wrap >> all the encrypted messages into a dedicated message type 'g'? Cannot >> we rely on the context on client and backend that should be set up >> after authentication to perform the encryption and decryption >> operations? This patch is enforcing the message type in >> socket_putmessage for backend and pggss_encrypt/pqPutMsgEnd for >> frontend, this just feels wrong and I think that the patch could be >> really simplified, this includes the crafting added in fe-protocol3.c >> that should be IMO reserved only for messages received from the >> backend and should not be made aware of any protocol-level logic. > > Well, it's not strictly necessary in the general case, but it makes > debugging a *lot* easier to have it present, and it simplifies some of > the handling logic. For instance, in the part quoted above, with > socket_putmessage() and socket_putmessage_noblock(), we need some way to > tell whether a message blob has already been encrypted. > Some enforcement of message type *will need to be carried out* anyway to > avoid security issues with tampering on the wire, whether that be by > sanity-checking the stated message type and then handling accordingly, > or trying to decrypt and detonating the connection if it fails. > GSSAPI does not define a wire protocol for the transport of messages the > way, e.g., TLS does, so there must be some handling of incomplete > messages, multiple messages at once, etc. There is already adequate > handling of these present in postgres already, so I have structured the > code to take advantage of it. That said, I could absolutely reimplement > them - but it would not be simpler, I'm reasonably sure. Hm, and that's why you chose this way of going. My main concern about this patch is that it adds on top of the existing Postgres protocol a layer to encrypt and decrypt the messages between server and client based on GSSAPI. All messages transmitted between client and server are changed to 'g' messages on the fly and switched back to their original state at reception. This is symbolized by the four routines you added in the patch in this purpose, two for frontend and two for backend, each one for encryption and decryption. I may be wrong of course, but it seems to me that this approach will not survive committer-level screening because of the fact that context-level things invade higher level protocol messages. IMO, we would live better with something at a lower level, like in be-secure.c and fe-secure.c to exchange the encrypted and decrypted packages using the GSSAPI context created at authentication, that's where the context-level switches are living after all. So I am thinking that this patch needs a rework, and should be returned with feedback. Stephen, others, what do you think? -- Michael
On 2015-10-22 16:47:09 +0900, Michael Paquier wrote: > Hm, and that's why you chose this way of going. My main concern about > this patch is that it adds on top of the existing Postgres protocol a > layer to encrypt and decrypt the messages between server and client > based on GSSAPI. All messages transmitted between client and server > are changed to 'g' messages on the fly and switched back to their > original state at reception. This is symbolized by the four routines > you added in the patch in this purpose, two for frontend and two for > backend, each one for encryption and decryption. I may be wrong of > course, but it seems to me that this approach will not survive > committer-level screening because of the fact that context-level > things invade higher level protocol messages. Agreed. At least one committer here indeed thinks this approach is not acceptable (and I've said so upthread). Greetings, Andres Freund
On Thu, Oct 22, 2015 at 6:00 PM, Andres Freund <andres@anarazel.de> wrote: > On 2015-10-22 16:47:09 +0900, Michael Paquier wrote: >> Hm, and that's why you chose this way of going. My main concern about >> this patch is that it adds on top of the existing Postgres protocol a >> layer to encrypt and decrypt the messages between server and client >> based on GSSAPI. All messages transmitted between client and server >> are changed to 'g' messages on the fly and switched back to their >> original state at reception. This is symbolized by the four routines >> you added in the patch in this purpose, two for frontend and two for >> backend, each one for encryption and decryption. I may be wrong of >> course, but it seems to me that this approach will not survive >> committer-level screening because of the fact that context-level >> things invade higher level protocol messages. > > Agreed. At least one committer here indeed thinks this approach is not > acceptable (and I've said so upthread). OK, so marked as returned with feedback. -- Michael
Andres Freund <andres@anarazel.de> writes: > On 2015-10-22 16:47:09 +0900, Michael Paquier wrote: >> Hm, and that's why you chose this way of going. My main concern about >> this patch is that it adds on top of the existing Postgres protocol a >> layer to encrypt and decrypt the messages between server and client >> based on GSSAPI. All messages transmitted between client and server >> are changed to 'g' messages on the fly and switched back to their >> original state at reception. This is symbolized by the four routines >> you added in the patch in this purpose, two for frontend and two for >> backend, each one for encryption and decryption. I may be wrong of >> course, but it seems to me that this approach will not survive >> committer-level screening because of the fact that context-level >> things invade higher level protocol messages. > > Agreed. At least one committer here indeed thinks this approach is not > acceptable (and I've said so upthread). Okay, I'll make some changes. Before I do, though, since this is not the approach I came up with, can you explicitly state what you're looking for here? It subjectively seems that I'm getting a lot of feedback of "this feels wrong" without suggestion for improvement. To be clear, what I need to know is: - What changes do you want to see in the wire protocol? (And how will fallback be supported if that's affected?) - Since this seems to be an important sticking point, what files am I encouraged to change (or prohibited from changing)? (Fallback makes this complex.) - I've been assuming that we care about fallback, but I'd like to be told that it's something postgres actually wants tosee because it's the most intricate part of these changes. (I'm reasonably confident that the code becomes simpler withoutit, and I myself have no use for it.) If I understand what you're asking for (and the above is intended to be sure that I will), this will not be a trivial rework, so I want to be really sure before doing that because writing this code a third time is something I don't relish. Thanks, --Robbie
On Thu, Oct 22, 2015 at 11:36 PM, Robbie Harwood wrote: > To be clear, what I need to know is: > - What changes do you want to see in the wire protocol? (And how will > fallback be supported if that's affected?) Hm. Something essential will be to send the length of the wrapped gss_buffer_t object to be sent in the first 4 bytes of the message so as the receiver can know how much it has to unwrap and can perform sanity checks on what has been received. > - Since this seems to be an important sticking point, what files am I > encouraged to change (or prohibited from changing)? (Fallback makes > this complex.) If we want to make that stick into Postgres, I think that we are going to need be_gss_read and be_gss_write in be-secure.c, and pqgss_write and pqgss_read in fe-secure.c, the use the context initialized at authentication time to wrap and unwrap messages between the server and client. > - I've been assuming that we care about fallback, but I'd like to be > told that it's something postgres actually wants to see because it's > the most intricate part of these changes. (I'm reasonably confident > that the code becomes simpler without it, and I myself have no use for > it.) As a first shot for this patch, I would not mind if there is no fallback at protocol level, it seems to me that it is challenging enough to get a solid core feature first. Perhaps others have different opinions? > If I understand what you're asking for (and the above is intended to be > sure that I will), this will not be a trivial rework, so I want to be > really sure before doing that because writing this code a third time is > something I don't relish. This makes sense. Let's be sure that we come up with a clear picture of what to do first. -- Michael
On Tue, Sep 29, 2015 at 7:53 AM, Robbie Harwood <rharwood@redhat.com> wrote: > Robbie Harwood <rharwood@redhat.com> writes: > >>>>> Michael Paquier <michael.paquier@gmail.com> writes: >>>>> >>>>>> Well, the issue is still here: login through gssapi fails with >>>>>> your patch, not with HEAD. This patch is next on my review list by >>>>>> the way so I'll see what I can do about it soon even if I am in >>>>>> the US for Postgres Open next week. Still, how did you test it? I >>>>>> am just creating by myself a KDC, setting up a valid credential >>>>>> with kinit, and after setting up Postgres for this purpose the >>>>>> protocol communication just fails. >> >> I have no issues, no sync loss; nothing is amiss as far as I can see. >> If there is actually a problem here, I need more information from you. >> At the very least, as previously mentioned, I need to know what >> messages went over the wire to/from the server before it occurred, and >> what command (if it it made it to command processing) it was in the >> midst of sending. > > Any follow-up on this? I'd really like my code to be bug-free. I don't know if this is worth posting as the patch is currently returned with feedback and you are redoing it in a different way, but with your patch I get this error when connecting: lost synchronization with server: got message type "T", length 27 The connection to the server was lost. Attempting reset: Failed. I only get the error when connection to a patched server from a patched libpq. If either is unpatched, then there is no problem. Let me know if this is worth looking into. Cheers, Jeff
Jeff Janes <jeff.janes@gmail.com> writes: > On Tue, Sep 29, 2015 at 7:53 AM, Robbie Harwood <rharwood@redhat.com> wrote: >> Robbie Harwood <rharwood@redhat.com> writes: >> >>>>>> Michael Paquier <michael.paquier@gmail.com> writes: >>>>>> >>>>>>> Well, the issue is still here: login through gssapi fails with >>>>>>> your patch, not with HEAD. This patch is next on my review list by >>>>>>> the way so I'll see what I can do about it soon even if I am in >>>>>>> the US for Postgres Open next week. Still, how did you test it? I >>>>>>> am just creating by myself a KDC, setting up a valid credential >>>>>>> with kinit, and after setting up Postgres for this purpose the >>>>>>> protocol communication just fails. >>> >>> I have no issues, no sync loss; nothing is amiss as far as I can see. >>> If there is actually a problem here, I need more information from you. >>> At the very least, as previously mentioned, I need to know what >>> messages went over the wire to/from the server before it occurred, and >>> what command (if it it made it to command processing) it was in the >>> midst of sending. >> >> Any follow-up on this? I'd really like my code to be bug-free. > > I don't know if this is worth posting as the patch is currently > returned with feedback and you are redoing it in a different way, but > with your patch I get this error when connecting: > > lost synchronization with server: got message type "T", length 27 > The connection to the server was lost. Attempting reset: Failed. > > I only get the error when connection to a patched server from a > patched libpq. If either is unpatched, then there is no problem. > > Let me know if this is worth looking into. Definitely good to know, and I appreciate your testing. It's probably not worth looking into right now, but please do test the next version of the code as well. Thanks! --Robbie
Andreas, can you please weigh in here since your voice is important to this process? Robbie Harwood <rharwood@redhat.com> writes: > Andres Freund <andres@anarazel.de> writes: > >> On 2015-10-22 16:47:09 +0900, Michael Paquier wrote: >>> Hm, and that's why you chose this way of going. My main concern about >>> this patch is that it adds on top of the existing Postgres protocol a >>> layer to encrypt and decrypt the messages between server and client >>> based on GSSAPI. All messages transmitted between client and server >>> are changed to 'g' messages on the fly and switched back to their >>> original state at reception. This is symbolized by the four routines >>> you added in the patch in this purpose, two for frontend and two for >>> backend, each one for encryption and decryption. I may be wrong of >>> course, but it seems to me that this approach will not survive >>> committer-level screening because of the fact that context-level >>> things invade higher level protocol messages. >> >> Agreed. At least one committer here indeed thinks this approach is not >> acceptable (and I've said so upthread). > > Okay, I'll make some changes. Before I do, though, since this is not > the approach I came up with, can you explicitly state what you're > looking for here? It subjectively seems that I'm getting a lot of > feedback of "this feels wrong" without suggestion for improvement. > > To be clear, what I need to know is: > > - What changes do you want to see in the wire protocol? (And how will > fallback be supported if that's affected?) > > - Since this seems to be an important sticking point, what files am I > encouraged to change (or prohibited from changing)? (Fallback makes > this complex.) > > - I've been assuming that we care about fallback, but I'd like to be > told that it's something postgres actually wants to see because it's > the most intricate part of these changes. (I'm reasonably confident > that the code becomes simpler without it, and I myself have no use for > it.) > > If I understand what you're asking for (and the above is intended to be > sure that I will), this will not be a trivial rework, so I want to be > really sure before doing that because writing this code a third time is > something I don't relish. > > Thanks, > --Robbie
Hello friends, For your consideration, here is a new version of GSSAPI encryption support. For those who prefer, it's also available on my github: https://github.com/frozencemetery/postgres/commit/c92275b6605d7929cda5551de47a4c60aab7179e Some thoughts: - The overall design is different this time - GSS encryption sits in parallel construction to SSL encryption rather than at the protocol level - so a strict diff probably isn't useful. - The GSSAPI authentication code has been moved without modification. In doing so, the temptation to modify it (flags, error checking, that big comment at the top about things from Athena, etc.) is very large. I do not know whether these changes are best suited to another patch in this series or should be reviewed separately. I am also hesitant to add things beyond the core before I am told this is the right approach. - There's no fallback here. I wrote fallback support for versions 1-3, and the same design could apply here without too much trouble, but I am hesitant to port it over before the encryption design is approved. I strongly suspect you will not want to merge this without fallback support, and that makes sense to me. - The client and server code look a lot like each other. This resemblance is not exact, and my understanding is that server and client need to compile independently, so I do not know of a way to rectify this. Suggestions are welcome. Thanks! From c92275b6605d7929cda5551de47a4c60aab7179e Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Tue, 17 Nov 2015 18:34:14 -0500 Subject: [PATCH] Connect encryption support for GSSAPI Existing GSSAPI authentication code is extended to support connection encryption. Connection begins as soon as possible - that is, immediately after the client and server complete authentication. --- configure | 2 + configure.in | 1 + doc/src/sgml/client-auth.sgml | 2 +- doc/src/sgml/runtime.sgml | 20 +- src/Makefile.global.in | 1 + src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 330 +------------------- src/backend/libpq/be-gssapi.c | 584 ++++++++++++++++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 + src/backend/postmaster/postmaster.c | 12 + src/include/libpq/libpq-be.h | 31 ++ src/interfaces/libpq/Makefile | 4 + src/interfaces/libpq/fe-auth.c | 182 ----------- src/interfaces/libpq/fe-auth.h | 5 + src/interfaces/libpq/fe-connect.c | 10 + src/interfaces/libpq/fe-gssapi.c | 475 +++++++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-int.h | 16 +- 18 files changed, 1193 insertions(+), 518 deletions(-) create mode 100644 src/backend/libpq/be-gssapi.c create mode 100644 src/interfaces/libpq/fe-gssapi.c diff --git a/configure b/configure index 3dd1b15..7fd7610 100755 --- a/configure +++ b/configure @@ -712,6 +712,7 @@ with_uuid with_selinux with_openssl krb_srvtab +with_gssapi with_python with_perl with_tcl @@ -5488,6 +5489,7 @@ $as_echo "$with_gssapi" >&6; } + # # Kerberos configuration parameters # diff --git a/configure.in b/configure.in index 9398482..b19932e 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/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 3b2935c..7d37223 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -915,7 +915,7 @@ 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> or <acronym>GSSAPI</acronym> are used. </para> <para> diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index cda05f5..bd8156f 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1880,12 +1880,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 </para> <para> - To prevent spoofing on TCP connections, the best 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 - linkend="auth-pg-hba-conf">) and have SSL key and certificate files - (<xref linkend="ssl-tcp">). The TCP client must connect using + To prevent spoofing on TCP connections, the best solutions are either to + use GSSAPI for authentication and encryption or to use SSL certificates and + make sure that clients check the server's certificate. To secure using + SSL, the server must be configured to accept only <literal>hostssl</> + connections (<xref linkend="auth-pg-hba-conf">) and have SSL key and + certificate files (<xref linkend="ssl-tcp">). The TCP client must connect + using <literal>sslmode=verify-ca</> or <literal>verify-full</> and have the appropriate root certificate file installed (<xref linkend="libq-ssl-certificates">). @@ -2005,6 +2006,13 @@ 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> + Similarly, GSSAPI also encrypts all data sent across the network, + including passwords, queries, and data, as in + SSL. <filename>pg_hba.conf</> allows specification of GSSAPI + connections, which are always encrypted. + </para> </listitem> </varlistentry> diff --git a/src/Makefile.global.in b/src/Makefile.global.in index 51f4797..842a397 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_libxml = @with_libxml@ with_libxslt = @with_libxslt@ diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 09410c4..b80ae74 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.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..c0366fd 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -22,6 +22,7 @@ #include <unistd.h> #include "libpq/auth.h" +#include "libpq/libpq-be.h" #include "libpq/crypt.h" #include "libpq/ip.h" #include "libpq/libpq.h" @@ -36,7 +37,7 @@ * Global authentication functions *---------------------------------------------------------------- */ -static void sendAuthRequest(Port *port, AuthRequest areq); +void sendAuthRequest(Port *port, AuthRequest areq); static void auth_failed(Port *port, int status, char *logdetail); static char *recv_password_packet(Port *port); static int recv_and_check_password_packet(Port *port, char **logdetail); @@ -132,21 +133,6 @@ bool pg_krb_caseins_users; /*---------------------------------------------------------------- - * GSSAPI Authentication - *---------------------------------------------------------------- - */ -#ifdef ENABLE_GSS -#if defined(HAVE_GSSAPI_H) -#include <gssapi.h> -#else -#include <gssapi/gssapi.h> -#endif - -static int pg_GSS_recvauth(Port *port); -#endif /* ENABLE_GSS */ - - -/*---------------------------------------------------------------- * SSPI Authentication *---------------------------------------------------------------- */ @@ -167,22 +153,6 @@ static int pg_SSPI_recvauth(Port *port); static int CheckRADIUSAuth(Port *port); -/* - * Maximum accepted size of GSS and SSPI authentication tokens. - * - * Kerberos tickets are usually quite small, but the TGTs issued by Windows - * domain controllers include an authorization field known as the Privilege - * Attribute Certificate (PAC), which contains the user's Windows permissions - * (group memberships etc.). The PAC is copied into all tickets obtained on - * the basis of this TGT (even those issued by Unix realms which the Windows - * realm trusts), and can be several kB in size. The maximum token size - * accepted by Windows systems is determined by the MaxAuthToken Windows - * registry setting. Microsoft recommends that it is not set higher than - * 65535 bytes, so that seems like a reasonable limit for us as well. - */ -#define PG_MAX_AUTH_TOKEN_LENGTH 65535 - - /*---------------------------------------------------------------- * Global authentication functions *---------------------------------------------------------------- @@ -565,7 +535,7 @@ ClientAuthentication(Port *port) /* * Send an authentication request packet to the frontend. */ -static void +void sendAuthRequest(Port *port, AuthRequest areq) { StringInfoData buf; @@ -707,300 +677,6 @@ recv_and_check_password_packet(Port *port, char **logdetail) return result; } - - -/*---------------------------------------------------------------- - * GSSAPI authentication system - *---------------------------------------------------------------- - */ -#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) -{ - OM_uint32 maj_stat, - min_stat, - lmin_s, - gflags; - int mtype; - int ret; - StringInfoData buf; - gss_buffer_desc gbuf; - - /* - * GSS auth is not supported for protocol versions before 3, because it - * relies on the overall message length word to determine the GSS payload - * size in AuthenticationGSSContinue and PasswordMessage messages. (This - * is, in fact, a design error in our GSS support, because protocol - * messages are supposed to be parsable without relying on the length - * word; but it's not worth changing it now.) - */ - if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) - ereport(FATAL, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("GSSAPI is not supported in protocol version 2"))); - - if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0) - { - /* - * Set default Kerberos keytab file for the Krb5 mechanism. - * - * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv() - * not always available. - */ - if (getenv("KRB5_KTNAME") == NULL) - { - size_t kt_len = strlen(pg_krb_server_keyfile) + 14; - char *kt_path = malloc(kt_len); - - if (!kt_path || - snprintf(kt_path, kt_len, "KRB5_KTNAME=%s", - pg_krb_server_keyfile) != kt_len - 2 || - putenv(kt_path) != 0) - { - ereport(LOG, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("out of memory"))); - return STATUS_ERROR; - } - } - } - - /* - * We accept any service principal that's present in our keytab. This - * increases interoperability between kerberos implementations that see - * for example case sensitivity differently, while not really opening up - * any vector of attack. - */ - port->gss->cred = GSS_C_NO_CREDENTIAL; - - /* - * Initialize sequence with an empty context - */ - port->gss->ctx = GSS_C_NO_CONTEXT; - - /* - * Loop through GSSAPI message exchange. This exchange can consist of - * multiple messags sent in both directions. First message is always from - * the client. All messages from client to server are password packets - * (type 'p'). - */ - do - { - pq_startmsgread(); - - CHECK_FOR_INTERRUPTS(); - - mtype = pq_getbyte(); - if (mtype != 'p') - { - /* Only log error if client didn't disconnect. */ - if (mtype != EOF) - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("expected GSS response, got message type %d", - mtype))); - return STATUS_ERROR; - } - - /* Get the actual GSS token */ - initStringInfo(&buf); - if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH)) - { - /* EOF - pq_getmessage already logged error */ - pfree(buf.data); - return STATUS_ERROR; - } - - /* Map to GSSAPI style buffer */ - gbuf.length = buf.len; - gbuf.value = buf.data; - - elog(DEBUG4, "Processing received GSS token of length %u", - (unsigned int) gbuf.length); - - maj_stat = gss_accept_sec_context( - &min_stat, - &port->gss->ctx, - port->gss->cred, - &gbuf, - GSS_C_NO_CHANNEL_BINDINGS, - &port->gss->name, - NULL, - &port->gss->outbuf, - &gflags, - NULL, - NULL); - - /* gbuf no longer used */ - pfree(buf.data); - - elog(DEBUG5, "gss_accept_sec_context major: %d, " - "minor: %d, outlen: %u, outflags: %x", - maj_stat, min_stat, - (unsigned int) port->gss->outbuf.length, gflags); - - CHECK_FOR_INTERRUPTS(); - - if (port->gss->outbuf.length != 0) - { - /* - * Negotiation generated data to be sent to the client. - */ - elog(DEBUG4, "sending GSS response token of length %u", - (unsigned int) port->gss->outbuf.length); - - sendAuthRequest(port, AUTH_REQ_GSS_CONT); - - gss_release_buffer(&lmin_s, &port->gss->outbuf); - } - - if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) - { - gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER); - pg_GSS_error(ERROR, - gettext_noop("accepting GSS security context failed"), - maj_stat, min_stat); - } - - if (maj_stat == GSS_S_CONTINUE_NEEDED) - elog(DEBUG4, "GSS continue needed"); - - } while (maj_stat == GSS_S_CONTINUE_NEEDED); - - if (port->gss->cred != GSS_C_NO_CREDENTIAL) - { - /* - * Release service principal credentials - */ - gss_release_cred(&min_stat, &port->gss->cred); - } - - /* - * GSS_S_COMPLETE indicates that authentication is now complete. - * - * Get the name of the user that authenticated, and compare it to the pg - * username that was specified for the connection. - */ - maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL); - if (maj_stat != GSS_S_COMPLETE) - pg_GSS_error(ERROR, - gettext_noop("retrieving GSS user name failed"), - maj_stat, min_stat); - - /* - * Split the username at the realm separator - */ - if (strchr(gbuf.value, '@')) - { - char *cp = strchr(gbuf.value, '@'); - - /* - * If we are not going to include the realm in the username that is - * passed to the ident map, destructively modify it here to remove the - * realm. Then advance past the separator to check the realm. - */ - if (!port->hba->include_realm) - *cp = '\0'; - cp++; - - if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm)) - { - /* - * Match the realm part of the name first - */ - if (pg_krb_caseins_users) - ret = pg_strcasecmp(port->hba->krb_realm, cp); - else - ret = strcmp(port->hba->krb_realm, cp); - - if (ret) - { - /* GSS realm does not match */ - elog(DEBUG2, - "GSSAPI realm (%s) and configured realm (%s) don't match", - cp, port->hba->krb_realm); - gss_release_buffer(&lmin_s, &gbuf); - return STATUS_ERROR; - } - } - } - else if (port->hba->krb_realm && strlen(port->hba->krb_realm)) - { - elog(DEBUG2, - "GSSAPI did not return realm but realm matching was requested"); - - gss_release_buffer(&lmin_s, &gbuf); - return STATUS_ERROR; - } - - ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, - pg_krb_caseins_users); - - gss_release_buffer(&lmin_s, &gbuf); - - return ret; -} -#endif /* ENABLE_GSS */ - - /*---------------------------------------------------------------- * SSPI authentication system *---------------------------------------------------------------- diff --git a/src/backend/libpq/be-gssapi.c b/src/backend/libpq/be-gssapi.c new file mode 100644 index 0000000..04d8a98 --- /dev/null +++ b/src/backend/libpq/be-gssapi.c @@ -0,0 +1,584 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi.c + * GSSAPI authentication and encryption support + * + * Portions Copyright (c) 2015-2016, Red Hat, Inc. + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/libpq/auth.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.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 + +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))); +} + +int +pg_GSS_recvauth(Port *port) +{ + OM_uint32 maj_stat, + min_stat, + lmin_s, + gflags; + int mtype; + int ret; + StringInfoData buf; + gss_buffer_desc gbuf; + + /* + * GSS auth is not supported for protocol versions before 3, because it + * relies on the overall message length word to determine the GSS payload + * size in AuthenticationGSSContinue and PasswordMessage messages. (This + * is, in fact, a design error in our GSS support, because protocol + * messages are supposed to be parsable without relying on the length + * word; but it's not worth changing it now.) + */ + if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) + ereport(FATAL, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("GSSAPI is not supported in protocol version 2"))); + + if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0) + { + /* + * Set default Kerberos keytab file for the Krb5 mechanism. + * + * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv() + * not always available. + */ + if (getenv("KRB5_KTNAME") == NULL) + { + size_t kt_len = strlen(pg_krb_server_keyfile) + 14; + char *kt_path = malloc(kt_len); + + if (!kt_path || + snprintf(kt_path, kt_len, "KRB5_KTNAME=%s", + pg_krb_server_keyfile) != kt_len - 2 || + putenv(kt_path) != 0) + { + ereport(LOG, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + return STATUS_ERROR; + } + } + } + + /* + * We accept any service principal that's present in our keytab. This + * increases interoperability between kerberos implementations that see + * for example case sensitivity differently, while not really opening up + * any vector of attack. + */ + port->gss->cred = GSS_C_NO_CREDENTIAL; + + /* + * Initialize sequence with an empty context + */ + port->gss->ctx = GSS_C_NO_CONTEXT; + + /* + * Loop through GSSAPI message exchange. This exchange can consist of + * multiple messags sent in both directions. First message is always from + * the client. All messages from client to server are password packets + * (type 'p'). + */ + do + { + pq_startmsgread(); + + CHECK_FOR_INTERRUPTS(); + + mtype = pq_getbyte(); + if (mtype != 'p') + { + /* Only log error if client didn't disconnect. */ + if (mtype != EOF) + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("expected GSS response, got message type %d", + mtype))); + return STATUS_ERROR; + } + + /* Get the actual GSS token */ + initStringInfo(&buf); + if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH)) + { + /* EOF - pq_getmessage already logged error */ + pfree(buf.data); + return STATUS_ERROR; + } + + /* Map to GSSAPI style buffer */ + gbuf.length = buf.len; + gbuf.value = buf.data; + + elog(DEBUG4, "Processing received GSS token of length %u", + (unsigned int) gbuf.length); + + maj_stat = gss_accept_sec_context( + &min_stat, + &port->gss->ctx, + port->gss->cred, + &gbuf, + GSS_C_NO_CHANNEL_BINDINGS, + &port->gss->name, + NULL, + &port->gss->outbuf, + &gflags, + NULL, + NULL); + + /* gbuf no longer used */ + pfree(buf.data); + + elog(DEBUG5, "gss_accept_sec_context major: %d, " + "minor: %d, outlen: %u, outflags: %x", + maj_stat, min_stat, + (unsigned int) port->gss->outbuf.length, gflags); + + CHECK_FOR_INTERRUPTS(); + + if (port->gss->outbuf.length != 0) + { + /* + * Negotiation generated data to be sent to the client. + */ + elog(DEBUG4, "sending GSS response token of length %u", + (unsigned int) port->gss->outbuf.length); + + sendAuthRequest(port, AUTH_REQ_GSS_CONT); + + gss_release_buffer(&lmin_s, &port->gss->outbuf); + } + + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) + { + gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER); + pg_GSS_error(ERROR, + gettext_noop("accepting GSS security context failed"), + maj_stat, min_stat); + } + + if (maj_stat == GSS_S_CONTINUE_NEEDED) + elog(DEBUG4, "GSS continue needed"); + + } while (maj_stat == GSS_S_CONTINUE_NEEDED); + + if (port->gss->cred != GSS_C_NO_CREDENTIAL) + { + /* + * Release service principal credentials + */ + gss_release_cred(&min_stat, &port->gss->cred); + } + + /* + * GSS_S_COMPLETE indicates that authentication is now complete. + * + * Get the name of the user that authenticated, and compare it to the pg + * username that was specified for the connection. + */ + maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL); + if (maj_stat != GSS_S_COMPLETE) + pg_GSS_error(ERROR, + gettext_noop("retrieving GSS user name failed"), + maj_stat, min_stat); + + /* + * Split the username at the realm separator + */ + if (strchr(gbuf.value, '@')) + { + char *cp = strchr(gbuf.value, '@'); + + /* + * If we are not going to include the realm in the username that is + * passed to the ident map, destructively modify it here to remove the + * realm. Then advance past the separator to check the realm. + */ + if (!port->hba->include_realm) + *cp = '\0'; + cp++; + + if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm)) + { + /* + * Match the realm part of the name first + */ + if (pg_krb_caseins_users) + ret = pg_strcasecmp(port->hba->krb_realm, cp); + else + ret = strcmp(port->hba->krb_realm, cp); + + if (ret) + { + /* GSS realm does not match */ + elog(DEBUG2, + "GSSAPI realm (%s) and configured realm (%s) don't match", + cp, port->hba->krb_realm); + gss_release_buffer(&lmin_s, &gbuf); + return STATUS_ERROR; + } + } + } + else if (port->hba->krb_realm && strlen(port->hba->krb_realm)) + { + elog(DEBUG2, + "GSSAPI did not return realm but realm matching was requested"); + + gss_release_buffer(&lmin_s, &gbuf); + return STATUS_ERROR; + } + + ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, + pg_krb_caseins_users); + + gss_release_buffer(&lmin_s, &gbuf); + + return ret; +} + +static ssize_t +be_gssapi_should_crypto(Port *port) +{ + OM_uint32 major, minor; + int open = 1; + + if (port->gss->ctx == GSS_C_NO_CONTEXT) + return 0; + else if (port->gss->should_encrypt) + return 1; + + major = gss_inquire_context(&minor, port->gss->ctx, + NULL, NULL, NULL, NULL, NULL, NULL, + &open); + if (major == GSS_S_NO_CONTEXT) + { + /* + * In MIT krb5 < 1.14, it was not possible to call gss_inquire_context + * on an incomplete context. This was a violation of rfc2744 and has + * been corrected in https://github.com/krb5/krb5/pull/285 + */ + return 0; + } + else if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, + gettext_noop("GSSAPI context state error"), + major, minor); + return -1; + } + else if (open != 0) + { + /* + * Though we can start encrypting here, our client is not ready since + * it has not received the final auth packet. Set encryption on for + * the next packet, but send this one in the clear. + */ + port->gss->should_encrypt = true; + } + return 0; +} + +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/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 9aaed5b..1d827ce 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -2349,6 +2349,10 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); +#endif #endif return port; @@ -2365,7 +2369,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/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 5d07b78..b0a31ae 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,9 @@ 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 */ + bool should_encrypt; /* GSSAPI encryption start */ + StringInfoData writebuf; /* GSSAPI nonblocking write buffering */ #endif } pg_gssinfo; #endif @@ -214,6 +218,33 @@ 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 +int pg_GSS_recvauth(Port *port); +ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); +ssize_t be_gssapi_write(Port *port, void *ptr, size_t len); + +/* GUC */ +extern char *pg_krb_server_keyfile; +extern bool pg_krb_caseins_users; + +/* + * Maximum accepted size of GSS and SSPI authentication tokens. + * + * Kerberos tickets are usually quite small, but the TGTs issued by Windows + * domain controllers include an authorization field known as the Privilege + * Attribute Certificate (PAC), which contains the user's Windows permissions + * (group memberships etc.). The PAC is copied into all tickets obtained on + * the basis of this TGT (even those issued by Unix realms which the Windows + * realm trusts), and can be several kB in size. The maximum token size + * accepted by Windows systems is determined by the MaxAuthToken Windows + * registry setting. Microsoft recommends that it is not set higher than + * 65535 bytes, so that seems like a reasonable limit for us as well. + */ +#define PG_MAX_AUTH_TOKEN_LENGTH 65535 +#endif + +extern void sendAuthRequest(Port *port, AuthRequest areq); + extern ProtocolVersion FrontendProtocol; /* TCP keepalives configuration. These are no-ops on an AF_UNIX socket. */ diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index 1b292d2..4b206b4 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.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..c50f6dd 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -42,188 +42,6 @@ #include "fe-auth.h" #include "libpq/md5.h" - -#ifdef ENABLE_GSS -/* - * 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); -} - -/* - * Continue GSS authentication with next token as needed. - */ -static int -pg_GSS_continue(PGconn *conn) -{ - OM_uint32 maj_stat, - min_stat, - lmin_s; - - 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, - 0, - GSS_C_NO_CHANNEL_BINDINGS, - (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf, - NULL, - &conn->goutbuf, - NULL, - NULL); - - if (conn->gctx != GSS_C_NO_CONTEXT) - { - free(conn->ginbuf.value); - conn->ginbuf.value = NULL; - conn->ginbuf.length = 0; - } - - if (conn->goutbuf.length != 0) - { - /* - * GSS generated data to send to the server. We don't care if it's the - * first or subsequent packet, just send the same kind of password - * packet. - */ - if (pqPacketSend(conn, 'p', - conn->goutbuf.value, conn->goutbuf.length) - != STATUS_OK) - { - gss_release_buffer(&lmin_s, &conn->goutbuf); - return STATUS_ERROR; - } - } - gss_release_buffer(&lmin_s, &conn->goutbuf); - - if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) - { - pg_GSS_error(libpq_gettext("GSSAPI continuation error"), - conn, - maj_stat, min_stat); - gss_release_name(&lmin_s, &conn->gtarg_nam); - if (conn->gctx) - gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER); - return STATUS_ERROR; - } - - if (maj_stat == GSS_S_COMPLETE) - gss_release_name(&lmin_s, &conn->gtarg_nam); - - return STATUS_OK; -} - -/* - * Send initial GSS authentication token - */ -static int -pg_GSS_startup(PGconn *conn) -{ - OM_uint32 maj_stat, - min_stat; - int maxlen; - gss_buffer_desc temp_gbuf; - - if (!(conn->pghost && conn->pghost[0] != '\0')) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("host name must be specified\n")); - return STATUS_ERROR; - } - - if (conn->gctx) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("duplicate GSS authentication request\n")); - return STATUS_ERROR; - } - - /* - * Import service principal name so the proper ticket can be acquired by - * the GSSAPI system. - */ - maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; - temp_gbuf.value = (char *) malloc(maxlen); - if (!temp_gbuf.value) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory\n")); - return STATUS_ERROR; - } - snprintf(temp_gbuf.value, maxlen, "%s@%s", - conn->krbsrvname, conn->pghost); - temp_gbuf.length = strlen(temp_gbuf.value); - - maj_stat = gss_import_name(&min_stat, &temp_gbuf, - GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); - free(temp_gbuf.value); - - if (maj_stat != GSS_S_COMPLETE) - { - pg_GSS_error(libpq_gettext("GSSAPI name import error"), - conn, - maj_stat, min_stat); - return STATUS_ERROR; - } - - /* - * Initial packet is the same as a continuation packet with no initial - * context. - */ - conn->gctx = GSS_C_NO_CONTEXT; - - return pg_GSS_continue(conn); -} -#endif /* ENABLE_GSS */ - - #ifdef ENABLE_SSPI /* * SSPI authentication system (Windows only) diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h index 9d11654..410c731 100644 --- a/src/interfaces/libpq/fe-auth.h +++ b/src/interfaces/libpq/fe-auth.h @@ -21,4 +21,9 @@ extern int pg_fe_sendauth(AuthRequest areq, PGconn *conn); extern char *pg_fe_getauthname(PQExpBuffer errorMessage); +#ifdef ENABLE_GSS +int pg_GSS_continue(PGconn *conn); +int pg_GSS_startup(PGconn *conn); +#endif + #endif /* FE_AUTH_H */ diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5ad4755..aa2340a 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -2798,6 +2798,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 +2918,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 +3027,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.c b/src/interfaces/libpq/fe-gssapi.c new file mode 100644 index 0000000..3427eb6 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi.c @@ -0,0 +1,475 @@ +/*------------------------------------------------------------------------- + * + * fe-auth.c + * The front-end (client) support for GSSAPI + * + * Portions Copyright (c) 2015-2016, Red Hat, Inc. + * 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.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "libpq-fe.h" +#include "libpq-int.h" +#include "fe-auth.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. + */ +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); +} + +/* + * Continue GSS authentication with next token as needed. + */ +int +pg_GSS_continue(PGconn *conn) +{ + OM_uint32 maj_stat, + min_stat, + lmin_s; + + 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, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf, + NULL, + &conn->goutbuf, + NULL, + NULL); + + if (conn->gctx != GSS_C_NO_CONTEXT) + { + free(conn->ginbuf.value); + conn->ginbuf.value = NULL; + conn->ginbuf.length = 0; + } + + if (conn->goutbuf.length != 0) + { + /* + * GSS generated data to send to the server. We don't care if it's the + * first or subsequent packet, just send the same kind of password + * packet. + */ + if (pqPacketSend(conn, 'p', + conn->goutbuf.value, conn->goutbuf.length) + != STATUS_OK) + { + gss_release_buffer(&lmin_s, &conn->goutbuf); + return STATUS_ERROR; + } + } + gss_release_buffer(&lmin_s, &conn->goutbuf); + + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) + { + pg_GSS_error(libpq_gettext("GSSAPI continuation error"), + conn, + maj_stat, min_stat); + gss_release_name(&lmin_s, &conn->gtarg_nam); + if (conn->gctx) + gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER); + return STATUS_ERROR; + } + + if (maj_stat == GSS_S_COMPLETE) + gss_release_name(&lmin_s, &conn->gtarg_nam); + + return STATUS_OK; +} + +/* + * Send initial GSS authentication token + */ +int +pg_GSS_startup(PGconn *conn) +{ + OM_uint32 maj_stat, + min_stat; + int maxlen; + gss_buffer_desc temp_gbuf; + + if (!(conn->pghost && conn->pghost[0] != '\0')) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("host name must be specified\n")); + return STATUS_ERROR; + } + + if (conn->gctx) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("duplicate GSS authentication request\n")); + return STATUS_ERROR; + } + + /* + * Import service principal name so the proper ticket can be acquired by + * the GSSAPI system. + */ + maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; + temp_gbuf.value = (char *) malloc(maxlen); + if (!temp_gbuf.value) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return STATUS_ERROR; + } + snprintf(temp_gbuf.value, maxlen, "%s@%s", + conn->krbsrvname, conn->pghost); + temp_gbuf.length = strlen(temp_gbuf.value); + + maj_stat = gss_import_name(&min_stat, &temp_gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); + free(temp_gbuf.value); + + if (maj_stat != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI name import error"), + conn, + maj_stat, min_stat); + return STATUS_ERROR; + } + + /* + * Initial packet is the same as a continuation packet with no initial + * context. + */ + conn->gctx = GSS_C_NO_CONTEXT; + + return pg_GSS_continue(conn); +} + +/* + * Only consider encryption when GSS context is complete + */ +static ssize_t +pg_GSS_should_crypto(PGconn *conn) +{ + OM_uint32 major, minor; + int open = 1; + + if (conn->gctx == GSS_C_NO_CONTEXT) + return 0; + else if (conn->gencrypt) + return 1; + + major = gss_inquire_context(&minor, conn->gctx, + NULL, NULL, NULL, NULL, NULL, NULL, + &open); + if (major == GSS_S_NO_CONTEXT) + { + /* + * In MIT krb5 < 1.14, it was not possible to call gss_inquire_context + * on an incomplete context. This was a violation of rfc2744 and has + * been corrected in https://github.com/krb5/krb5/pull/285 + */ + return 0; + } + else if (GSS_ERROR(major)) + { + pg_GSS_error(libpq_gettext("GSSAPI context state error"), conn, + major, minor); + return -1; + } + else if (open != 0) + { + conn->gencrypt = true; + return 1; + } + return 0; +} + +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..6b75d104 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,11 @@ 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 */ + bool gencrypt; /* GSS is ready for encryption */ + PQExpBufferData gwritebuf; /* GSS nonblocking write buffering */ + size_t gwritecurs; /* GSS write buffer position */ #endif #ifdef ENABLE_SSPI @@ -620,7 +626,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 +648,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-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
Attachment
On Thu, Feb 11, 2016 at 6:06 AM, Robbie Harwood <rharwood@redhat.com> wrote: > For your consideration, here is a new version of GSSAPI encryption > support. For those who prefer, it's also available on my github: > https://github.com/frozencemetery/postgres/commit/c92275b6605d7929cda5551de47a4c60aab7179e Yeah! Glad to see you back. > Some thoughts: > > - The overall design is different this time - GSS encryption sits in > parallel construction to SSL encryption rather than at the protocol > level - so a strict diff probably isn't useful. > > - The GSSAPI authentication code has been moved without modification. > In doing so, the temptation to modify it (flags, error checking, that > big comment at the top about things from Athena, etc.) is very large. > I do not know whether these changes are best suited to another patch > in this series or should be reviewed separately. I am also hesitant > to add things beyond the core before I am told this is the right > approach. I would recommend a different patch if code needs to be moved around. The move may make sense taken as an independent piece of the integration. > - There's no fallback here. I wrote fallback support for versions 1-3, > and the same design could apply here without too much trouble, but I > am hesitant to port it over before the encryption design is approved. > I strongly suspect you will not want to merge this without fallback > support, and that makes sense to me. > > - The client and server code look a lot like each other. This > resemblance is not exact, and my understanding is that server and > client need to compile independently, so I do not know of a way to > rectify this. Suggestions are welcome. At quick glance, I like the direction this is taking. You moved all the communication protocol at a lower level where SSL and secure reads are located, so this results in a neat integration. + * Portions Copyright (c) 2015-2016, Red Hat, Inc. + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group I think that this part may be a problem... Not sure the feeling of the others regarding additional copyright notices. It would be good to add that to the next CF, I will be happy to get a look at it. -- Michael
On Thu, Feb 11, 2016 at 8:42 AM, Michael Paquier <michael.paquier@gmail.com> wrote: > + * Portions Copyright (c) 2015-2016, Red Hat, Inc. > + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group > I think that this part may be a problem... Not sure the feeling of the > others regarding additional copyright notices. Yep. "PostgreSQL Global Development Group" means "whoever it was that contributed", not a specific legal organization. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
Michael Paquier <michael.paquier@gmail.com> writes: > On Thu, Feb 11, 2016 at 6:06 AM, Robbie Harwood <rharwood@redhat.com> wrote: >> >> - The GSSAPI authentication code has been moved without modification. >> In doing so, the temptation to modify it (flags, error checking, that >> big comment at the top about things from Athena, etc.) is very large. >> I do not know whether these changes are best suited to another patch >> in this series or should be reviewed separately. I am also hesitant >> to add things beyond the core before I am told this is the right >> approach. > > I would recommend a different patch if code needs to be moved around. > The move may make sense taken as an independent piece of the > integration. Sorry, are you suggesting separate patch for moving the GSS auth code, or separate patch for changes to said code? I am happy to move it if so, just want to be sure. > + * Portions Copyright (c) 2015-2016, Red Hat, Inc. > + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group > I think that this part may be a problem... Not sure the feeling of the > others regarding additional copyright notices. Good catch. That's an accident (force of habit). Since I'm pretty sure this version won't be merged anyway, I'll drop it from the next one. > It would be good to add that to the next CF, I will be happy to get a > look at it. Sounds good. Thanks for looking at it!
On Fri, Feb 12, 2016 at 3:56 AM, Robbie Harwood <rharwood@redhat.com> wrote: > Michael Paquier <michael.paquier@gmail.com> writes: >> On Thu, Feb 11, 2016 at 6:06 AM, Robbie Harwood <rharwood@redhat.com> wrote: >>> - The GSSAPI authentication code has been moved without modification. >>> In doing so, the temptation to modify it (flags, error checking, that >>> big comment at the top about things from Athena, etc.) is very large. >>> I do not know whether these changes are best suited to another patch >>> in this series or should be reviewed separately. I am also hesitant >>> to add things beyond the core before I am told this is the right >>> approach. >> >> I would recommend a different patch if code needs to be moved around. >> The move may make sense taken as an independent piece of the >> integration. > > Sorry, are you suggesting separate patch for moving the GSS auth code, > or separate patch for changes to said code? I am happy to move it if > so, just want to be sure. This is based on my first impressions on the patch. Let's discuss more those points once I got a more in-depth look at the patch with what it actually does. In short, there is no need to put more efforts in the coding now :) Sorry to confuse you. >> + * Portions Copyright (c) 2015-2016, Red Hat, Inc. >> + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group >> I think that this part may be a problem... Not sure the feeling of the >> others regarding additional copyright notices. > > Good catch. That's an accident (force of habit). Since I'm pretty sure > this version won't be merged anyway, I'll drop it from the next one. > >> It would be good to add that to the next CF, I will be happy to get a >> look at it. > > Sounds good. Thanks for looking at it! Okay, let's do this. -- Michael
Hi Robbie, On 2/10/16 4:06 PM, Robbie Harwood wrote: > Hello friends, > > For your consideration, here is a new version of GSSAPI encryption > support. For those who prefer, it's also available on my github: > https://github.com/frozencemetery/postgres/commit/c92275b6605d7929cda5551de47a4c60aab7179e It tried out this patch and ran into a few problems: 1) It didn't apply cleanly to HEAD. It did apply cleanly on a455878 which I figured was recent enough for testing. I didn't bisect to find the exact commit that broke it. 2) While I was able to apply the patch and get it compiled it seemed pretty flaky - I was only able to logon about 1 in 10 times on average. Here was my testing methodology: a) Build Postgres from a455878 (without your patch), install/configure Kerberos and get everything working. I was able the set the auth method to gss in pg_hba.conf and logon successfully every time. b) On the same system rebuild Postgres from a455878 including your patch and attempt authentication. The problems arose after step 2b. Sometimes I would try to logon twenty times without success and sometimes it only take five or six attempts. I was never able to logon successfully twice in a row. When not successful the client always output this incomplete message (without terminating LF): psql: expected authentication request from server, but received From the logs I can see the server is reporting EOF from the client, though the client does not core dump and prints the above message before exiting. I have attached files that contain server logs at DEBUG5 and tcpdump output for both the success and failure cases. Please let me know if there's any more information you would like me to provide. -- -David david@pgmasters.net
Attachment
David Steele <david@pgmasters.net> writes: > Hi Robbie, > > On 2/10/16 4:06 PM, Robbie Harwood wrote: >> Hello friends, >> >> For your consideration, here is a new version of GSSAPI encryption >> support. For those who prefer, it's also available on my github: >> https://github.com/frozencemetery/postgres/commit/c92275b6605d7929cda5551de47a4c60aab7179e > > It tried out this patch and ran into a few problems: > > 1) It didn't apply cleanly to HEAD. It did apply cleanly on a455878 > which I figured was recent enough for testing. I didn't bisect to find > the exact commit that broke it. It applied to head of master (57c932475504d63d8f8a68fc6925d7decabc378a) for me (`patch -p1 < v4-GSSAPI-encryption-support.patch`). I rebased it anyway and cut a v5 anyway, just to be sure. It's attached, and available on github as well: https://github.com/frozencemetery/postgres/commit/dc10e3519f0f6c67f79abd157dc8ff1a1c293f53 > 2) While I was able to apply the patch and get it compiled it seemed > pretty flaky - I was only able to logon about 1 in 10 times on average. > Here was my testing methodology: > > a) Build Postgres from a455878 (without your patch), install/configure > Kerberos and get everything working. I was able the set the auth method > to gss in pg_hba.conf and logon successfully every time. > > b) On the same system rebuild Postgres from a455878 including your patch > and attempt authentication. > > The problems arose after step 2b. Sometimes I would try to logon twenty > times without success and sometimes it only take five or six attempts. > I was never able to logon successfully twice in a row. > > When not successful the client always output this incomplete message > (without terminating LF): > > psql: expected authentication request from server, but received > > From the logs I can see the server is reporting EOF from the client, > though the client does not core dump and prints the above message before > exiting. > > I have attached files that contain server logs at DEBUG5 and tcpdump > output for both the success and failure cases. > > Please let me know if there's any more information you would like me to > provide. What I can't tell from looking at your methodology is whether both the client and server were running my patches or no. There's no fallback here (I'd like to talk about how that should work, with example from v1-v3, if people have ideas). This means that both the client and the server need to be running my patches for the moment. Is this your setup? Thanks for taking it for a spin! --Robbie From dc10e3519f0f6c67f79abd157dc8ff1a1c293f53 Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Tue, 17 Nov 2015 18:34:14 -0500 Subject: [PATCH] Connect encryption support for GSSAPI Existing GSSAPI authentication code is extended to support connection encryption. Connection begins as soon as possible - that is, immediately after the client and server complete authentication. --- configure | 2 + configure.in | 1 + doc/src/sgml/client-auth.sgml | 2 +- doc/src/sgml/runtime.sgml | 20 +- src/Makefile.global.in | 1 + src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 330 +------------------- src/backend/libpq/be-gssapi.c | 583 ++++++++++++++++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 + src/backend/postmaster/postmaster.c | 12 + src/include/libpq/libpq-be.h | 31 ++ src/interfaces/libpq/Makefile | 4 + src/interfaces/libpq/fe-auth.c | 182 ----------- src/interfaces/libpq/fe-auth.h | 5 + src/interfaces/libpq/fe-connect.c | 10 + src/interfaces/libpq/fe-gssapi.c | 474 +++++++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-int.h | 16 +- 18 files changed, 1191 insertions(+), 518 deletions(-) create mode 100644 src/backend/libpq/be-gssapi.c create mode 100644 src/interfaces/libpq/fe-gssapi.c 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/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 3b2935c..7d37223 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -915,7 +915,7 @@ 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> or <acronym>GSSAPI</acronym> are used. </para> <para> diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 209eb9e..f76b79b 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1915,12 +1915,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 </para> <para> - To prevent spoofing on TCP connections, the best 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 - linkend="auth-pg-hba-conf">) and have SSL key and certificate files - (<xref linkend="ssl-tcp">). The TCP client must connect using + To prevent spoofing on TCP connections, the best solutions are either to + use GSSAPI for authentication and encryption or to use SSL certificates and + make sure that clients check the server's certificate. To secure using + SSL, the server must be configured to accept only <literal>hostssl</> + connections (<xref linkend="auth-pg-hba-conf">) and have SSL key and + certificate files (<xref linkend="ssl-tcp">). The TCP client must connect + using <literal>sslmode=verify-ca</> or <literal>verify-full</> and have the appropriate root certificate file installed (<xref linkend="libq-ssl-certificates">). @@ -2040,6 +2041,13 @@ 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> + Similarly, GSSAPI also encrypts all data sent across the network, + including passwords, queries, and data, as in + SSL. <filename>pg_hba.conf</> allows specification of GSSAPI + connections, which are always encrypted. + </para> </listitem> </varlistentry> 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..b80ae74 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.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..c0366fd 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -22,6 +22,7 @@ #include <unistd.h> #include "libpq/auth.h" +#include "libpq/libpq-be.h" #include "libpq/crypt.h" #include "libpq/ip.h" #include "libpq/libpq.h" @@ -36,7 +37,7 @@ * Global authentication functions *---------------------------------------------------------------- */ -static void sendAuthRequest(Port *port, AuthRequest areq); +void sendAuthRequest(Port *port, AuthRequest areq); static void auth_failed(Port *port, int status, char *logdetail); static char *recv_password_packet(Port *port); static int recv_and_check_password_packet(Port *port, char **logdetail); @@ -132,21 +133,6 @@ bool pg_krb_caseins_users; /*---------------------------------------------------------------- - * GSSAPI Authentication - *---------------------------------------------------------------- - */ -#ifdef ENABLE_GSS -#if defined(HAVE_GSSAPI_H) -#include <gssapi.h> -#else -#include <gssapi/gssapi.h> -#endif - -static int pg_GSS_recvauth(Port *port); -#endif /* ENABLE_GSS */ - - -/*---------------------------------------------------------------- * SSPI Authentication *---------------------------------------------------------------- */ @@ -167,22 +153,6 @@ static int pg_SSPI_recvauth(Port *port); static int CheckRADIUSAuth(Port *port); -/* - * Maximum accepted size of GSS and SSPI authentication tokens. - * - * Kerberos tickets are usually quite small, but the TGTs issued by Windows - * domain controllers include an authorization field known as the Privilege - * Attribute Certificate (PAC), which contains the user's Windows permissions - * (group memberships etc.). The PAC is copied into all tickets obtained on - * the basis of this TGT (even those issued by Unix realms which the Windows - * realm trusts), and can be several kB in size. The maximum token size - * accepted by Windows systems is determined by the MaxAuthToken Windows - * registry setting. Microsoft recommends that it is not set higher than - * 65535 bytes, so that seems like a reasonable limit for us as well. - */ -#define PG_MAX_AUTH_TOKEN_LENGTH 65535 - - /*---------------------------------------------------------------- * Global authentication functions *---------------------------------------------------------------- @@ -565,7 +535,7 @@ ClientAuthentication(Port *port) /* * Send an authentication request packet to the frontend. */ -static void +void sendAuthRequest(Port *port, AuthRequest areq) { StringInfoData buf; @@ -707,300 +677,6 @@ recv_and_check_password_packet(Port *port, char **logdetail) return result; } - - -/*---------------------------------------------------------------- - * GSSAPI authentication system - *---------------------------------------------------------------- - */ -#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) -{ - OM_uint32 maj_stat, - min_stat, - lmin_s, - gflags; - int mtype; - int ret; - StringInfoData buf; - gss_buffer_desc gbuf; - - /* - * GSS auth is not supported for protocol versions before 3, because it - * relies on the overall message length word to determine the GSS payload - * size in AuthenticationGSSContinue and PasswordMessage messages. (This - * is, in fact, a design error in our GSS support, because protocol - * messages are supposed to be parsable without relying on the length - * word; but it's not worth changing it now.) - */ - if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) - ereport(FATAL, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("GSSAPI is not supported in protocol version 2"))); - - if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0) - { - /* - * Set default Kerberos keytab file for the Krb5 mechanism. - * - * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv() - * not always available. - */ - if (getenv("KRB5_KTNAME") == NULL) - { - size_t kt_len = strlen(pg_krb_server_keyfile) + 14; - char *kt_path = malloc(kt_len); - - if (!kt_path || - snprintf(kt_path, kt_len, "KRB5_KTNAME=%s", - pg_krb_server_keyfile) != kt_len - 2 || - putenv(kt_path) != 0) - { - ereport(LOG, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("out of memory"))); - return STATUS_ERROR; - } - } - } - - /* - * We accept any service principal that's present in our keytab. This - * increases interoperability between kerberos implementations that see - * for example case sensitivity differently, while not really opening up - * any vector of attack. - */ - port->gss->cred = GSS_C_NO_CREDENTIAL; - - /* - * Initialize sequence with an empty context - */ - port->gss->ctx = GSS_C_NO_CONTEXT; - - /* - * Loop through GSSAPI message exchange. This exchange can consist of - * multiple messags sent in both directions. First message is always from - * the client. All messages from client to server are password packets - * (type 'p'). - */ - do - { - pq_startmsgread(); - - CHECK_FOR_INTERRUPTS(); - - mtype = pq_getbyte(); - if (mtype != 'p') - { - /* Only log error if client didn't disconnect. */ - if (mtype != EOF) - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("expected GSS response, got message type %d", - mtype))); - return STATUS_ERROR; - } - - /* Get the actual GSS token */ - initStringInfo(&buf); - if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH)) - { - /* EOF - pq_getmessage already logged error */ - pfree(buf.data); - return STATUS_ERROR; - } - - /* Map to GSSAPI style buffer */ - gbuf.length = buf.len; - gbuf.value = buf.data; - - elog(DEBUG4, "Processing received GSS token of length %u", - (unsigned int) gbuf.length); - - maj_stat = gss_accept_sec_context( - &min_stat, - &port->gss->ctx, - port->gss->cred, - &gbuf, - GSS_C_NO_CHANNEL_BINDINGS, - &port->gss->name, - NULL, - &port->gss->outbuf, - &gflags, - NULL, - NULL); - - /* gbuf no longer used */ - pfree(buf.data); - - elog(DEBUG5, "gss_accept_sec_context major: %d, " - "minor: %d, outlen: %u, outflags: %x", - maj_stat, min_stat, - (unsigned int) port->gss->outbuf.length, gflags); - - CHECK_FOR_INTERRUPTS(); - - if (port->gss->outbuf.length != 0) - { - /* - * Negotiation generated data to be sent to the client. - */ - elog(DEBUG4, "sending GSS response token of length %u", - (unsigned int) port->gss->outbuf.length); - - sendAuthRequest(port, AUTH_REQ_GSS_CONT); - - gss_release_buffer(&lmin_s, &port->gss->outbuf); - } - - if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) - { - gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER); - pg_GSS_error(ERROR, - gettext_noop("accepting GSS security context failed"), - maj_stat, min_stat); - } - - if (maj_stat == GSS_S_CONTINUE_NEEDED) - elog(DEBUG4, "GSS continue needed"); - - } while (maj_stat == GSS_S_CONTINUE_NEEDED); - - if (port->gss->cred != GSS_C_NO_CREDENTIAL) - { - /* - * Release service principal credentials - */ - gss_release_cred(&min_stat, &port->gss->cred); - } - - /* - * GSS_S_COMPLETE indicates that authentication is now complete. - * - * Get the name of the user that authenticated, and compare it to the pg - * username that was specified for the connection. - */ - maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL); - if (maj_stat != GSS_S_COMPLETE) - pg_GSS_error(ERROR, - gettext_noop("retrieving GSS user name failed"), - maj_stat, min_stat); - - /* - * Split the username at the realm separator - */ - if (strchr(gbuf.value, '@')) - { - char *cp = strchr(gbuf.value, '@'); - - /* - * If we are not going to include the realm in the username that is - * passed to the ident map, destructively modify it here to remove the - * realm. Then advance past the separator to check the realm. - */ - if (!port->hba->include_realm) - *cp = '\0'; - cp++; - - if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm)) - { - /* - * Match the realm part of the name first - */ - if (pg_krb_caseins_users) - ret = pg_strcasecmp(port->hba->krb_realm, cp); - else - ret = strcmp(port->hba->krb_realm, cp); - - if (ret) - { - /* GSS realm does not match */ - elog(DEBUG2, - "GSSAPI realm (%s) and configured realm (%s) don't match", - cp, port->hba->krb_realm); - gss_release_buffer(&lmin_s, &gbuf); - return STATUS_ERROR; - } - } - } - else if (port->hba->krb_realm && strlen(port->hba->krb_realm)) - { - elog(DEBUG2, - "GSSAPI did not return realm but realm matching was requested"); - - gss_release_buffer(&lmin_s, &gbuf); - return STATUS_ERROR; - } - - ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, - pg_krb_caseins_users); - - gss_release_buffer(&lmin_s, &gbuf); - - return ret; -} -#endif /* ENABLE_GSS */ - - /*---------------------------------------------------------------- * SSPI authentication system *---------------------------------------------------------------- diff --git a/src/backend/libpq/be-gssapi.c b/src/backend/libpq/be-gssapi.c new file mode 100644 index 0000000..d4d6c3a --- /dev/null +++ b/src/backend/libpq/be-gssapi.c @@ -0,0 +1,583 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi.c + * GSSAPI authentication and encryption support + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/libpq/auth.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.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 + +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))); +} + +int +pg_GSS_recvauth(Port *port) +{ + OM_uint32 maj_stat, + min_stat, + lmin_s, + gflags; + int mtype; + int ret; + StringInfoData buf; + gss_buffer_desc gbuf; + + /* + * GSS auth is not supported for protocol versions before 3, because it + * relies on the overall message length word to determine the GSS payload + * size in AuthenticationGSSContinue and PasswordMessage messages. (This + * is, in fact, a design error in our GSS support, because protocol + * messages are supposed to be parsable without relying on the length + * word; but it's not worth changing it now.) + */ + if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) + ereport(FATAL, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("GSSAPI is not supported in protocol version 2"))); + + if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0) + { + /* + * Set default Kerberos keytab file for the Krb5 mechanism. + * + * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv() + * not always available. + */ + if (getenv("KRB5_KTNAME") == NULL) + { + size_t kt_len = strlen(pg_krb_server_keyfile) + 14; + char *kt_path = malloc(kt_len); + + if (!kt_path || + snprintf(kt_path, kt_len, "KRB5_KTNAME=%s", + pg_krb_server_keyfile) != kt_len - 2 || + putenv(kt_path) != 0) + { + ereport(LOG, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + return STATUS_ERROR; + } + } + } + + /* + * We accept any service principal that's present in our keytab. This + * increases interoperability between kerberos implementations that see + * for example case sensitivity differently, while not really opening up + * any vector of attack. + */ + port->gss->cred = GSS_C_NO_CREDENTIAL; + + /* + * Initialize sequence with an empty context + */ + port->gss->ctx = GSS_C_NO_CONTEXT; + + /* + * Loop through GSSAPI message exchange. This exchange can consist of + * multiple messags sent in both directions. First message is always from + * the client. All messages from client to server are password packets + * (type 'p'). + */ + do + { + pq_startmsgread(); + + CHECK_FOR_INTERRUPTS(); + + mtype = pq_getbyte(); + if (mtype != 'p') + { + /* Only log error if client didn't disconnect. */ + if (mtype != EOF) + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("expected GSS response, got message type %d", + mtype))); + return STATUS_ERROR; + } + + /* Get the actual GSS token */ + initStringInfo(&buf); + if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH)) + { + /* EOF - pq_getmessage already logged error */ + pfree(buf.data); + return STATUS_ERROR; + } + + /* Map to GSSAPI style buffer */ + gbuf.length = buf.len; + gbuf.value = buf.data; + + elog(DEBUG4, "Processing received GSS token of length %u", + (unsigned int) gbuf.length); + + maj_stat = gss_accept_sec_context( + &min_stat, + &port->gss->ctx, + port->gss->cred, + &gbuf, + GSS_C_NO_CHANNEL_BINDINGS, + &port->gss->name, + NULL, + &port->gss->outbuf, + &gflags, + NULL, + NULL); + + /* gbuf no longer used */ + pfree(buf.data); + + elog(DEBUG5, "gss_accept_sec_context major: %d, " + "minor: %d, outlen: %u, outflags: %x", + maj_stat, min_stat, + (unsigned int) port->gss->outbuf.length, gflags); + + CHECK_FOR_INTERRUPTS(); + + if (port->gss->outbuf.length != 0) + { + /* + * Negotiation generated data to be sent to the client. + */ + elog(DEBUG4, "sending GSS response token of length %u", + (unsigned int) port->gss->outbuf.length); + + sendAuthRequest(port, AUTH_REQ_GSS_CONT); + + gss_release_buffer(&lmin_s, &port->gss->outbuf); + } + + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) + { + gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER); + pg_GSS_error(ERROR, + gettext_noop("accepting GSS security context failed"), + maj_stat, min_stat); + } + + if (maj_stat == GSS_S_CONTINUE_NEEDED) + elog(DEBUG4, "GSS continue needed"); + + } while (maj_stat == GSS_S_CONTINUE_NEEDED); + + if (port->gss->cred != GSS_C_NO_CREDENTIAL) + { + /* + * Release service principal credentials + */ + gss_release_cred(&min_stat, &port->gss->cred); + } + + /* + * GSS_S_COMPLETE indicates that authentication is now complete. + * + * Get the name of the user that authenticated, and compare it to the pg + * username that was specified for the connection. + */ + maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL); + if (maj_stat != GSS_S_COMPLETE) + pg_GSS_error(ERROR, + gettext_noop("retrieving GSS user name failed"), + maj_stat, min_stat); + + /* + * Split the username at the realm separator + */ + if (strchr(gbuf.value, '@')) + { + char *cp = strchr(gbuf.value, '@'); + + /* + * If we are not going to include the realm in the username that is + * passed to the ident map, destructively modify it here to remove the + * realm. Then advance past the separator to check the realm. + */ + if (!port->hba->include_realm) + *cp = '\0'; + cp++; + + if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm)) + { + /* + * Match the realm part of the name first + */ + if (pg_krb_caseins_users) + ret = pg_strcasecmp(port->hba->krb_realm, cp); + else + ret = strcmp(port->hba->krb_realm, cp); + + if (ret) + { + /* GSS realm does not match */ + elog(DEBUG2, + "GSSAPI realm (%s) and configured realm (%s) don't match", + cp, port->hba->krb_realm); + gss_release_buffer(&lmin_s, &gbuf); + return STATUS_ERROR; + } + } + } + else if (port->hba->krb_realm && strlen(port->hba->krb_realm)) + { + elog(DEBUG2, + "GSSAPI did not return realm but realm matching was requested"); + + gss_release_buffer(&lmin_s, &gbuf); + return STATUS_ERROR; + } + + ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, + pg_krb_caseins_users); + + gss_release_buffer(&lmin_s, &gbuf); + + return ret; +} + +static ssize_t +be_gssapi_should_crypto(Port *port) +{ + OM_uint32 major, minor; + int open = 1; + + if (port->gss->ctx == GSS_C_NO_CONTEXT) + return 0; + else if (port->gss->should_encrypt) + return 1; + + major = gss_inquire_context(&minor, port->gss->ctx, + NULL, NULL, NULL, NULL, NULL, NULL, + &open); + if (major == GSS_S_NO_CONTEXT) + { + /* + * In MIT krb5 < 1.14, it was not possible to call gss_inquire_context + * on an incomplete context. This was a violation of rfc2744 and has + * been corrected in https://github.com/krb5/krb5/pull/285 + */ + return 0; + } + else if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, + gettext_noop("GSSAPI context state error"), + major, minor); + return -1; + } + else if (open != 0) + { + /* + * Though we can start encrypting here, our client is not ready since + * it has not received the final auth packet. Set encryption on for + * the next packet, but send this one in the clear. + */ + port->gss->should_encrypt = true; + } + return 0; +} + +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/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/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 5d07b78..b0a31ae 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,9 @@ 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 */ + bool should_encrypt; /* GSSAPI encryption start */ + StringInfoData writebuf; /* GSSAPI nonblocking write buffering */ #endif } pg_gssinfo; #endif @@ -214,6 +218,33 @@ 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 +int pg_GSS_recvauth(Port *port); +ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); +ssize_t be_gssapi_write(Port *port, void *ptr, size_t len); + +/* GUC */ +extern char *pg_krb_server_keyfile; +extern bool pg_krb_caseins_users; + +/* + * Maximum accepted size of GSS and SSPI authentication tokens. + * + * Kerberos tickets are usually quite small, but the TGTs issued by Windows + * domain controllers include an authorization field known as the Privilege + * Attribute Certificate (PAC), which contains the user's Windows permissions + * (group memberships etc.). The PAC is copied into all tickets obtained on + * the basis of this TGT (even those issued by Unix realms which the Windows + * realm trusts), and can be several kB in size. The maximum token size + * accepted by Windows systems is determined by the MaxAuthToken Windows + * registry setting. Microsoft recommends that it is not set higher than + * 65535 bytes, so that seems like a reasonable limit for us as well. + */ +#define PG_MAX_AUTH_TOKEN_LENGTH 65535 +#endif + +extern void sendAuthRequest(Port *port, AuthRequest areq); + extern ProtocolVersion FrontendProtocol; /* TCP keepalives configuration. These are no-ops on an AF_UNIX socket. */ diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index 1b292d2..4b206b4 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.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..c50f6dd 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -42,188 +42,6 @@ #include "fe-auth.h" #include "libpq/md5.h" - -#ifdef ENABLE_GSS -/* - * 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); -} - -/* - * Continue GSS authentication with next token as needed. - */ -static int -pg_GSS_continue(PGconn *conn) -{ - OM_uint32 maj_stat, - min_stat, - lmin_s; - - 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, - 0, - GSS_C_NO_CHANNEL_BINDINGS, - (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf, - NULL, - &conn->goutbuf, - NULL, - NULL); - - if (conn->gctx != GSS_C_NO_CONTEXT) - { - free(conn->ginbuf.value); - conn->ginbuf.value = NULL; - conn->ginbuf.length = 0; - } - - if (conn->goutbuf.length != 0) - { - /* - * GSS generated data to send to the server. We don't care if it's the - * first or subsequent packet, just send the same kind of password - * packet. - */ - if (pqPacketSend(conn, 'p', - conn->goutbuf.value, conn->goutbuf.length) - != STATUS_OK) - { - gss_release_buffer(&lmin_s, &conn->goutbuf); - return STATUS_ERROR; - } - } - gss_release_buffer(&lmin_s, &conn->goutbuf); - - if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) - { - pg_GSS_error(libpq_gettext("GSSAPI continuation error"), - conn, - maj_stat, min_stat); - gss_release_name(&lmin_s, &conn->gtarg_nam); - if (conn->gctx) - gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER); - return STATUS_ERROR; - } - - if (maj_stat == GSS_S_COMPLETE) - gss_release_name(&lmin_s, &conn->gtarg_nam); - - return STATUS_OK; -} - -/* - * Send initial GSS authentication token - */ -static int -pg_GSS_startup(PGconn *conn) -{ - OM_uint32 maj_stat, - min_stat; - int maxlen; - gss_buffer_desc temp_gbuf; - - if (!(conn->pghost && conn->pghost[0] != '\0')) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("host name must be specified\n")); - return STATUS_ERROR; - } - - if (conn->gctx) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("duplicate GSS authentication request\n")); - return STATUS_ERROR; - } - - /* - * Import service principal name so the proper ticket can be acquired by - * the GSSAPI system. - */ - maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; - temp_gbuf.value = (char *) malloc(maxlen); - if (!temp_gbuf.value) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory\n")); - return STATUS_ERROR; - } - snprintf(temp_gbuf.value, maxlen, "%s@%s", - conn->krbsrvname, conn->pghost); - temp_gbuf.length = strlen(temp_gbuf.value); - - maj_stat = gss_import_name(&min_stat, &temp_gbuf, - GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); - free(temp_gbuf.value); - - if (maj_stat != GSS_S_COMPLETE) - { - pg_GSS_error(libpq_gettext("GSSAPI name import error"), - conn, - maj_stat, min_stat); - return STATUS_ERROR; - } - - /* - * Initial packet is the same as a continuation packet with no initial - * context. - */ - conn->gctx = GSS_C_NO_CONTEXT; - - return pg_GSS_continue(conn); -} -#endif /* ENABLE_GSS */ - - #ifdef ENABLE_SSPI /* * SSPI authentication system (Windows only) diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h index 9d11654..410c731 100644 --- a/src/interfaces/libpq/fe-auth.h +++ b/src/interfaces/libpq/fe-auth.h @@ -21,4 +21,9 @@ extern int pg_fe_sendauth(AuthRequest areq, PGconn *conn); extern char *pg_fe_getauthname(PQExpBuffer errorMessage); +#ifdef ENABLE_GSS +int pg_GSS_continue(PGconn *conn); +int pg_GSS_startup(PGconn *conn); +#endif + #endif /* FE_AUTH_H */ diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5ad4755..aa2340a 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -2798,6 +2798,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 +2918,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 +3027,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.c b/src/interfaces/libpq/fe-gssapi.c new file mode 100644 index 0000000..471c6a2 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi.c @@ -0,0 +1,474 @@ +/*------------------------------------------------------------------------- + * + * fe-auth.c + * The front-end (client) support for GSSAPI + * + * 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.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "libpq-fe.h" +#include "libpq-int.h" +#include "fe-auth.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. + */ +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); +} + +/* + * Continue GSS authentication with next token as needed. + */ +int +pg_GSS_continue(PGconn *conn) +{ + OM_uint32 maj_stat, + min_stat, + lmin_s; + + 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, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf, + NULL, + &conn->goutbuf, + NULL, + NULL); + + if (conn->gctx != GSS_C_NO_CONTEXT) + { + free(conn->ginbuf.value); + conn->ginbuf.value = NULL; + conn->ginbuf.length = 0; + } + + if (conn->goutbuf.length != 0) + { + /* + * GSS generated data to send to the server. We don't care if it's the + * first or subsequent packet, just send the same kind of password + * packet. + */ + if (pqPacketSend(conn, 'p', + conn->goutbuf.value, conn->goutbuf.length) + != STATUS_OK) + { + gss_release_buffer(&lmin_s, &conn->goutbuf); + return STATUS_ERROR; + } + } + gss_release_buffer(&lmin_s, &conn->goutbuf); + + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) + { + pg_GSS_error(libpq_gettext("GSSAPI continuation error"), + conn, + maj_stat, min_stat); + gss_release_name(&lmin_s, &conn->gtarg_nam); + if (conn->gctx) + gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER); + return STATUS_ERROR; + } + + if (maj_stat == GSS_S_COMPLETE) + gss_release_name(&lmin_s, &conn->gtarg_nam); + + return STATUS_OK; +} + +/* + * Send initial GSS authentication token + */ +int +pg_GSS_startup(PGconn *conn) +{ + OM_uint32 maj_stat, + min_stat; + int maxlen; + gss_buffer_desc temp_gbuf; + + if (!(conn->pghost && conn->pghost[0] != '\0')) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("host name must be specified\n")); + return STATUS_ERROR; + } + + if (conn->gctx) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("duplicate GSS authentication request\n")); + return STATUS_ERROR; + } + + /* + * Import service principal name so the proper ticket can be acquired by + * the GSSAPI system. + */ + maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; + temp_gbuf.value = (char *) malloc(maxlen); + if (!temp_gbuf.value) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return STATUS_ERROR; + } + snprintf(temp_gbuf.value, maxlen, "%s@%s", + conn->krbsrvname, conn->pghost); + temp_gbuf.length = strlen(temp_gbuf.value); + + maj_stat = gss_import_name(&min_stat, &temp_gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); + free(temp_gbuf.value); + + if (maj_stat != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI name import error"), + conn, + maj_stat, min_stat); + return STATUS_ERROR; + } + + /* + * Initial packet is the same as a continuation packet with no initial + * context. + */ + conn->gctx = GSS_C_NO_CONTEXT; + + return pg_GSS_continue(conn); +} + +/* + * Only consider encryption when GSS context is complete + */ +static ssize_t +pg_GSS_should_crypto(PGconn *conn) +{ + OM_uint32 major, minor; + int open = 1; + + if (conn->gctx == GSS_C_NO_CONTEXT) + return 0; + else if (conn->gencrypt) + return 1; + + major = gss_inquire_context(&minor, conn->gctx, + NULL, NULL, NULL, NULL, NULL, NULL, + &open); + if (major == GSS_S_NO_CONTEXT) + { + /* + * In MIT krb5 < 1.14, it was not possible to call gss_inquire_context + * on an incomplete context. This was a violation of rfc2744 and has + * been corrected in https://github.com/krb5/krb5/pull/285 + */ + return 0; + } + else if (GSS_ERROR(major)) + { + pg_GSS_error(libpq_gettext("GSSAPI context state error"), conn, + major, minor); + return -1; + } + else if (open != 0) + { + conn->gencrypt = true; + return 1; + } + return 0; +} + +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..6b75d104 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,11 @@ 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 */ + bool gencrypt; /* GSS is ready for encryption */ + PQExpBufferData gwritebuf; /* GSS nonblocking write buffering */ + size_t gwritecurs; /* GSS write buffer position */ #endif #ifdef ENABLE_SSPI @@ -620,7 +626,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 +648,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-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
Attachment
On 2/15/16 12:45 PM, Robbie Harwood wrote: > David Steele <david@pgmasters.net> writes: > >> 1) It didn't apply cleanly to HEAD. It did apply cleanly on a455878 >> which I figured was recent enough for testing. I didn't bisect to find >> the exact commit that broke it. > > It applied to head of master (57c932475504d63d8f8a68fc6925d7decabc378a) > for me (`patch -p1 < v4-GSSAPI-encryption-support.patch`). I rebased it > anyway and cut a v5 anyway, just to be sure. It's attached, and > available on github as well: > https://github.com/frozencemetery/postgres/commit/dc10e3519f0f6c67f79abd157dc8ff1a1c293f53 It could have been my mistake. I'll give it another try when you have a new patch. >> 2) While I was able to apply the patch and get it compiled it seemed >> pretty flaky - I was only able to logon about 1 in 10 times on average. >> Here was my testing methodology: > > What I can't tell from looking at your methodology is whether both the > client and server were running my patches or no. There's no fallback > here (I'd like to talk about how that should work, with example from > v1-v3, if people have ideas). This means that both the client and the > server need to be running my patches for the moment. Is this your > setup? I was testing on a system with no version of PostgreSQL installed. I applied your patch to master and then ran both server and client from that patched version. Is there something I'm missing? -- -David david@pgmasters.net
David Steele <david@pgmasters.net> writes: > On 2/15/16 12:45 PM, Robbie Harwood wrote: >> David Steele <david@pgmasters.net> writes: >> >>> 1) It didn't apply cleanly to HEAD. It did apply cleanly on a455878 >>> which I figured was recent enough for testing. I didn't bisect to find >>> the exact commit that broke it. >> >> It applied to head of master (57c932475504d63d8f8a68fc6925d7decabc378a) >> for me (`patch -p1 < v4-GSSAPI-encryption-support.patch`). I rebased it >> anyway and cut a v5 anyway, just to be sure. It's attached, and >> available on github as well: >> https://github.com/frozencemetery/postgres/commit/dc10e3519f0f6c67f79abd157dc8ff1a1c293f53 > > It could have been my mistake. I'll give it another try when you have a > new patch. Please do let me know how v5 goes. If you run into trouble, in addition to the logs you helpfully provided before, I'd like a traffic dump (pcap preferable; I need tcp/udp port 88 for Kerberos and tcp port 5432 or whatever you're running postgres on) if possible. Thanks! >>> 2) While I was able to apply the patch and get it compiled it seemed >>> pretty flaky - I was only able to logon about 1 in 10 times on average. >>> Here was my testing methodology: >> >> What I can't tell from looking at your methodology is whether both the >> client and server were running my patches or no. There's no fallback >> here (I'd like to talk about how that should work, with example from >> v1-v3, if people have ideas). This means that both the client and the >> server need to be running my patches for the moment. Is this your >> setup? > > I was testing on a system with no version of PostgreSQL installed. I > applied your patch to master and then ran both server and client from > that patched version. Is there something I'm missing? Not that I can immediately see. As long as the client and server are both patched, everything should work. My process is the same as with previous versions of this patchset [0], and though I'm using FreeIPA there is no reason it shouldn't work with any other KDC (MIT, for instance[1]) provided the IPA calls are converted. I am curious, though - I haven't changed any of the authentication code in v4/v5 from what's in ~master, so how often can you log in using GSSAPI using master? [0]: https://mivehind.net/2015/06/11/kerberized-postgresql/ [1]: http://web.mit.edu/kerberos/krb5-devel/doc/admin/install_kdc.html
On Tue, Feb 16, 2016 at 2:45 AM, Robbie Harwood <rharwood@redhat.com> wrote: > David Steele <david@pgmasters.net> writes: >> On 2/10/16 4:06 PM, Robbie Harwood wrote: >>> Hello friends, >>> >>> For your consideration, here is a new version of GSSAPI encryption >>> support. For those who prefer, it's also available on my github: >>> https://github.com/frozencemetery/postgres/commit/c92275b6605d7929cda5551de47a4c60aab7179e >> >> It tried out this patch and ran into a few problems: >> >> 1) It didn't apply cleanly to HEAD. It did apply cleanly on a455878 >> which I figured was recent enough for testing. I didn't bisect to find >> the exact commit that broke it. > > It applied to head of master (57c932475504d63d8f8a68fc6925d7decabc378a) > for me (`patch -p1 < v4-GSSAPI-encryption-support.patch`). I rebased it > anyway and cut a v5 anyway, just to be sure. It's attached, and > available on github as well: > https://github.com/frozencemetery/postgres/commit/dc10e3519f0f6c67f79abd157dc8ff1a1c293f53 v5 is applying fine for me. There were two diff hunks in routine.sgml but nothing to worry about. >> 2) While I was able to apply the patch and get it compiled it seemed >> pretty flaky - I was only able to logon about 1 in 10 times on average. >> Here was my testing methodology: >> >> a) Build Postgres from a455878 (without your patch), install/configure >> Kerberos and get everything working. I was able the set the auth method >> to gss in pg_hba.conf and logon successfully every time. >> >> b) On the same system rebuild Postgres from a455878 including your patch >> and attempt authentication. >> >> The problems arose after step 2b. Sometimes I would try to logon twenty >> times without success and sometimes it only take five or six attempts. >> I was never able to logon successfully twice in a row. >> >> When not successful the client always output this incomplete message >> (without terminating LF): >> >> psql: expected authentication request from server, but received >> >> From the logs I can see the server is reporting EOF from the client, >> though the client does not core dump and prints the above message before >> exiting. >> >> I have attached files that contain server logs at DEBUG5 and tcpdump >> output for both the success and failure cases. >> >> Please let me know if there's any more information you would like me to >> provide. > > What I can't tell from looking at your methodology is whether both the > client and server were running my patches or no. There's no fallback > here (I'd like to talk about how that should work, with example from > v1-v3, if people have ideas). This means that both the client and the > server need to be running my patches for the moment. Is this your > setup? We need to be careful here, backward-compatibility is critical for both the client and the server, or to put it in other words, an uptables client should still be able to connect a patched server, and vice-versa. This is an area where it is important to not break any third-part tool, either using libpq or reimplementing the frontend protocol. So I finally began to dive into your new patch... And after testing this is breaking the authentication protocol for GSS. I have been able to connect to a server once, but at the second attempt and after connection is failing with the following error: psql: expected authentication request from server, but received ioltas Also, something that is missing is the parametrization that has been discussed the last time this patch was on the mailing list. Where is the capacity to control if a client is connecting to a server that is performing encryption and downgrade to non-ecrypted connection should the server not be able to support it? Enforcing a client to require encryption support using pg_hba.conf was as well a good thing. Some more infrastructure is needed here, I thought that there was an agreement previously regarding that. Also, and to make the review a bit easier, it would be good to split the patch into smaller pieces (thanks for waiting on my input here, this became quite clear after looking at this code). Basically, pg_GSS_error is moved to a new file, bringing with it pg_GSS_recvauth because the error routine is being used by the new ones you are introducing: be_gssapi_write, etc. The split that this patch is doing is a bit confusing, all the GSS-related stuff is put within one single file: - read/write routines - authentication routine - constants - routines for error handling Mixing read/write routines with the authentication routine looks wrong, because the read-write routines just need to create a dependency with for example be-secure.c on the backend. In short, where before authentication and secure read/writes after authentication get linked to each other, and create a dependency that did not exist before. For the sake of clarity I would suggest the following split: - be-gss-common.c, where all the constants and the error handling routine are located. - Let authrecv in auth.c - Move only the read/write routines to the new file be-[secure-]gssapi.c Splitting the patches into one that moves around the routines, and a second that introduces the new logic would bring more clarity. As In understood from the patch, enforcing encryption is not good either, and any patched clients would not be able to 9.5 or older servers. Regarding the parametrization, I liked the previous v1-v3 layout to control the encryption, and this allows to set up the session context on backend and frontend at authentication. The connection errors are also something to look at, I guess that David and I ran into the same thing. I am wondering how this patch is tested to be honest. -- Michael
On Wed, Feb 24, 2016 at 7:12 PM, Robbie Harwood <rharwood@redhat.com> wrote: > David Steele <david@pgmasters.net> writes: > >> On 2/15/16 12:45 PM, Robbie Harwood wrote: >>> David Steele <david@pgmasters.net> writes: >>> >>>> 1) It didn't apply cleanly to HEAD. It did apply cleanly on a455878 >>>> which I figured was recent enough for testing. I didn't bisect to find >>>> the exact commit that broke it. >>> >>> It applied to head of master (57c932475504d63d8f8a68fc6925d7decabc378a) >>> for me (`patch -p1 < v4-GSSAPI-encryption-support.patch`). I rebased it >>> anyway and cut a v5 anyway, just to be sure. It's attached, and >>> available on github as well: >>> https://github.com/frozencemetery/postgres/commit/dc10e3519f0f6c67f79abd157dc8ff1a1c293f53 >> >> It could have been my mistake. I'll give it another try when you have a >> new patch. > > Please do let me know how v5 goes. If you run into trouble, in addition > to the logs you helpfully provided before, I'd like a traffic dump (pcap > preferable; I need tcp/udp port 88 for Kerberos and tcp port 5432 or > whatever you're running postgres on) if possible. Thanks! > >>>> 2) While I was able to apply the patch and get it compiled it seemed >>>> pretty flaky - I was only able to logon about 1 in 10 times on average. >>>> Here was my testing methodology: >>> >>> What I can't tell from looking at your methodology is whether both the >>> client and server were running my patches or no. There's no fallback >>> here (I'd like to talk about how that should work, with example from >>> v1-v3, if people have ideas). This means that both the client and the >>> server need to be running my patches for the moment. Is this your >>> setup? >> >> I was testing on a system with no version of PostgreSQL installed. I >> applied your patch to master and then ran both server and client from >> that patched version. Is there something I'm missing? > > Not that I can immediately see. As long as the client and server are > both patched, everything should work. My process is the same as with > previous versions of this patchset [0], and though I'm using FreeIPA > there is no reason it shouldn't work with any other KDC (MIT, for > instance[1]) provided the IPA calls are converted. I used a custom krb5kdc set up manually, and all my connection attempts are working on HEAD, not with your patch (both client and server patched). > I am curious, though - I haven't changed any of the authentication code > in v4/v5 from what's in ~master, so how often can you log in using > GSSAPI using master? My guess is that there is something not been correctly cleaned up when closing the connection. The first attempt worked for me, not after. -- Michael
On 2/25/16 2:08 AM, Michael Paquier wrote: > On Wed, Feb 24, 2016 at 7:12 PM, Robbie Harwood <rharwood@redhat.com> wrote: >> >> Not that I can immediately see. As long as the client and server are >> both patched, everything should work. My process is the same as with >> previous versions of this patchset [0], and though I'm using FreeIPA >> there is no reason it shouldn't work with any other KDC (MIT, for >> instance[1]) provided the IPA calls are converted. > > I used a custom krb5kdc set up manually, and all my connection > attempts are working on HEAD, not with your patch (both client and > server patched). I've got the same setup with the same results. >> I am curious, though - I haven't changed any of the authentication code >> in v4/v5 from what's in ~master, so how often can you log in using >> GSSAPI using master? > > My guess is that there is something not been correctly cleaned up when > closing the connection. The first attempt worked for me, not after. I was able to get in again after a number of failed attempts, though the number varied. -- -David david@pgmasters.net
Michael Paquier <michael.paquier@gmail.com> writes: > On Tue, Feb 16, 2016 at 2:45 AM, Robbie Harwood <rharwood@redhat.com> wrote: >> David Steele <david@pgmasters.net> writes: >>> On 2/10/16 4:06 PM, Robbie Harwood wrote: >>>> Hello friends, >>>> >>>> For your consideration, here is a new version of GSSAPI encryption >>>> support. For those who prefer, it's also available on my github: >>>> https://github.com/frozencemetery/postgres/commit/c92275b6605d7929cda5551de47a4c60aab7179e >>> >>> It tried out this patch and ran into a few problems: >>> >>> 2) While I was able to apply the patch and get it compiled it seemed >>> pretty flaky - I was only able to logon about 1 in 10 times on average. >>> >>> When not successful the client always output this incomplete message >>> (without terminating LF): >>> >>> psql: expected authentication request from server, but received >>> >>> From the logs I can see the server is reporting EOF from the client, >>> though the client does not core dump and prints the above message before >>> exiting. >>> >>> I have attached files that contain server logs at DEBUG5 and tcpdump >>> output for both the success and failure cases. >>> >>> Please let me know if there's any more information you would like me to >>> provide. >> >> What I can't tell from looking at your methodology is whether both the >> client and server were running my patches or no. There's no fallback >> here (I'd like to talk about how that should work, with example from >> v1-v3, if people have ideas). This means that both the client and the >> server need to be running my patches for the moment. Is this your >> setup? > > We need to be careful here, backward-compatibility is critical for > both the client and the server, or to put it in other words, an > uptables client should still be able to connect a patched server, and > vice-versa. This is an area where it is important to not break any > third-part tool, either using libpq or reimplementing the frontend > protocol. Which is why in my introduction to the v4 patch I explicitly mentioned that this was missing. I wanted to talk about how this should be implemented, since I feel that I received no feedback on that design From last time. It's not hard to port that design over, if desired. > So I finally began to dive into your new patch... And after testing > this is breaking the authentication protocol for GSS. I have been able > to connect to a server once, but at the second attempt and after > connection is failing with the following error: > psql: expected authentication request from server, but received ioltas Interesting. I will see if I can find anything. The capture posted earlier (thanks David!) suggests that the client doesn't expect the encrypted data. I suspect what has happened is a race between the client buffering data From the socket and the client processing the completion of GSSAPI authentication. This kind of issue is why my v1 handled GSSAPI at the protocol level, not at the transport level. I think we end up with encrypted data in the buffer that's supposed to be decrypted, and since the GSSAPI blob starts with \x00, it doesn't know what to do. I'll cut a v6 with most of the changes we've talked about here. It should address this issue, but I suspect that no one will be happy about how, since the client essentially needs to "un-read" some data. As a side note, this would also explain why I can't reproduce the issue, since I'm running in very low-latency environments (three VMs on my laptop). > Also, something that is missing is the parametrization that has been > discussed the last time this patch was on the mailing list. Where is > the capacity to control if a client is connecting to a server that is > performing encryption and downgrade to non-ecrypted connection should > the server not be able to support it? Enforcing a client to require > encryption support using pg_hba.conf was as well a good thing. Some > more infrastructure is needed here, I thought that there was an > agreement previously regarding that. This does not match my impression of the discussion, but I would be happy to be wrong about that since it is less work for me. > Also, and to make the review a bit easier, it would be good to split > the patch into smaller pieces (thanks for waiting on my input here, > this became quite clear after looking at this code). Basically, > pg_GSS_error is moved to a new file, bringing with it pg_GSS_recvauth > because the error routine is being used by the new ones you are > introducing: be_gssapi_write, etc. The split that this patch is doing > is a bit confusing, all the GSS-related stuff is put within one single > file: > - read/write routines > - authentication routine > - constants > - routines for error handling > Mixing read/write routines with the authentication routine looks > wrong, because the read-write routines just need to create a > dependency with for example be-secure.c on the backend. In short, > where before authentication and secure read/writes after > authentication get linked to each other, and create a dependency that > did not exist before. > > For the sake of clarity I would suggest the following split: > - be-gss-common.c, where all the constants and the error handling > routine are located. > - Let authrecv in auth.c > - Move only the read/write routines to the new file be-[secure-]gssapi.c > Splitting the patches into one that moves around the routines, and a > second that introduces the new logic would bring more clarity. So... auth.c is now going to depend on be-gss-common.c, and be-secure-gssapi.c is also going to depend on be-gss-common.c. That doesn't seem better in terms of dependencies: now I'm creating two that didn't exist before. That said, I would believe that it makes everything clearer. I will do that shortly. > I am wondering how this patch is tested to be honest. We've already had this conversation; please let's stay constructive.
On Fri, Feb 26, 2016 at 5:02 AM, Robbie Harwood <rharwood@redhat.com> wrote: > Michael Paquier <michael.paquier@gmail.com> writes: >> We need to be careful here, backward-compatibility is critical for >> both the client and the server, or to put it in other words, an >> uptables client should still be able to connect a patched server, and >> vice-versa. This is an area where it is important to not break any >> third-part tool, either using libpq or reimplementing the frontend >> protocol. > > Which is why in my introduction to the v4 patch I explicitly mentioned > that this was missing. I wanted to talk about how this should be > implemented, since I feel that I received no feedback on that design > From last time. It's not hard to port that design over, if desired. I gave my opinion about the parametrization here a couple of months back, and thought that it was rather a neat design. I still think so: http://www.postgresql.org/message-id/CAB7nPqRGQ60PWLb-CLLrvMoQwPAbnu-961W+xGPvG62RMSkcZQ@mail.gmail.com In short: 1) Introduction of new pg_hba parameter require_encrypt, defaulting to off, enforcing the clients to have encryption. This way an administrator of a new server can prevent connections of old clients if he/she wants to have all the connections excrypted. 2) Client-side parameter, that you named previously gss_encrypt, to let a client decide if he wishes to do encryption or not. >> So I finally began to dive into your new patch... And after testing >> this is breaking the authentication protocol for GSS. I have been able >> to connect to a server once, but at the second attempt and after >> connection is failing with the following error: >> psql: expected authentication request from server, but received ioltas > > Interesting. I will see if I can find anything. The capture posted > earlier (thanks David!) suggests that the client doesn't expect the > encrypted data. > > I suspect what has happened is a race between the client buffering data > From the socket and the client processing the completion of GSSAPI > authentication. This kind of issue is why my v1 handled GSSAPI at the > protocol level, not at the transport level. I think we end up with > encrypted data in the buffer that's supposed to be decrypted, and since > the GSSAPI blob starts with \x00, it doesn't know what to do. > > I'll cut a v6 with most of the changes we've talked about here. It > should address this issue, but I suspect that no one will be happy about > how, since the client essentially needs to "un-read" some data. Let's be sure that we come out with something rock-solid here, the code paths taken for authentication do not require an authenticated user, so any bugs introduced could have dangerous consequences for the backend. > As a side note, this would also explain why I can't reproduce the issue, > since I'm running in very low-latency environments (three VMs on my > laptop). I'm doing my tests in single VM, with both krb5kdc and Postgres running together. >> Also, something that is missing is the parametrization that has been >> discussed the last time this patch was on the mailing list. Where is >> the capacity to control if a client is connecting to a server that is >> performing encryption and downgrade to non-ecrypted connection should >> the server not be able to support it? Enforcing a client to require >> encryption support using pg_hba.conf was as well a good thing. Some >> more infrastructure is needed here, I thought that there was an >> agreement previously regarding that. > > This does not match my impression of the discussion, but I would be > happy to be wrong about that since it is less work for me. OK, good to know. I had the opposite impression actually :) See above. >> Also, and to make the review a bit easier, it would be good to split >> the patch into smaller pieces (thanks for waiting on my input here, >> this became quite clear after looking at this code). Basically, >> pg_GSS_error is moved to a new file, bringing with it pg_GSS_recvauth >> because the error routine is being used by the new ones you are >> introducing: be_gssapi_write, etc. The split that this patch is doing >> is a bit confusing, all the GSS-related stuff is put within one single >> file: >> - read/write routines >> - authentication routine >> - constants >> - routines for error handling >> Mixing read/write routines with the authentication routine looks >> wrong, because the read-write routines just need to create a >> dependency with for example be-secure.c on the backend. In short, >> where before authentication and secure read/writes after >> authentication get linked to each other, and create a dependency that >> did not exist before. >> >> For the sake of clarity I would suggest the following split: >> - be-gss-common.c, where all the constants and the error handling >> routine are located. >> - Let authrecv in auth.c >> - Move only the read/write routines to the new file be-[secure-]gssapi.c >> Splitting the patches into one that moves around the routines, and a >> second that introduces the new logic would bring more clarity. > > So... auth.c is now going to depend on be-gss-common.c, and > be-secure-gssapi.c is also going to depend on be-gss-common.c. That > doesn't seem better in terms of dependencies: now I'm creating two that > didn't exist before. > > That said, I would believe that it makes everything clearer. I will do > that shortly. That's a personal suggestion on the matter. If you feel that the way you did the separation is better, nothing prevents you from submitting the patch as such. IMO making a clear separation between the authentication code path and the read/write code path matters, and this separation exists today. In any case, a patch doing the refactoring, and a second patch adding the feature would greatly facilitate the review. After looking at the patch once this is pretty clear. -- Michael
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
Hi Robbie, On 3/8/16 5:44 PM, Robbie Harwood wrote: > Hello friends, > > Here's yet another version of GSSAPI encryption support. It's also > available for viewing on my github: I got this warning when applying the first patch in the set: ../other/v6-0001-Move-common-GSSAPI-code-into-its-own-files.patch:245: new blank line at EOF. + warning: 1 line adds whitespace errors. I know it's minor but I'm always happier when patches apply cleanly. The build went fine but when testing I was unable to logon at all. I'm using the same methodology as in http://www.postgresql.org/message-id/56BE0FF9.70302@pgmasters.net except that I'm running against 51c0f63 and using the v6 patch set. psql simply hangs and never returns. I have attached a pcap of the psql/postgres session generated with: tcpdump -i lo -nnvvXSs 1514 port 5432 -w gssapi.pcap If you would like me to capture more information please let me know specifically how you would like me to capture it. I reverted to v5 and got the same behavior I was seeing with v4 and v5, namely that I can only logon occasionally and usually get this error: psql: expected authentication request from server, but received Using a fresh build from 51c0f63 I can logon reliably every time so I don't think there's an issue in my environment. -- -David david@pgmasters.net
Attachment
David Steele <david@pgmasters.net> writes: > On 3/8/16 5:44 PM, Robbie Harwood wrote: >> >> Here's yet another version of GSSAPI encryption support. It's also >> available for viewing on my github: > > I got this warning when applying the first patch in the set: > > ../other/v6-0001-Move-common-GSSAPI-code-into-its-own-files.patch:245: > new blank line at EOF. > + > warning: 1 line adds whitespace errors. Hah, so it does. Thanks for catching it; will fix. > The build went fine but when testing I was unable to logon at all. I'm > using the same methodology as in > http://www.postgresql.org/message-id/56BE0FF9.70302@pgmasters.net except > that I'm running against 51c0f63 and using the v6 patch set. > > psql simply hangs and never returns. I have attached a pcap of the > psql/postgres session generated with: > > tcpdump -i lo -nnvvXSs 1514 port 5432 -w gssapi.pcap > > If you would like me to capture more information please let me know > specifically how you would like me to capture it. Thank you for the pcap! (I'm using wireshark so formats it can open are greatly appreciated.) This suggests that the hang is my client code's fault, but just in case: I assume nothing unusual was logged on the server? v6-0002-Connection-encryption-support-for-GSSAPI.patch in fe-connect.c at around line 2518 adds a call to appendBinaryPQExpBuffer and sets conn->inEnd. Can you try without those lines? Can you also (e.g., with gdb or by adding printf calls) tell me what the values of conn->inStart, conn->inEnd, and conn->inCursor any time (should only be once) that those lines are triggered? > I reverted to v5 and got the same behavior I was seeing with v4 and v5, > namely that I can only logon occasionally and usually get this error: > > psql: expected authentication request from server, but received > > Using a fresh build from 51c0f63 I can logon reliably every time so I > don't think there's an issue in my environment. Agreed, I'm sure I've caused it somehow, though I don't know what's wrong yet. (And if it weren't my fault but you didn't get useful errors out, that'd be my fault anyway for not checking enough stuff!) I don't know if this would say anything relevant, but it might be interesting to see what the results are of applying [1] to the v5 code. It's the same approach to solving the problem, though it happens at a different time due to the aforementioned protocol change between v5 and v6. Thanks, --Robbie [1] https://github.com/frozencemetery/postgres/commit/82c89227a6b499ac9273044f91cff747c154629f
David Steele <david@pgmasters.net> writes: > Hi Robbie, > > On 3/8/16 5:44 PM, Robbie Harwood wrote: >> Hello friends, >> >> Here's yet another version of GSSAPI encryption support. It's also >> available for viewing on my github: > > The build went fine but when testing I was unable to logon at all. I'm > using the same methodology as in > http://www.postgresql.org/message-id/56BE0FF9.70302@pgmasters.net except > that I'm running against 51c0f63 and using the v6 patch set. > > psql simply hangs and never returns. I have attached a pcap of the > psql/postgres session generated with: > > tcpdump -i lo -nnvvXSs 1514 port 5432 -w gssapi.pcap > > If you would like me to capture more information please let me know > specifically how you would like me to capture it. Please disregard my other email. I think I've found the issue; will post a new version in a moment.
On 3/14/16 4:10 PM, Robbie Harwood wrote: > David Steele <david@pgmasters.net> writes: > >> Hi Robbie, >> >> On 3/8/16 5:44 PM, Robbie Harwood wrote: >>> Hello friends, >>> >>> Here's yet another version of GSSAPI encryption support. It's also >>> available for viewing on my github: >> >> The build went fine but when testing I was unable to logon at all. I'm >> using the same methodology as in >> http://www.postgresql.org/message-id/56BE0FF9.70302@pgmasters.net except >> that I'm running against 51c0f63 and using the v6 patch set. >> >> psql simply hangs and never returns. I have attached a pcap of the >> psql/postgres session generated with: >> >> tcpdump -i lo -nnvvXSs 1514 port 5432 -w gssapi.pcap >> >> If you would like me to capture more information please let me know >> specifically how you would like me to capture it. > > Please disregard my other email. I think I've found the issue; will > post a new version in a moment. Strange timing since I was just testing this. Here's what I got: $ pg/bin/psql -h localhost -U vagrant@PGMASTERS.NET postgres conn->inStart = 179, conn->inEnd = 179, conn->inCursor = 179 psql (9.6devel) Type "help" for help. postgres=> This was after commenting out: // appendBinaryPQExpBuffer(&conn->gwritebuf, // conn->inBuffer + conn->inStart, // conn->inEnd - conn->inStart); // conn->inEnd = conn->inStart; The good news I can log on every time now! -- -David david@pgmasters.net
Hello friends, New week, new version. GitHub link: https://github.com/frozencemetery/postgres/tree/feature/gssencrypt7 Changes in this version: - Removed extra whitespace in auth code movement. - Fixed connection desync issue. A diff of this and v6 will reveal three issues: - First, that pg_GSS_read() didn't properly handle having a full buffer when called because pqsecure_raw_read() doesn't handle reads of size 0. I've elected to change my own code here only, but it may be desirable to change pqsecure_raw_read() as well depending on whether other people are likely to hit that. - Second, that I was shunting data into the wrong buffer (don't know how this was overlooked; it has "write" right there in the name). - Third, that I'm now immediately decrypting that data into conn->inBuffer rather than deferring that step until later. This removes the hang because now the connection will not erroneously get stuck polling while data is buffered. Thanks! From 3b62e99de16f2c4600d0bb02f3626e5157ecdc6c 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 | 74 +++++++++++++++++++++++++++++++++ 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, 198 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..eab68a5 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,74 @@ +/*------------------------------------------------------------------------- + * + * 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 7905c9d84f23a0f7c330d5362a0387a6fa3b250c 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 | 101 +++++++++++- 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 | 262 +++++++++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-int.h | 18 ++- 22 files changed, 814 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..0d669e1 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,38 @@ 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) && conn->inEnd > conn->inStart) + { + /* + * 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. + */ + int n; + + appendBinaryPQExpBuffer(&conn->gbuf, + conn->inBuffer + conn->inStart, + conn->inEnd - conn->inStart); + conn->inEnd = conn->inStart; + + /* Will not block on nonblocking sockets */ + n = pg_GSS_read(conn, conn->inBuffer + conn->inEnd, + conn->inBufSize - conn->inEnd); + if (n > 0) + conn->inEnd += n; + /* + * If n < 0, then this wasn't a full request and + * either more data will be available later to + * complete it or we will error out then. + */ + } +#endif + /* * Set asyncStatus so that PQgetResult will think that * what comes back next is the result of a query. See @@ -2558,6 +2602,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 +2650,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 +2660,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 +2883,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 +3003,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 +3112,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..d245a64 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------- + * + * 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; + } + + if (conn->gbuf.len - 4 < input.length) + { + 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 3ab19af128c3fbe0c8def86155e3163dcebb0bd7 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 eab68a5..7f9640e 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
David Steele <david@pgmasters.net> writes: > On 3/14/16 4:10 PM, Robbie Harwood wrote: > >> David Steele <david@pgmasters.net> writes: >> >>> On 3/8/16 5:44 PM, Robbie Harwood wrote: >>> >>>> Here's yet another version of GSSAPI encryption support. It's also >>>> available for viewing on my github: >>> >>> psql simply hangs and never returns. I have attached a pcap of the >>> psql/postgres session generated with: >> >> Please disregard my other email. I think I've found the issue; will >> post a new version in a moment. > > Strange timing since I was just testing this. Here's what I got: > > $ pg/bin/psql -h localhost -U vagrant@PGMASTERS.NET postgres > conn->inStart = 179, conn->inEnd = 179, conn->inCursor = 179 > psql (9.6devel) > Type "help" for help. > > postgres=> Thanks, that certainly is interesting! I did finally manage to reproduce the issue on my end, but the rate of incidence is much lower than what you and Michael were seeing: I have to run connections in a loop for about 10-20 minutes before it makes itself apparent (and no, it's not due to entropy). Apparently I just wasn't patient enough. > This was after commenting out: > > // appendBinaryPQExpBuffer(&conn->gwritebuf, > // conn->inBuffer + conn->inStart, > // conn->inEnd - conn->inStart); > // conn->inEnd = conn->inStart; > > The good news I can log on every time now! Since conn->inStart == conn->inEnd in the case you were testing, the lines you commented out would have been a no-op anyway (that's the normal case of operation, as far as I can tell). That said, the chances of hitting the race for me seemed very dependent on how much code wants to run in that conditional: I got it up to 30-40 minutes when I added a lot of printf()s (can't just run in gdb because it's nondeterministic and rr has flushing bugs at the moment). All that is to say: thank you very much for investigating that!
On 3/14/16 7:20 PM, Robbie Harwood wrote: > David Steele <david@pgmasters.net> writes: > >> >> Strange timing since I was just testing this. Here's what I got: >> >> $ pg/bin/psql -h localhost -U vagrant@PGMASTERS.NET postgres >> conn->inStart = 179, conn->inEnd = 179, conn->inCursor = 179 >> psql (9.6devel) >> Type "help" for help. >> >> postgres=> > > Thanks, that certainly is interesting! I did finally manage to > reproduce the issue on my end, but the rate of incidence is much lower > than what you and Michael were seeing: I have to run connections in a > loop for about 10-20 minutes before it makes itself apparent (and no, > it's not due to entropy). Apparently I just wasn't patient enough. I'm running client and server on the same VM - perhaps that led to less latency than your setup? > All that is to say: thank you very much for investigating that! My pleasure! -- -David david@pgmasters.net
On 3/8/16 5:44 PM, Robbie Harwood wrote: > Here's yet another version of GSSAPI encryption support. OK, everything seems to be working fine with a 9.6 client and server so next I tested older clients and I got this error: $ /usr/lib/postgresql/9.1/bin/psql -h localhost \ -U vagrant@PGMASTERS.NET postgres psql: FATAL: GSSAPI did no provide required flags There's a small typo in the error message, BTW. Here's the output from the server logs: DEBUG: forked new backend, pid=7746 socket=9 DEBUG: postgres child[7746]: starting with ( DEBUG: postgres DEBUG: ) DEBUG: InitPostgres DEBUG: my backend ID is 2 DEBUG: StartTransaction DEBUG: name: unnamed; blockState: DEFAULT; state: INPROGR, xid/subid/cid: 0/1/0, nestlvl: 1, children: DEBUG: Processing received GSS token of length 667 DEBUG: gss_accept_sec_context major: 0, minor: 0, outlen: 161, outflags: 1b2 DEBUG: sending GSS response token of length 161 DEBUG: sending GSS token of length 161 FATAL: GSSAPI did no provide required flags DEBUG: shmem_exit(1): 1 before_shmem_exit callbacks to make DEBUG: shmem_exit(1): 6 on_shmem_exit callbacks to make DEBUG: proc_exit(1): 3 callbacks to make DEBUG: exit(1) DEBUG: shmem_exit(-1): 0 before_shmem_exit callbacks to make DEBUG: shmem_exit(-1): 0 on_shmem_exit callbacks to make DEBUG: proc_exit(-1): 0 callbacks to make DEBUG: reaping dead processes DEBUG: server process (PID 7746) exited with exit code 1 I got the same result with a 9.4 client so didn't try any others. -- -David david@pgmasters.net
On Tue, Mar 15, 2016 at 3:12 PM, David Steele <david@pgmasters.net> wrote: > On 3/8/16 5:44 PM, Robbie Harwood wrote: >> Here's yet another version of GSSAPI encryption support. This looks far more stable than last versions, cool to see the progress. pgbench -C does not complain on my side so that's a good thing. This is not yet a detailed review, there are a couple of things that I find strange in what you did and are potential subject to bugs, but I need a bit of time to let that mature a bit. This is not something yet committable, but I really like the direction that the patch is taking. For now, regarding 0002: /* - * 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(); Er, this sends unconditionally the message without caring about the protocol, and so this is incorrect, no? +#ifdef ENABLE_GSS + if (conn->gss->buf.data) + pfree(conn->gss->buf.data); + if (conn->gss->writebuf.data) + pfree(conn->gss->writebuf.data); +#endif You should use resetStringInfo here. > OK, everything seems to be working fine with a 9.6 client and server so > next I tested older clients and I got this error: > > $ /usr/lib/postgresql/9.1/bin/psql -h localhost \ > -U vagrant@PGMASTERS.NET postgres > psql: FATAL: GSSAPI did no provide required flags > > There's a small typo in the error message, BTW. And in 0003, the previous error is caused by that: + 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; + } Yeah, this is a recipe for protocol incompatibility and here the connection context is not yet fully defined I believe. We need to be careful. - maj_stat = gss_accept_sec_context( - &min_stat, + maj_stat = gss_accept_sec_context(&min_stat, This is just noise. -- Michael
Michael Paquier <michael.paquier@gmail.com> writes: > On Tue, Mar 15, 2016 at 3:12 PM, David Steele <david@pgmasters.net> wrote: >> On 3/8/16 5:44 PM, Robbie Harwood wrote: >>> Here's yet another version of GSSAPI encryption support. >> >> This looks far more stable than last versions, cool to see the >> progress. pgbench -C does not complain on my side so that's a good >> thing. This is not yet a detailed review, there are a couple of >> things that I find strange in what you did and are potential subject >> to bugs, but I need a bit of time to let that mature a bit. This is >> not something yet committable, but I really like the direction that >> the patch is taking. Thanks! I must admit a preference for receiving all feedback at once (reduces back-and-forth overhead), but if you feel there are still design-type problems then that's very reasonable. (I also admit to feeling the pressure of feature freeze in less than a month.) > For now, regarding 0002: > /* > - * 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(); > Er, this sends unconditionally the message without caring about the > protocol, and so this is incorrect, no? My impression from reading the old version of the comment above it was that on other protocols it could go either way. I think last time I made it conditional; I can do so again if it is desired. It's certainly not /incorrect/ to send it immediately; there's just a question of performance by minimizing the number of writes as far as I can tell. > +#ifdef ENABLE_GSS > + if (conn->gss->buf.data) > + pfree(conn->gss->buf.data); > + if (conn->gss->writebuf.data) > + pfree(conn->gss->writebuf.data); > +#endif > You should use resetStringInfo here. That will leak since resetStringInfo() doesn't free the underlying representation. >> OK, everything seems to be working fine with a 9.6 client and server so >> next I tested older clients and I got this error: >> >> $ /usr/lib/postgresql/9.1/bin/psql -h localhost \ >> -U vagrant@PGMASTERS.NET postgres >> psql: FATAL: GSSAPI did no provide required flags >> >> There's a small typo in the error message, BTW. Thanks, will fix. I forgot that MIT doesn't provide GSS_C_REPLAY_FLAG and GSS_C_SEQUENCE_FLAG by default. Removing those from auth.c should temporarily resolve the problem, which is what I'll do in the next version. (Tested with MIT krb5.) On the subject of older code, I realized (one of those wake up in the middle of the night-type moments) that the old server has a potential problem with new clients now in that we try to decrypt the "help I don't recognize connection parameter gss_encrypt" error message. v8 will have some sort of a fix, though I don't know what yet. The only thing I've come up with so far is to have the client decrypt check the first time through for packets beginning with 'E'. Pretty much anything I can do will end up being a poor substitute for being at the protocol layer anyway. > And in 0003, the previous error is caused by that: > + 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; > + } > Yeah, this is a recipe for protocol incompatibility and here the > connection context is not yet fully defined I believe. We need to be > careful. Nope, it's done. This is happening immediately prior to username checks. By the time we exit the do/while the context is fully complete. > - maj_stat = gss_accept_sec_context( > - &min_stat, > + maj_stat = gss_accept_sec_context(&min_stat, > > This is just noise. You're not wrong, though I do think it makes the code more readable by enforcing style and this is a "cleanup" commit. I'll take it out if it bothers you.
Robbie, * Robbie Harwood (rharwood@redhat.com) wrote: > Michael Paquier <michael.paquier@gmail.com> writes: > > - maj_stat = gss_accept_sec_context( > > - &min_stat, > > + maj_stat = gss_accept_sec_context(&min_stat, > > > > This is just noise. > > You're not wrong, though I do think it makes the code more readable by > enforcing style and this is a "cleanup" commit. I'll take it out if it > bothers you. First, thanks much for working on this, I've been following along the discussions and hope to be able to help move it to commit, once it's ready. Secondly, generally, speaking, we prefer that 'cleanup' type changes (particularly whitespace-only ones) are in independent commits which are marked as just whitespace/indentation changes. We have a number of organizations which follow our code changes and it makes it more difficult on them to include whitespace/indentation changes with code changes. Thanks! Stephen
Stephen Frost <sfrost@snowman.net> writes: > Robbie, > > * Robbie Harwood (rharwood@redhat.com) wrote: >> Michael Paquier <michael.paquier@gmail.com> writes: >> > - maj_stat = gss_accept_sec_context( >> > - &min_stat, >> > + maj_stat = gss_accept_sec_context(&min_stat, >> > >> > This is just noise. >> >> You're not wrong, though I do think it makes the code more readable by >> enforcing style and this is a "cleanup" commit. I'll take it out if it >> bothers you. > > First, thanks much for working on this, I've been following along the > discussions and hope to be able to help move it to commit, once it's > ready. > > Secondly, generally, speaking, we prefer that 'cleanup' type changes > (particularly whitespace-only ones) are in independent commits which are > marked as just whitespace/indentation changes. We have a number of > organizations which follow our code changes and it makes it more > difficult on them to include whitespace/indentation changes with code > changes. Thanks for the clarification. I'll be sure to take it out!
Hello friends, A new version of my GSSAPI encryption patchset is available, both in this email and on my github: https://github.com/frozencemetery/postgres/tree/feature/gssencrypt8 What changed: - Fixed fallback in the new server/old client case. The server flag checking has been reduced. In the (distant) future when all old clients are gone, the checking should be increased in strength once again. - Fixed fallback in the old server/new client case. This consists of checking whether the first message we receive after auth is done is an error, and raising it without decrypting if it is so that reconnection can happen if desired. The quality of the fallback path has also generally been improved. - Removed overzealous whitespace cleanup. I'm a packager and have been bitten by upstreams doing this; I should have known better. - Made flushing the AUTH_REQ_OK message conditional again. - Fixed typo in server error message for insufficient GSSAPI protection. Thanks! From 3b62e99de16f2c4600d0bb02f3626e5157ecdc6c 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 | 74 +++++++++++++++++++++++++++++++++ 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, 198 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..eab68a5 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,74 @@ +/*------------------------------------------------------------------------- + * + * 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 32b29af13b175b1debd3bf06511d2416dfa17732 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 | 7 +- 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 | 99 ++++++++++- 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 | 294 ++++++++++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-int.h | 19 ++- 22 files changed, 846 insertions(+), 12 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..deca1ca 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -596,10 +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) + if (areq != AUTH_REQ_OK || port->gss != NULL) 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..67772f6 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,38 @@ 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) && conn->inEnd > conn->inStart) + { + /* + * 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. + */ + int n; + + appendBinaryPQExpBuffer(&conn->gbuf, + conn->inBuffer + conn->inStart, + conn->inEnd - conn->inStart); + conn->inEnd = conn->inStart; + + /* Will not block on nonblocking sockets */ + n = pg_GSS_read(conn, conn->inBuffer + conn->inEnd, + conn->inBufSize - conn->inEnd); + if (n > 0) + conn->inEnd += n; + /* + * If n < 0, then this wasn't a full request and + * either more data will be available later to + * complete it or we will error out then. + */ + } +#endif + /* * Set asyncStatus so that PQgetResult will think that * what comes back next is the result of a query. See @@ -2558,6 +2602,47 @@ 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. + */ + 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; + conn->gss_auth_done = false; + conn->gss_decrypted = false; + + /* Must drop the old connection */ + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + gss_delete_sec_context(&minor, &conn->gctx, + GSS_C_NO_BUFFER); + + resetPQExpBuffer(&conn->gbuf); + resetPQExpBuffer(&conn->gwritebuf); + goto keep_going; + } + } + else if (*conn->gss_enc_require == '1') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("Server does not support required GSSAPI encryption\n")); + } +#endif else if (conn->send_appname && (conn->appname || conn->fbappname)) { @@ -2575,7 +2660,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; @@ -2798,6 +2883,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 +3003,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 +3112,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..62f9366 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,294 @@ +/*------------------------------------------------------------------------- + * + * 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); + + /* Pass up error message fragments. See comment below. */ + if (!conn->gss_decrypted && conn->gcursor == conn->gbuf.len) + { + /* Call _raw_read to get any remaining parts of the message */ + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = 0; + } + + 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 can receive error messages from old servers that don't support + * GSSAPI encryption at this time. They need to be passed up so that we + * can potentially reconnect. + * + * This limits the server's first reply to not be between 1157627904 + * (about 2**30) and 1174405119, which are both over a gigabyte in size. + * If the server sends a connection parameter status message of this size, + * there are other problems present. + */ + if (!conn->gss_decrypted && conn->gbuf.data[0] == 'E') + { + ret = pg_GSS_read_from_buffer(conn, ptr, len); + if (conn->gcursor == conn->gbuf.len) + { + /* Call _raw_read to get any remaining parts of the message */ + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = 0; + } + return ret; + } + + /* 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; + } + + if (conn->gbuf.len - 4 < input.length) + { + 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->gss_decrypted = true; + + 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..a786460 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,14 @@ 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 */ + bool gss_decrypted; /* GSS decrypted first message */ + char *gss_enc_require; /* GSS encryption required */ #endif #ifdef ENABLE_SSPI @@ -620,7 +629,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 +651,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 b12cc44b6fd4ff9e8a613b21afadb6b80edd3f6a 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, 29 insertions(+), 26 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index deca1ca..3040707 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -718,7 +718,8 @@ pg_GSS_recvauth(Port *port) OM_uint32 maj_stat, min_stat, lmin_s, - gflags; + gflags, + target_flags; int mtype; int ret; StringInfoData buf; @@ -874,6 +875,17 @@ pg_GSS_recvauth(Port *port) } /* + * GSS_C_REPLAY_FLAG and GSS_C_SEQUENCE_FLAG are missing for compatability + * with older clients and should be added in as soon as possible. + */ + target_flags = GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; + if ((gflags & target_flags) != target_flags) + { + ereport(FATAL, (errmsg("GSSAPI did not provide required flags"))); + return STATUS_ERROR; + } + + /* * GSS_S_COMPLETE indicates that authentication is now complete. * * Get the name of the user that authenticated, and compare it to the pg diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c index eab68a5..7f9640e 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
On 3/20/16 12:09 AM, Robbie Harwood wrote: > Hello friends, > > A new version of my GSSAPI encryption patchset is available, both in > this email and on my github: > https://github.com/frozencemetery/postgres/tree/feature/gssencrypt8 Excellent, Robbie! I've run this patch through my test cases and everything works. Now that it's working I'll be writing up an actual review so expect that by Monday. -- -David david@pgmasters.net
On Fri, Mar 25, 2016 at 10:10 PM, David Steele <david@pgmasters.net> wrote: > Excellent, Robbie! I've run this patch through my test cases and > everything works. > > Now that it's working I'll be writing up an actual review so expect that > by Monday. (I haven't given up on this patch yet, sorry for the low activity on this thread) -- Michael
Hi Robbie, On 3/20/16 12:09 AM, Robbie Harwood wrote: > A new version of my GSSAPI encryption patchset is available Here's a more thorough review: * PATCH - Move common GSSAPI code into its own files diff --git a/src/backend/libpq/be-gssapi-common.c + if (msg_ctx) + + /* + * More than one message available. XXX: Should we loop and read all + * messages? (same below) + */ It seems like it would be a good idea to pull all error messages if only for debugging purposes. + /* 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"))); Same here. * PATCH - Connection encryption support for GSSAPI +++ b/doc/src/sgml/client-auth.sgml 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. Perhaps "... unless SSL or GSSAPI encryption is used." + <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. Some rewording: Require GSSAPI encryption. The default is off which enables GSSAPI encryption only when available and requested to maintain compatability with older clients. This setting should be enabled unless older clients are present. +++ b/doc/src/sgml/libpq.sgml @@ -1356,6 +1356,18 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <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> Some rewording: Require GSSAPI encryption support from the remote server when set. By default clients will fall back to not using GSSAPI encryption if the server does not support encryption through GSSAPI. +++ b/doc/src/sgml/runtime.sgml I think you mean postgresql.conf? + <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> I think a link to the client parameter would be valuable here. +++ b/src/backend/libpq/auth.c + * 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. Why? +++ b/src/backend/libpq/be-secure-gssapi.c Some of the functions in this file are missing comment headers and should have more inline comments for each major section. + ret = be_gssapi_should_crypto(port); Add LF here. + port->gss->writebuf.cursor += ret; And here. + /* need to be called again */ And here. Well, you get the idea. These functions could all use more whitespace and comments. +++ b/src/backend/utils/misc/guc.c + { + {"gss_encrypt", PGC_USERSET, CONN_AUTH_SECURITY, + gettext_noop("Whether client wants encryption for this connection."), Perhaps, "Require encryption for all GSSAPI connections." +++ b/src/interfaces/libpq/fe-connect.c + if (n > 0) + conn->inEnd += n; + /* + * If n < 0, then this wasn't a full request and + * either more data will be available later to + * complete it or we will error out then. + */ Shouldn't this comment block go above the if? + /* + * 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. + */ typo - "doesn't support appname". +++ b/src/interfaces/libpq/fe-secure-gssapi.c Comments are a bit better here than in be-secure-gssapi.c but could still be better. And again, more whitespace. * PATCH 3 - GSSAPI authentication cleanup +++ b/src/backend/libpq/auth.c + * GSS_C_REPLAY_FLAG and GSS_C_SEQUENCE_FLAG are missing for compatability + * with older clients and should be added in as soon as possible. Please elaborate here. Why should they be added and what functionality is missing before they are. +++ b/src/backend/libpq/be-gssapi-common.c -#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 Can you explain why it's OK to remove this now? I can see you've replaced it in gss_init_sec_context() with GSS_C_MUTUAL_FLAG. Have you tested this on Win32? Things look pretty good in general but fe-secure-gssapi.c and be-secure-gssapi.c should both be reworked with better comments and more whitespace before this patch goes to a committer. -- -David david@pgmasters.net
David Steele <david@pgmasters.net> writes: > On 3/20/16 12:09 AM, Robbie Harwood wrote: > >> A new version of my GSSAPI encryption patchset is available > > Here's a more thorough review: Thanks for the review! To keep this a manageable size, I'm going to trim pretty heavily. If I haven't replied to something, please interpret it to mean that I think it's a good suggestion and will fix/change in v9. > +++ b/doc/src/sgml/runtime.sgml > > I think you mean postgresql.conf? Sorry, what does this review comment go with? > +++ b/src/backend/libpq/auth.c > > + * 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. > > Why? Because otherwise we will send the connection spew (connection parameters and such) unencrypted, since it will get grouped with the AUTH_REQ_OK packet. I'll note this in the comment. > + ret = be_gssapi_should_crypto(port); > > Add LF here. > > > + port->gss->writebuf.cursor += ret; > > And here. > > + /* need to be called again */ > > And here. Well, you get the idea. Sorry, what is LF? ASCII newline? > * PATCH 3 - GSSAPI authentication cleanup > > +++ b/src/backend/libpq/auth.c > > + * GSS_C_REPLAY_FLAG and GSS_C_SEQUENCE_FLAG are missing for compatability > + * with older clients and should be added in as soon as possible. > > Please elaborate here. Why should they be added and what functionality > is missing before they are. It's request reply detection and out-of-sequence detection. We can't require them because old clients don't request them, and so would be unable to connect. (There's some history of this in the last couple versions I've posted, but it's not really interesting beyond "it doesn't work".) I will clarify this comment. > +++ b/src/backend/libpq/be-gssapi-common.c > > -#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 > > Can you explain why it's OK to remove this now? I can see you've > replaced it in gss_init_sec_context() with GSS_C_MUTUAL_FLAG. Have you > tested this on Win32? This comment is old enough that it references sources from Athena. If this is in fact a current krb5 bug (which I doubt, since MIT tests windows rather heavily), then it should be filed against krb5 instead of kludged around here. I do not however have win32 machines to test this with. (GSS_C_MUTUAL_FLAG is unrelated; it just requests that the client and server are both authenticated to each other.) Thanks, --Robbie
On 3/29/16 5:05 PM, Robbie Harwood wrote: > David Steele <david@pgmasters.net> writes: > >> On 3/20/16 12:09 AM, Robbie Harwood wrote: >> >>> A new version of my GSSAPI encryption patchset is available >> >> Here's a more thorough review: > > Thanks for the review! To keep this a manageable size, I'm going to > trim pretty heavily. If I haven't replied to something, please > interpret it to mean that I think it's a good suggestion and will > fix/change in v9. > >> +++ b/doc/src/sgml/runtime.sgml >> >> I think you mean postgresql.conf? > > Sorry, what does this review comment go with? Oops! That was a comment I made then realized I had misunderstood what was going on. I removed the code but not the comment apparently. Thanks for the other clarifications. -- -David david@pgmasters.net
Hello friends, A new version of my GSSAPI encryption patchset is available, both in this email and on my github: https://github.com/frozencemetery/postgres/tree/feature/gssencrypt9 This version is intended to address David's review suggestions: - The only code change (not counting SGML and comments) is that I've resolved the pre-existing "XXX: Should we loop and read all messages?" comment in the affirmative by... looping and reading all messages in pg_GSS_error. Though commented on as part of the first patch, this is bundled with the rest of the auth cleanup since the first patch is code motion only. - Several changes to comments, documentation rewordings, and whitespace additions. I look forward to finding out what needs even more of the same treatment. Of all the changesets I've posted thus far, this might be the one for which it makes the most sense to see what changed by diffing from the previous changeset. Thanks! From 3b62e99de16f2c4600d0bb02f3626e5157ecdc6c 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 | 74 +++++++++++++++++++++++++++++++++ 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, 198 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..eab68a5 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,74 @@ +/*------------------------------------------------------------------------- + * + * 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.8.0.rc3 From 0eb508757d11ab4a42fe99003fb4199eb4524a56 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 | 14 +- doc/src/sgml/libpq.sgml | 11 ++ doc/src/sgml/runtime.sgml | 22 ++- src/backend/libpq/Makefile | 2 +- src/backend/libpq/auth.c | 8 +- src/backend/libpq/be-secure-gssapi.c | 315 +++++++++++++++++++++++++++++ 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 | 99 +++++++++- 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 | 340 ++++++++++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-int.h | 19 +- 22 files changed, 941 insertions(+), 12 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..c1d9a36 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -915,7 +915,7 @@ 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> or <acronym>GSSAPI</acronym> encryption is in use. </para> <para> @@ -1046,6 +1046,18 @@ omicron bryanh guest1 </para> </listitem> </varlistentry> + + <varlistentry id="gssapi-require-encrypt" xreflabel="require_encrypt"> + <term><literal>require_encrypt</literal></term> + <listitem> + <para> + Require GSSAPI encryption. Defaults to off, which enables GSSAPI + encryption only when both available and requested to maintain + compatability with old clients. This setting should be enabled 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..a5be5f9 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1356,6 +1356,17 @@ 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> + Require GSSAPI encryption support from the remote server when set. By + default, clients will 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..2d6132f 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,17 @@ 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 + called <xref linkend="gssapi-require-encrypt">; unless set, 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 + called <xref linkend="libpq-connect-gss-enc-require">. + </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..94d95bd 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -596,10 +596,12 @@ 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. However, if we are doing GSSAPI encryption, that request + * must go out immediately to ensure that all messages which follow the + * AUTH_REQ_OK are not grouped with it and can therefore be encrypted. */ - if (areq != AUTH_REQ_OK) + if (areq != AUTH_REQ_OK || port->gss != NULL) 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..d197025 --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,315 @@ +/*------------------------------------------------------------------------- + * + * 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" + +/* + * Wrapper function indicating whether we are currently performing GSSAPI + * connection encryption. + * + * gss_encrypt is set when connection parameters are processed, which happens + * immediately after AUTH_REQ_OK is sent. + */ +static ssize_t +be_gssapi_should_crypto(Port *port) +{ + if (port->gss->ctx == GSS_C_NO_CONTEXT) + return 0; + return gss_encrypt; +} + +/* + * Send a message along the connection, possibly encrypting using GSSAPI. + * + * If we are not encrypting at the moment, we send data plaintext and follow + * the calling conventions of secure_raw_write. Otherwise, the following + * hold: Incomplete writes are buffered using a dedicated StringInfo in the + * port structure. On failure, we return -1; on partial write, we return 0 + * since the translation between plaintext and encrypted is indeterminate; on + * completed write, we return the number of bytes written. Behavior when + * called with a new pointer/length combination after an incomplete write is + * undefined. + */ +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); + + /* send any data we have buffered */ + 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; + + /* update and possibly clear buffer state */ + 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; + } + + /* encrypt the message */ + 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; + } + + /* format for on-wire: 4 network-order bytes of length, then payload */ + 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; + } + + /* buffer any leftovers from a partial write */ + 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; +} + +/* + * Wrapper function for buffering decrypted data. + * + * This allows us to present a stream-like interface to secure_read. For a + * description of buffering behavior, see comment at be_gssapi_read. + */ +static ssize_t +be_gssapi_read_from_buffer(Port *port, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* Is any data available? */ + if (port->gss->buf.len > 4 && port->gss->buf.cursor < port->gss->buf.len) + { + /* clamp length */ + 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 all data has been read, reset buffer */ + 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); + + /* ensure proper behavior under recursion */ + if (len == 0) + return 0; + + /* report any buffered data, then recur */ + 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; + + /* write length to buffer */ + 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); + + /* read the packet into our buffer */ + 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; + + /* decrypt the packet */ + 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; + } + + /* load decrypted packet into our buffer, then recur */ + 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..821899e 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("Require encryption for all GSSAPI connections."), + 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..d2d4349 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,38 @@ 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) && conn->inEnd > conn->inStart) + { + /* + * 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. + */ + int n; + + appendBinaryPQExpBuffer(&conn->gbuf, + conn->inBuffer + conn->inStart, + conn->inEnd - conn->inStart); + conn->inEnd = conn->inStart; + + /* Will not block on nonblocking sockets */ + n = pg_GSS_read(conn, conn->inBuffer + conn->inEnd, + conn->inBufSize - conn->inEnd); + /* + * If n < 0, then this wasn't a full request and + * either more data will be available later to + * complete it or we will error out then. + */ + if (n > 0) + conn->inEnd += n; + } +#endif + /* * Set asyncStatus so that PQgetResult will think that * what comes back next is the result of a query. See @@ -2558,6 +2602,47 @@ 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 support appname will also not support + * GSSAPI encryption. + */ + 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; + conn->gss_auth_done = false; + conn->gss_decrypted = false; + + /* Must drop the old connection */ + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + gss_delete_sec_context(&minor, &conn->gctx, + GSS_C_NO_BUFFER); + + resetPQExpBuffer(&conn->gbuf); + resetPQExpBuffer(&conn->gwritebuf); + goto keep_going; + } + } + else if (*conn->gss_enc_require == '1') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("Server does not support required GSSAPI encryption\n")); + } +#endif else if (conn->send_appname && (conn->appname || conn->fbappname)) { @@ -2575,7 +2660,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; @@ -2798,6 +2883,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 +3003,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 +3112,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..1c41947 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,340 @@ +/*------------------------------------------------------------------------- + * + * 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" + +/* + * Send a message along the connection, possibly using GSSAPI. + * + * If not encrypting at the call-time, we send plaintext following calling + * conventions of pqsecure_raw_write. Partial writes are supported using a + * dedicated PQExpBuffer in conn. A partial write will return 0; otherwise, + * we return -1 (for error) or the number of total bytes written in the write + * of the current ptr. Calling with a new value of ptr after a partial write + * is undefined. + */ +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); + + /* send any data we have buffered */ + 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; + + /* update and possibly clear buffer state */ + 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; + } + + /* encrypt the message */ + 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; + } + + /* format for on-wire: 4 network-order bytes of length, then payload */ + 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; + } + + /* buffer any leftovers from a partial write */ + 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; +} + +/* + * Wrapper function for buffering decrypted data. + * + * This allows us to present a stream-like interface to pqsecure_read. For a + * description of buffering, see comment at be_gssapi_read (in be-gssapi.c). + */ +static ssize_t +pg_GSS_read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* Is any data available? */ + if (conn->gcursor < conn->gbuf.len) + { + /* clamp length */ + 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 all data has been read, reset buffer */ + 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); + + /* ensure proper behavior under recursion */ + if (len == 0) + return 0; + + /* report any buffered data, then recur */ + if (conn->gcursor > 0) + { + ret = pg_GSS_read_from_buffer(conn, ptr, len); + + /* Pass up error message fragments. See comment below. */ + if (!conn->gss_decrypted && conn->gcursor == conn->gbuf.len) + { + /* call _raw_read to get any remaining parts of the message */ + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = 0; + } + + 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; + + /* write length to buffer */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + return 0; + } + + /* + * We can receive error messages from old servers that don't support + * GSSAPI encryption at this time. They need to be passed up so that we + * can potentially reconnect. + * + * This limits the server's first reply to not be between 1157627904 + * (about 2**30) and 1174405119, which are both over a gigabyte in size. + * If the server sends a connection parameter status message of this size, + * there are other problems present. + */ + if (!conn->gss_decrypted && conn->gbuf.data[0] == 'E') + { + ret = pg_GSS_read_from_buffer(conn, ptr, len); + if (conn->gcursor == conn->gbuf.len) + { + /* Call _raw_read to get any remaining parts of the message */ + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = 0; + } + return ret; + } + + /* 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; + } + + /* load any remaining parts of the packet into our buffer */ + if (conn->gbuf.len - 4 < input.length) + { + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + input.length - conn->gbuf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < input.length) + return 0; + } + + /* decrypt the packet */ + 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->gss_decrypted = true; + + /* load decrypted packet into our buffer, then recur */ + 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..a786460 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,14 @@ 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 */ + bool gss_decrypted; /* GSS decrypted first message */ + char *gss_enc_require; /* GSS encryption required */ #endif #ifdef ENABLE_SSPI @@ -620,7 +629,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 +651,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.8.0.rc3 From ddbdabe37fc66f6881a22e20db8aebbd46385f84 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. Finally, resolve a pre-existing action item for acquiring all possible GSSAPI error messages on the backend, rather than just the first major and first minor statuses. --- src/backend/libpq/auth.c | 20 ++++++++++++- src/backend/libpq/be-gssapi-common.c | 50 ++++++++++++++++----------------- src/interfaces/libpq/fe-auth.c | 19 +++++++++++-- src/interfaces/libpq/fe-gssapi-common.c | 11 -------- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 94d95bd..83f7fd1 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -719,7 +719,8 @@ pg_GSS_recvauth(Port *port) OM_uint32 maj_stat, min_stat, lmin_s, - gflags; + gflags, + target_flags; int mtype; int ret; StringInfoData buf; @@ -875,6 +876,23 @@ pg_GSS_recvauth(Port *port) } /* + * GSS_C_REPLAY_FLAG (request replay detection) and GSS_C_SEQUENCE_FLAG + * (out-of-sequence detection) are missing for compatability with older + * clients which do not request these flags. They should be added in as + * soon as we no longer care about pre-9.6 clients. + * + * Newer clients will request both GSS_C_REPLAY_FLAGS and + * GSS_C_SEQUENCE_FLAGS as well as the flags we check for below, so lack + * of these two flags is not the end of the world. + */ + target_flags = GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; + if ((gflags & target_flags) != target_flags) + { + ereport(FATAL, (errmsg("GSSAPI did not provide required flags"))); + return STATUS_ERROR; + } + + /* * GSS_S_COMPLETE indicates that authentication is now complete. * * Get the name of the user that authenticated, and compare it to the pg diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c index eab68a5..843d449 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) { @@ -36,31 +25,40 @@ pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat) msg_ctx; char msg_major[128], msg_minor[128]; + short i; + + gmsg.value = NULL; + gmsg.length = 0; /* 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); + i = 0; + do + { + gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(msg_major + i, gmsg.value, sizeof(msg_major) - i); + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < sizeof(msg_major)); - if (msg_ctx) - - /* - * More than one message available. XXX: Should we loop and read all - * messages? (same below) - */ + if (msg_ctx || i == sizeof(msg_major)) 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); + i = 0; + do + { + gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(msg_minor + i, gmsg.value, sizeof(msg_minor) - i); + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < sizeof(msg_minor)); - if (msg_ctx) + if (msg_ctx || i == sizeof(msg_minor)) ereport(WARNING, (errmsg_internal("incomplete GSS minor error report"))); 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.8.0.rc3
Attachment
On Wed, Mar 30, 2016 at 1:01 PM, Robbie Harwood <rharwood@redhat.com> wrote: > A new version of my GSSAPI encryption patchset is available, both in > this email and on my github: > https://github.com/frozencemetery/postgres/tree/feature/gssencrypt9 > > This version is intended to address David's review suggestions: > > - The only code change (not counting SGML and comments) is that I've > resolved the pre-existing "XXX: Should we loop and read all messages?" > comment in the affirmative by... looping and reading all messages in > pg_GSS_error. Though commented on as part of the first patch, this is > bundled with the rest of the auth cleanup since the first patch is > code motion only. > > - Several changes to comments, documentation rewordings, and whitespace > additions. I look forward to finding out what needs even more of the > same treatment. Of all the changesets I've posted thus far, this > might be the one for which it makes the most sense to see what changed > by diffing from the previous changeset. Thanks for the new versions. For now I have been having a look at only 0001. The first thing I have noticed is that 0001 breaks the Windows build using Visual Studio. When building without GSSAPI, fe-gssapi-common.c and be-gssapi-common.c should be removed from the list of files used so that's simple enough to fix. Now when GSSAPI build is enabled, there are some declaration conflicts with your patch, leading to many compilation errors. This took me some time to figure out but the cause is this diff in be-gssapi-common.c: +#include "libpq/be-gssapi-common.h" + +#include "postgres.h" postgres.h should be declared *before* be-gssapi-common.h. As I suggested the refactoring and I guess you don't have a Windows environment at hand, attached is a patch that fixes the build with and without gssapi for Visual Studio, that can be applied on top of your 0001. Feel free to rebase using it. Note for others: I had as well to patch the MSVC scripts because in the newest installations of Kerberos: - inc/ does not exist anymore, include/ does - The current VS scripts ignore completely x64 builds, the libraries linked to are for x86 unconditionally. I am not sure if there are *any* people building the code with Kerberos on Windows except I, but as things stand those scripts are rather broken in my view. Now looking at 0002 and 0003... -- Michael
Attachment
On Thu, Mar 31, 2016 at 2:14 PM, Michael Paquier <michael.paquier@gmail.com> wrote: > On Wed, Mar 30, 2016 at 1:01 PM, Robbie Harwood <rharwood@redhat.com> wrote: >> A new version of my GSSAPI encryption patchset is available, both in >> this email and on my github: >> https://github.com/frozencemetery/postgres/tree/feature/gssencrypt9 >> >> This version is intended to address David's review suggestions: >> >> - The only code change (not counting SGML and comments) is that I've >> resolved the pre-existing "XXX: Should we loop and read all messages?" >> comment in the affirmative by... looping and reading all messages in >> pg_GSS_error. Though commented on as part of the first patch, this is >> bundled with the rest of the auth cleanup since the first patch is >> code motion only. >> >> - Several changes to comments, documentation rewordings, and whitespace >> additions. I look forward to finding out what needs even more of the >> same treatment. Of all the changesets I've posted thus far, this >> might be the one for which it makes the most sense to see what changed >> by diffing from the previous changeset. > > Thanks for the new versions. For now I have been having a look at only 0001. > > The first thing I have noticed is that 0001 breaks the Windows build > using Visual Studio. When building without GSSAPI, fe-gssapi-common.c > and be-gssapi-common.c should be removed from the list of files used > so that's simple enough to fix. Now when GSSAPI build is enabled, > there are some declaration conflicts with your patch, leading to many > compilation errors. This took me some time to figure out but the cause > is this diff in be-gssapi-common.c: > +#include "libpq/be-gssapi-common.h" > + > +#include "postgres.h" > > postgres.h should be declared *before* be-gssapi-common.h. As I > suggested the refactoring and I guess you don't have a Windows > environment at hand, attached is a patch that fixes the build with and > without gssapi for Visual Studio, that can be applied on top of your > 0001. Feel free to rebase using it. > > Note for others: I had as well to patch the MSVC scripts because in > the newest installations of Kerberos: > - inc/ does not exist anymore, include/ does > - The current VS scripts ignore completely x64 builds, the libraries > linked to are for x86 unconditionally. > I am not sure if there are *any* people building the code with > Kerberos on Windows except I, but as things stand those scripts are > rather broken in my view. (Lonely feeling after typing that) > Now looking at 0002 and 0003... (Thanks for the split btw, this is far easier to look at) Patch 0002 has the same problems, because it introduces the gssapi-secure stuff (see attached patch that can be applied on top of 0002 fixing the windows build). When gss build is not enabled, compilation is able to work, now when gss is enabled... See below. + <varlistentry id="libpq-connect-gss-enc-require" xreflabel="gss_enc_require"> + <term><literal>gss_enc_require</literal></term> + <listitem> Perhaps renaming that gss_require_encrypt? +++ b/src/backend/libpq/be-secure-gssapi.c [...] +#include "libpq/be-gssapi-common.h" + +#include "postgres.h" Those should be reversed. + Require GSSAPI encryption. Defaults to off, which enables GSSAPI + encryption only when both available and requested to maintain + compatability with old clients. This setting should be enabled unless + old clients are present. s/compatability/compatibility/ + <para> + Require GSSAPI encryption support from the remote server when set. By + default, clients will fall back to not using GSSAPI encryption if the + server does not support encryption through GSSAPI. + </para> Nitpick: the use of the <acronym> markup for GSSAPI may be more adapted (I know, the docs are a bit lazy on that). + 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); writev and iovec are not present on Windows, so this code would never compile there, and it does not sound that this patch is a reason sufficient enough to drop support of GSSAPI on Windows. +static ssize_t +be_gssapi_should_crypto(Port *port) +{ + if (port->gss->ctx == GSS_C_NO_CONTEXT) + return 0; + return gss_encrypt; +} This should return a boolean, gss_encrypt being one. + if (!gss_encrypt && port->hba->require_encrypt) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("GSSAPI encryption required from user \"%s\"", + port->user_name))); + } Could be clearer here, with some error detail message, like: "User has requested GSSAPI encryption, but server disallows it". +static void +assign_gss_encrypt(bool newval, void *extra) +{ + gss_encrypt = newval; +} Assigning a variable is done by the GUC machinery, you don't need that. + { + {"gss_encrypt", PGC_USERSET, CONN_AUTH_SECURITY, + gettext_noop("Require encryption for all GSSAPI connections."), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &gss_encrypt, false, check_gss_encrypt, assign_gss_encrypt, NULL + }, Hm. I think that this would be better as PGC_POSTMASTER, the idea behind this parameter being that the server enforces any connections from clients to be encrypted. Clients should not have the right to disable that at will depending on their session, and this is used in authentication code paths. Also, this parameter is not documented, the new parameters in pg_hba.conf and client-side are though. A consequence of that, is that you would not need the following thingy: +static bool +check_gss_encrypt(bool *newval, void **extra, GucSource source) +{ + if (MyProcPort && MyProcPort->hba && MyProcPort->hba->require_encrypt && + !*newval) + return false; + return true; +} So I'd suggest go with PGC_POSTMASTER first to simplify the code. We could always relax that later on as need be, but that's not something I think the code patch should bother much about. +/* + * Only consider encryption when GSS context is complete + */ +ssize_t +pg_GSS_should_crypto(PGconn *conn) +{ s/crypto/encrypt? + /* + * 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 support appname will also not support + * GSSAPI encryption. + */ + const char *sqlstate; Hm... I'm having second thoughts regarding gss_enc_require... This code path would happen with a 9.6 client and a ~9.5 server, it seems a bit treacherous to not ERROR here and fallback to an unencrypted connection, client want to have an encrypted connection at the end. I ran as well some tests, and I was able to crash the server. For example take this SQL sequence: CREATE TABLE aa (a int); INSERT INTO aa VALUES (generate_series(1,1000000)); COPY aa TO STDOUT; -- crash Which resulted in the following crash: 1046 AssertArg(MemoryContextIsValid(context)); (gdb) bt #0 0x0000000000980552 in repalloc (pointer=0x2aa1bb0, size=8192) at mcxt.c:1046 #1 0x00000000006b75d5 in enlargeStringInfo (str=0x2aa6b70, needed=7784) at stringinfo.c:286 #2 0x00000000006b7447 in appendBinaryStringInfo (str=0x2aa6b70, data=0x2b40f25 "\337\321\261\026jy\352\334#\275l)\030\021s\235\f;\237\336\222PZsd\025>ioS\313`9C\375\a\340Z\354E\355\235\276y\307)D\261\344$D\347\323\036\177S\265\374\373\332~\264\377\317\375<\017\330\214P\a\237\321\375\002$=6\326\263\265{\237\344\214\344.W\303\216\373\206\325\257E\223N\t\324\223\030\363\252&\374\241T\322<\343,\233\203\320\252\343\344\f\036*\274\311\066\206\n\337\300\320L,>-A\016D\346\263pv+A>y\324\254k\003)\264\212zc\344\n\223\224\211\243\"\224\343\241Q\264\233\223\303\"\b\275\026%\302\352\065]8\207\244\304\353\220p\364\272\240\307\247l\216}N\325\aUO6\322\352\273"..., datalen=7783) at stringinfo.c:213 #3 0x00000000006c8359 in be_gssapi_write (port=0x2aa31d0, ptr=0x2aa8b48, len=8192) at be-secure-gssapi.c:158 #4 0x00000000006b9697 in secure_write (port=0x2aa31d0, ptr=0x2aa8b48, len=8192) at be-secure.c:248 Which is actually related to your use of StringInfoData, pointing out that the buffer handling should be reworked. Also, to sum up with this patch, things behave like that: 1) server enforces encryption, client asks for encryption: all good, encryption is done. 2) server enforces encryption, client does not ask for it: encryption is enforced. Perhaps a FATAL on backend would be more adapted to let the caller know the incompatibility? I am afraid of cases where the client intentionally does *not* want encryption because of the overhead that it creates at low-level to encrypt/decrypt the messages. We'd lose control of that with this patch, and encryption can get really a lot slower.. 3) server does not enforce encryption, client asks for it: FATAL on backend side. 4) server does not enforce encryption, client not asking for it, no encryption. -#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 What makes you say that it is fine to remove that? If that's actually the case, we should remove it as part of a separate patch. -- Michael
Attachment
On Thu, Mar 31, 2016 at 4:48 PM, Michael Paquier <michael.paquier@gmail.com> wrote: > [long review] Note as well that I have switched the patch as "waiting on author" for the time being. Missing symbols on Windows as well as crashes are pointing out that this should be returned with feedback for now (sorry for the late reviews btw). -- Michael
Michael Paquier <michael.paquier@gmail.com> writes: > On Thu, Mar 31, 2016 at 2:14 PM, Michael Paquier > <michael.paquier@gmail.com> wrote: >> On Wed, Mar 30, 2016 at 1:01 PM, Robbie Harwood <rharwood@redhat.com> wrote: >>> A new version of my GSSAPI encryption patchset is available, both in >>> this email and on my github: >>> https://github.com/frozencemetery/postgres/tree/feature/gssencrypt9 >>> >> postgres.h should be declared *before* be-gssapi-common.h. As I >> suggested the refactoring and I guess you don't have a Windows >> environment at hand, attached is a patch that fixes the build with and >> without gssapi for Visual Studio, that can be applied on top of your >> 0001. Feel free to rebase using it. Thank you for the patch to fix 0001. There is basically no way I would have been able to make it work on Windows myself. > + 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); > > writev and iovec are not present on Windows, so this code would never > compile there, and it does not sound that this patch is a reason > sufficient enough to drop support of GSSAPI on Windows. Um. Okay. I guess on Windows I'll make two write calls then, since the only other option I see is to hit alloc again here. > + { > + {"gss_encrypt", PGC_USERSET, CONN_AUTH_SECURITY, > + gettext_noop("Require encryption for all GSSAPI connections."), > + NULL, > + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE > + }, > + &gss_encrypt, false, check_gss_encrypt, assign_gss_encrypt, NULL > + }, > > Hm. I think that this would be better as PGC_POSTMASTER, the idea > behind this parameter being that the server enforces any connections > from clients to be encrypted. Clients should not have the right to > disable that at will depending on their session, and this is used in > authentication code paths. Also, this parameter is not documented, the > new parameters in pg_hba.conf and client-side are though. Negative, setting PGC_POSTMASTER breaks the fallback support; I get "psql: FATAL: parameter "gss_encrypt" cannot be changed without restarting the server" when trying to connect a new client to a new server. I will add documentation. > + /* > + * 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 support appname will also not support > + * GSSAPI encryption. > + */ > + const char *sqlstate; > Hm... I'm having second thoughts regarding gss_enc_require... This > code path would happen with a 9.6 client and a ~9.5 server, it seems a > bit treacherous to not ERROR here and fallback to an unencrypted > connection, client want to have an encrypted connection at the end. I'm confused by what you're saying here. 1. If you have a 9.6 client and a 9.5 server, with no additional connection parameters the client will reconnect and getan unencrypted connection. This is the "fallback" support. 2. If the client passes gss_enc_require=1, then it will just fail because the server cannot provide encryption. > I ran as well some tests, and I was able to crash the server. For > example take this SQL sequence: > CREATE TABLE aa (a int); > INSERT INTO aa VALUES (generate_series(1,1000000)); > COPY aa TO STDOUT; -- crash > Which resulted in the following crash: > 1046 AssertArg(MemoryContextIsValid(context)); > (gdb) bt > #0 0x0000000000980552 in repalloc (pointer=0x2aa1bb0, size=8192) at mcxt.c:1046 > #1 0x00000000006b75d5 in enlargeStringInfo (str=0x2aa6b70, > needed=7784) at stringinfo.c:286 > #2 0x00000000006b7447 in appendBinaryStringInfo (str=0x2aa6b70, > data=0x2b40f25 > "\337\321\261\026jy\352\334#\275l)\030\021s\235\f;\237\336\222PZsd\025>ioS\313`9C\375\a\340Z\354E\355\235\276y\307)D\261\344$D\347\323\036\177S\265\374\373\332~\264\377\317\375<\017\330\214P\a\237\321\375\002$=6\326\263\265{\237\344\214\344.W\303\216\373\206\325\257E\223N\t\324\223\030\363\252&\374\241T\322<\343,\233\203\320\252\343\344\f\036*\274\311\066\206\n\337\300\320L,>-A\016D\346\263pv+A>y\324\254k\003)\264\212zc\344\n\223\224\211\243\"\224\343\241Q\264\233\223\303\"\b\275\026%\302\352\065]8\207\244\304\353\220p\364\272\240\307\247l\216}N\325\aUO6\322\352\273"..., > datalen=7783) at stringinfo.c:213 > #3 0x00000000006c8359 in be_gssapi_write (port=0x2aa31d0, > ptr=0x2aa8b48, len=8192) at be-secure-gssapi.c:158 > #4 0x00000000006b9697 in secure_write (port=0x2aa31d0, ptr=0x2aa8b48, > len=8192) at be-secure.c:248 > > Which is actually related to your use of StringInfoData, pointing out > that the buffer handling should be reworked. This crash is not deterministic. I do observe it though and will try to debug. Any tips on debugging this are appreciated. > 2) server enforces encryption, client does not ask for it: encryption > is enforced. Perhaps a FATAL on backend would be more adapted to let > the caller know the incompatibility? I am afraid of cases where the > client intentionally does *not* want encryption because of the > overhead that it creates at low-level to encrypt/decrypt the messages. > We'd lose control of that with this patch, and encryption can get > really a lot slower.. This is really late in the process to bring this up, but alright, let's talk about it. I think about this in a manner similar to HTTPS: if support for it is there, you don't want to have to have people think about using it, you want that security by default. Wherever possible, it should just happen. The overhead of reasonable encryption is in general quite low, even more so since Kerberos will be using symmetric cryptography (typically AES at the time of writing, which specifically has CPU support in many cases). Even running on something on lower-power hardware, you will typically notice other problems (especially since we're working on a performant database right now) before you notice performance impact due to encryption. To back that up, here's an article from a Google employee: https://www.imperialviolet.org/2010/06/25/overclocking-ssl.html To summarize, when they turned HTTPS on by default for GMail in 2010, no new hardware was required and the performance impact was strongly negligible. > -#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 > What makes you say that it is fine to remove that? If that's actually > the case, we should remove it as part of a separate patch. Please see earlier comments on David's review. I feel that part of a "GSSAPI auth cleanup" patch it is in the right place.
Robbie Harwood wrote: > Michael Paquier <michael.paquier@gmail.com> writes: > > + 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); > > > > writev and iovec are not present on Windows, so this code would never > > compile there, and it does not sound that this patch is a reason > > sufficient enough to drop support of GSSAPI on Windows. > > Um. Okay. I guess on Windows I'll make two write calls then, since the > only other option I see is to hit alloc again here. Hmm, I wouldn't push my luck by using writev here at all. We don't use writev/readv anywhere, and it's quite possible that they are not present on older Unixen which we still support. http://pubs.opengroup.org/onlinepubs/009695399/functions/writev.html says writev was introduced in "issue 4 version 2", which AFAICT is the 2004 version, but our baseline is SUSv2 (1997). So it's definitely not workable. > > + { > > + {"gss_encrypt", PGC_USERSET, CONN_AUTH_SECURITY, > > + gettext_noop("Require encryption for all GSSAPI connections."), > > + NULL, > > + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE > > + }, > > + &gss_encrypt, false, check_gss_encrypt, assign_gss_encrypt, NULL > > + }, Why is this marked NOT_IN_SAMPLE? -- Álvaro Herrera http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Alvaro Herrera <alvherre@2ndquadrant.com> writes: > Robbie Harwood wrote: >> Michael Paquier <michael.paquier@gmail.com> writes: > >> > + 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); >> > >> > writev and iovec are not present on Windows, so this code would never >> > compile there, and it does not sound that this patch is a reason >> > sufficient enough to drop support of GSSAPI on Windows. >> >> Um. Okay. I guess on Windows I'll make two write calls then, since the >> only other option I see is to hit alloc again here. > > Hmm, I wouldn't push my luck by using writev here at all. We don't use > writev/readv anywhere, and it's quite possible that they are not present > on older Unixen which we still support. > http://pubs.opengroup.org/onlinepubs/009695399/functions/writev.html > says writev was introduced in "issue 4 version 2", which AFAICT is the > 2004 version, but our baseline is SUSv2 (1997). So it's definitely not > workable. Understood. What do you suggest instead? To give some context here, writev() is being used here because I have a GSSAPI payload that is (effectively) opaque and need to include length in it. The only alternatives I can see are either allocating a new buffer and reading the payload + length into it (incurs additional memory overhead), or calling a write/send function twice (incurs syscall overhead at minimum). >> > + { >> > + {"gss_encrypt", PGC_USERSET, CONN_AUTH_SECURITY, >> > + gettext_noop("Require encryption for all GSSAPI connections."), >> > + NULL, >> > + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE >> > + }, >> > + &gss_encrypt, false, check_gss_encrypt, assign_gss_encrypt, NULL >> > + }, > > Why is this marked NOT_IN_SAMPLE? Well, because it's not something that's supposed to be set in the file (and indeed, you can't set it there, if I understand GUC_DISALLOW_IN_FILE). It's used only as a connection parameter, and I use its presence / absence for the client and server to negotiate GSSAPI encryption support.
Robbie Harwood <rharwood@redhat.com> writes: >>> + { >>> + {"gss_encrypt", PGC_USERSET, CONN_AUTH_SECURITY, >>> + gettext_noop("Require encryption for all GSSAPI connections."), >>> + NULL, >>> + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE >>> + }, >>> + &gss_encrypt, false, check_gss_encrypt, assign_gss_encrypt, NULL >>> + }, >> Why is this marked NOT_IN_SAMPLE? > Well, because it's not something that's supposed to be set in the file > (and indeed, you can't set it there, if I understand > GUC_DISALLOW_IN_FILE). It's used only as a connection parameter, and I > use its presence / absence for the client and server to negotiate GSSAPI > encryption support. If that's what it is, it seems fairly broken to have it connected up to a GUC variable. Especially one that's USERSET; some people will wonder why frobbing it with SET does nothing, and others will bitch that they think it should be superuser-only or some such. I'd keep it localized to the connection logic, myself. There's already logic in ProcessStartupPacket for connection options that aren't GUC variables, so I'd suggest adding another case there instead of pretending this is a settable GUC variable. regards, tom lane
On Fri, Apr 1, 2016 at 6:10 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > If that's what it is, it seems fairly broken to have it connected up to a > GUC variable. Especially one that's USERSET; some people will wonder why > frobbing it with SET does nothing, and others will bitch that they think > it should be superuser-only or some such. I'd keep it localized to the > connection logic, myself. There's already logic in ProcessStartupPacket > for connection options that aren't GUC variables, so I'd suggest adding > another case there instead of pretending this is a settable GUC variable. Argh, yes right. That's what we should look for. -- Michael
Hello friends, Here is another version of GSSAPI encryption support, both on this email and on my github: https://github.com/frozencemetery/postgres/tree/feature/gssencrypt10 This version is intended to address Michael's review: - Fixed Windows build. Thanks to Michael for patches. - Fixed buffering of large replies on the serverside. This should fix the traceback that was being seen. The issue had to do with the difference between the server and client calling conventions for the _read and _write functions. - gss_enc_require renamed to gss_require_encrypt. Slightly longer, but half the stomach churn. - Move gss_encrypt out of the GUCs and into connection-specific logic. Thanks to Tom Lane for pointing me in the right direction here. - Replace writev() with two calls to _raw_write(). I'm not attached to this design; if someone has a preference for allocating a buffer and making a single write from that, I could be persuaded. I don't know what the performance tradeoffs are. - Typo fixes. I need to figure out spellchecking in my editor. - More use of <acronym>. - Change _should_crypto() functions to return bool. Also rename them to be the _should_encrypt functions. - Error message cleanup. Thanks! From 945805d45e8021f92ad73518b3a74ac6bab89525 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. Thanks to Michael Paquier for the Windows fixes. --- 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 | 74 +++++++++++++++++++++++++++++++++ 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 ++++++++++ src/tools/msvc/Mkvcbuild.pm | 18 ++++++-- 12 files changed, 212 insertions(+), 113 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..dc27fa8 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,74 @@ +/*------------------------------------------------------------------------- + * + * 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 "postgres.h" + +#include "libpq/be-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_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 */ diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index e4fb44e..2fa2509 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -155,12 +155,17 @@ sub mkvcbuild $postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); $postgres->FullExportDLL('postgres.lib'); - # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c - # if building without OpenSSL + # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c + # if building without OpenSSL, and be-gssapi-common.c when building with + # GSSAPI. if (!$solution->{options}->{openssl}) { $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', 'src/backend/snowball'); @@ -210,12 +215,17 @@ sub mkvcbuild 'src/interfaces/libpq/libpq.rc'); $libpq->AddReference($libpgport); - # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c - # if building without OpenSSL + # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c + # if building without OpenSSL, and fe-gssapi-common.c when building with + # GSSAPI if (!$solution->{options}->{openssl}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + } my $libpqwalreceiver = $solution->AddProject('libpqwalreceiver', 'dll', '', -- 2.8.0.rc3 From 2dbd5df611eb007121580d5e92d8d805520bc082 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 | 14 +- doc/src/sgml/libpq.sgml | 12 ++ doc/src/sgml/runtime.sgml | 22 ++- src/backend/libpq/Makefile | 2 +- src/backend/libpq/auth.c | 8 +- src/backend/libpq/be-secure-gssapi.c | 321 +++++++++++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 ++ src/backend/libpq/hba.c | 9 + src/backend/postmaster/postmaster.c | 16 ++ src/backend/utils/init/postinit.c | 15 +- src/backend/utils/misc/guc.c | 2 - src/include/libpq/hba.h | 1 + src/include/libpq/libpq-be.h | 10 + src/include/libpq/libpq.h | 2 + src/interfaces/libpq/Makefile | 2 +- src/interfaces/libpq/fe-connect.c | 99 +++++++++- 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 | 330 ++++++++++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-int.h | 19 +- src/tools/msvc/Mkvcbuild.pm | 8 +- 23 files changed, 924 insertions(+), 18 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..f0d2348 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -915,7 +915,7 @@ 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> or <acronym>GSSAPI</acronym> encryption is in use. </para> <para> @@ -1046,6 +1046,18 @@ omicron bryanh guest1 </para> </listitem> </varlistentry> + + <varlistentry id="gssapi-require-encrypt" xreflabel="require_encrypt"> + <term><literal>require_encrypt</literal></term> + <listitem> + <para> + Require <acronym>GSSAPI</acronym> encryption. Defaults to off, which + enables <acronym>GSSAPI</acronym> encryption only when both available + and requested to maintain compatibility with old clients. This + setting should be enabled 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..5a5dc8f 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-require_encrypt" xreflabel="gss_require_encrypt"> + <term><literal>gss_require_encrypt</literal></term> + <listitem> + <para> + Require <acronym>GSSAPI</acronym> encryption support from the remote + server when set. By default, clients will fall back to not using + <acronym>GSSAPI</acronym> encryption if the server does not support + encryption through <acronym>GSSAPI</acronym>. + </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..d5c2df0 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,17 @@ 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 + called <xref linkend="gssapi-require-encrypt">; unless set, 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 + called <xref linkend="libpq-connect-gss-require-encrypt">. + </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..94d95bd 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -596,10 +596,12 @@ 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. However, if we are doing GSSAPI encryption, that request + * must go out immediately to ensure that all messages which follow the + * AUTH_REQ_OK are not grouped with it and can therefore be encrypted. */ - if (areq != AUTH_REQ_OK) + if (areq != AUTH_REQ_OK || port->gss != NULL) 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..a4d5f71 --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,321 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gssapi.c + * GSSAPI encryption support + * + * Portions Copyright (c) 2016, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "libpq/be-gssapi-common.h" + +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" + +/* + * Wrapper function indicating whether we are currently performing GSSAPI + * connection encryption. + * + * gss->encrypt is set when connection parameters are processed, which happens + * immediately after AUTH_REQ_OK is sent. + */ +static bool +be_gssapi_should_encrypt(Port *port) +{ + if (port->gss->ctx == GSS_C_NO_CONTEXT) + return false; + return port->gss->encrypt; +} + +/* + * Send a message along the connection, possibly encrypting using GSSAPI. + * + * If we are not encrypting at the moment, we send data plaintext and follow + * the calling conventions of secure_raw_write. Otherwise, the following + * hold: Incomplete writes are buffered using a dedicated StringInfo in the + * port structure. On failure, we return -1; on partial write, we return -1 + * and set errno=EWOULDBLOCK since the translation between plaintext and + * encrypted is indeterminate; on completed write, we return the total number + * of bytes written including any buffering that occurred. Behavior when + * called with a new pointer/length combination after an incomplete write is + * undefined. + */ +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]; + + ret = be_gssapi_should_encrypt(port); + + if (ret == -1) + return -1; + else if (ret == 0) + return secure_raw_write(port, ptr, len); + + /* send any data we have buffered */ + if (port->gss->writebuf.len != 0) + { + ret = secure_raw_write( + port, + port->gss->writebuf.data + port->gss->writebuf.cursor, + port->gss->writebuf.len - port->gss->writebuf.cursor); + if (ret < 0) + return ret; + + /* update and possibly clear buffer state */ + 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 */ + errno = EWOULDBLOCK; + return -1; + } + + /* encrypt the message */ + 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; + } + + /* format for on-wire: 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + memcpy(lenbuf, &netlen, 4); + + ret = secure_raw_write(port, lenbuf, 4); + if (ret < 0) + { + ereport(FATAL, (errmsg("Failed to send entire GSSAPI blob"))); + return ret; + } + else if (ret < 4) + { + appendBinaryStringInfo(&port->gss->writebuf, lenbuf + ret, 4 - ret); + ret = 0; /* just buffer everything else */ + } + + if (ret != 0) + ret = secure_raw_write(port, output.value, output.length); + + if (ret < 0) + { + ereport(FATAL, (errmsg("Failed to send entire GSSAPI blob"))); + return ret; + } + else if (ret == output.length) + { + /* + * 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; + } + + appendBinaryStringInfo(&port->gss->writebuf, (char *)output.value + ret, + output.length - ret); + + /* set return so that we get retried when the socket becomes writable */ + ret = -1; + errno = EWOULDBLOCK; + cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + + return ret; +} + +/* + * Wrapper function for buffering decrypted data. + * + * This allows us to present a stream-like interface to secure_read. For a + * description of buffering behavior, see comment at be_gssapi_read. + */ +static ssize_t +be_gssapi_read_from_buffer(Port *port, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* Is any data available? */ + if (port->gss->buf.len > 4 && port->gss->buf.cursor < port->gss->buf.len) + { + /* clamp length */ + 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 all data has been read, reset buffer */ + 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_encrypt(port); + + if (ret == -1) + return -1; + else if (ret == 0) + return secure_raw_read(port, ptr, len); + + /* ensure proper behavior under recursion */ + if (len == 0) + return 0; + + /* report any buffered data, then recur */ + 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; + + /* write length to buffer */ + port->gss->buf.len += ret; + port->gss->buf.data[port->gss->buf.len] = '\0'; + if (port->gss->buf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + + /* 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); + + /* read the packet into our buffer */ + 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) + { + errno = EWOULDBLOCK; + return -1; + } + + /* decrypt the packet */ + 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; + } + + /* load decrypted packet into our buffer, then recur */ + 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..62cfd4c 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -2036,6 +2036,10 @@ retry1: port->user_name = pstrdup(valptr); else if (strcmp(nameptr, "options") == 0) port->cmdline_options = pstrdup(valptr); +#ifdef ENABLE_GSS + else if (strcmp(nameptr, "gss_encrypt") == 0) + port->gss->gss_encrypt = pstrdup(valptr); +#endif else if (strcmp(nameptr, "replication") == 0) { /* @@ -2355,6 +2359,10 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); +#endif #endif return port; @@ -2371,7 +2379,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..de1552a 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,19 @@ process_startup_options(Port *port, bool am_superuser) SetConfigOption(name, value, gucctx, PGC_S_CLIENT); } + +#ifdef ENABLE_GSS + /* delay processing until after AUTH_REQ_OK has been sent */ + if (port->gss->gss_encrypt != NULL) + port->gss->encrypt = !strcmp(port->gss->gss_encrypt, "on"); + + if (!port->gss->encrypt && port->hba->require_encrypt) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("User \"%s\" is required to use GSSAPI encryption but did not request it", + port->user_name))); + } +#endif } /* diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index ea5a09a..c945e88 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -186,7 +186,6 @@ static const char *show_log_file_mode(void); static ConfigVariable *ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel); - /* * Options for enum values defined in this module. * @@ -498,7 +497,6 @@ static bool assert_enabled; /* should be static, but commands/variable.c needs to get at this */ char *role_string; - /* * Displayable names for context types (enum GucContext) * 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..71c1f95 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,10 @@ 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 */ + char *gss_encrypt; /* GSSAPI encryption literal request */ + bool encrypt; /* GSSAPI encryption has started */ #endif } pg_gssinfo; #endif @@ -214,6 +219,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..8c154ed 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_require_encrypt", "GSS_REQUIRE_ENCRYPT", "0", NULL, + "Require-GSS-encryption", "", 1, /* should be '0' or '1' */ + offsetof(struct pg_conn, gss_require_encrypt)}, +#endif + {"replication", NULL, NULL, NULL, "Replication", "D", 5, offsetof(struct pg_conn, replication)}, @@ -2518,6 +2530,38 @@ 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_encrypt(conn) && conn->inEnd > conn->inStart) + { + /* + * 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. + */ + int n; + + appendBinaryPQExpBuffer(&conn->gbuf, + conn->inBuffer + conn->inStart, + conn->inEnd - conn->inStart); + conn->inEnd = conn->inStart; + + /* Will not block on nonblocking sockets */ + n = pg_GSS_read(conn, conn->inBuffer + conn->inEnd, + conn->inBufSize - conn->inEnd); + /* + * If n < 0, then this wasn't a full request and + * either more data will be available later to + * complete it or we will error out then. + */ + if (n > 0) + conn->inEnd += n; + } +#endif + /* * Set asyncStatus so that PQgetResult will think that * what comes back next is the result of a query. See @@ -2558,6 +2602,47 @@ 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_require_encrypt != '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 support appname will also not support + * GSSAPI encryption. + */ + 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; + conn->gss_auth_done = false; + conn->gss_decrypted = false; + + /* Must drop the old connection */ + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + gss_delete_sec_context(&minor, &conn->gctx, + GSS_C_NO_BUFFER); + + resetPQExpBuffer(&conn->gbuf); + resetPQExpBuffer(&conn->gwritebuf); + goto keep_going; + } + } + else if (*conn->gss_require_encrypt == '1') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("Server does not support required GSSAPI encryption\n")); + } +#endif else if (conn->send_appname && (conn->appname || conn->fbappname)) { @@ -2575,7 +2660,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; @@ -2798,6 +2883,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 +3003,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 +3112,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..22b9104 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 + */ +bool +pg_GSS_should_encrypt(PGconn *conn) +{ + if (conn->gctx == GSS_C_NO_CONTEXT) + return false; + 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..e23f524 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); +bool pg_GSS_should_encrypt(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..322b3cc --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,330 @@ +/*------------------------------------------------------------------------- + * + * 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" + +/* + * Send a message along the connection, possibly using GSSAPI. + * + * If not encrypting at the call-time, we send plaintext following calling + * conventions of pqsecure_raw_write. Partial writes are supported using a + * dedicated PQExpBuffer in conn. A partial write will return 0; otherwise, + * we return -1 (for error) or the number of total bytes written in the write + * of the current ptr. Calling with a new value of ptr after a partial write + * is undefined. + */ +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]; + + ret = pg_GSS_should_encrypt(conn); + + if (ret == -1) + return -1; + else if (ret == 0) + return pqsecure_raw_write(conn, ptr, len); + + /* send any data we have buffered */ + if (conn->gwritebuf.len != 0) + { + ret = pqsecure_raw_write(conn, + conn->gwritebuf.data + conn->gwritecurs, + conn->gwritebuf.len - conn->gwritecurs); + if (ret < 0) + return ret; + + conn->gwritecurs += ret; + + /* update and possibly clear buffer state */ + 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; + } + + /* encrypt the message */ + 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; + } + + /* format for on-wire: 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + memcpy(lenbuf, &netlen, 4); + + ret = pqsecure_raw_write(conn, lenbuf, 4); + if (ret < 0) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI write failed to send everything\n")); + ret = -1; + goto cleanup; + } + + /* buffer any leftovers from a partial write */ + if (ret < 4) + { + appendBinaryPQExpBuffer(&conn->gwritebuf, lenbuf + ret, 4 - ret); + ret = 0; /* just buffer everything else */ + } + + if (ret != 0) + ret = pqsecure_raw_write(conn, output.value, output.length); + + if (ret == output.length) + { + ret = len; /* all requested bytes now written */ + goto cleanup; + } + + 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; +} + +/* + * Wrapper function for buffering decrypted data. + * + * This allows us to present a stream-like interface to pqsecure_read. For a + * description of buffering, see comment at be_gssapi_read (in be-gssapi.c). + */ +static ssize_t +pg_GSS_read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* Is any data available? */ + if (conn->gcursor < conn->gbuf.len) + { + /* clamp length */ + 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 all data has been read, reset buffer */ + 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_encrypt(conn); + + if (ret == -1) + return -1; + else if (ret == 0) + return pqsecure_raw_read(conn, ptr, len); + + /* ensure proper behavior under recursion */ + if (len == 0) + return 0; + + /* report any buffered data, then recur */ + if (conn->gcursor > 0) + { + ret = pg_GSS_read_from_buffer(conn, ptr, len); + + /* Pass up error message fragments. See comment below. */ + if (!conn->gss_decrypted && conn->gcursor == conn->gbuf.len) + { + /* call _raw_read to get any remaining parts of the message */ + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = 0; + } + + 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; + + /* write length to buffer */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + return 0; + } + + /* + * We can receive error messages from old servers that don't support + * GSSAPI encryption at this time. They need to be passed up so that we + * can potentially reconnect. + * + * This limits the server's first reply to not be between 1157627904 + * (about 2**30) and 1174405119, which are both over a gigabyte in size. + * If the server sends a connection parameter status message of this size, + * there are other problems present. + */ + if (!conn->gss_decrypted && conn->gbuf.data[0] == 'E') + { + ret = pg_GSS_read_from_buffer(conn, ptr, len); + if (conn->gcursor == conn->gbuf.len) + { + /* Call _raw_read to get any remaining parts of the message */ + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = 0; + } + return ret; + } + + /* 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; + } + + /* load any remaining parts of the packet into our buffer */ + if (conn->gbuf.len - 4 < input.length) + { + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + input.length - conn->gbuf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < input.length) + return 0; + } + + /* decrypt the packet */ + 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->gss_decrypted = true; + + /* load decrypted packet into our buffer, then recur */ + 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..b00b03b 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,14 @@ 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 */ + bool gss_decrypted; /* GSS decrypted first message */ + char *gss_require_encrypt; /* GSS encryption required */ #endif #ifdef ENABLE_SSPI @@ -620,7 +629,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 +651,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 */ diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 2fa2509..5e30739 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -156,8 +156,7 @@ sub mkvcbuild $postgres->FullExportDLL('postgres.lib'); # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c - # if building without OpenSSL, and be-gssapi-common.c when building with - # GSSAPI. + # if building without OpenSSL, and GSSAPI files when building with it. if (!$solution->{options}->{openssl}) { $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); @@ -165,6 +164,7 @@ sub mkvcbuild if (!$solution->{options}->{gss}) { $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + $postgres->RemoveFile('src/backend/libpq/be-secure-gssapi.c'); } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', @@ -216,8 +216,7 @@ sub mkvcbuild $libpq->AddReference($libpgport); # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c - # if building without OpenSSL, and fe-gssapi-common.c when building with - # GSSAPI + # if building without OpenSSL, and GSSAPI-only files when building with it. if (!$solution->{options}->{openssl}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); @@ -225,6 +224,7 @@ sub mkvcbuild if (!$solution->{options}->{gss}) { $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c'); } my $libpqwalreceiver = -- 2.8.0.rc3 From 6cd8e3756766ae6ae568659bdb6cae8332ad3f2c 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. Finally, resolve a pre-existing action item for acquiring all possible GSSAPI error messages on the backend, rather than just the first major and first minor statuses. --- src/backend/libpq/auth.c | 20 ++++++++++++- src/backend/libpq/be-gssapi-common.c | 50 ++++++++++++++++----------------- src/interfaces/libpq/fe-auth.c | 19 +++++++++++-- src/interfaces/libpq/fe-gssapi-common.c | 11 -------- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 94d95bd..661228e 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -719,7 +719,8 @@ pg_GSS_recvauth(Port *port) OM_uint32 maj_stat, min_stat, lmin_s, - gflags; + gflags, + target_flags; int mtype; int ret; StringInfoData buf; @@ -875,6 +876,23 @@ pg_GSS_recvauth(Port *port) } /* + * GSS_C_REPLAY_FLAG (request replay detection) and GSS_C_SEQUENCE_FLAG + * (out-of-sequence detection) are missing for compatibility with older + * clients which do not request these flags. They should be added in as + * soon as we no longer care about pre-9.6 clients. + * + * Newer clients will request both GSS_C_REPLAY_FLAGS and + * GSS_C_SEQUENCE_FLAGS as well as the flags we check for below, so lack + * of these two flags is not the end of the world. + */ + target_flags = GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; + if ((gflags & target_flags) != target_flags) + { + ereport(FATAL, (errmsg("GSSAPI did not provide required flags"))); + return STATUS_ERROR; + } + + /* * GSS_S_COMPLETE indicates that authentication is now complete. * * Get the name of the user that authenticated, and compare it to the pg diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c index dc27fa8..6cf5513 100644 --- a/src/backend/libpq/be-gssapi-common.c +++ b/src/backend/libpq/be-gssapi-common.c @@ -17,17 +17,6 @@ #include "libpq/be-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_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) { @@ -36,31 +25,40 @@ pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat) msg_ctx; char msg_major[128], msg_minor[128]; + short i; + + gmsg.value = NULL; + gmsg.length = 0; /* 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); + i = 0; + do + { + gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(msg_major + i, gmsg.value, sizeof(msg_major) - i); + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < sizeof(msg_major)); - if (msg_ctx) - - /* - * More than one message available. XXX: Should we loop and read all - * messages? (same below) - */ + if (msg_ctx || i == sizeof(msg_major)) 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); + i = 0; + do + { + gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(msg_minor + i, gmsg.value, sizeof(msg_minor) - i); + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < sizeof(msg_minor)); - if (msg_ctx) + if (msg_ctx || i == sizeof(msg_minor)) ereport(WARNING, (errmsg_internal("incomplete GSS minor error report"))); 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 22b9104..446a669 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.8.0.rc3
Attachment
On Fri, Apr 1, 2016 at 12:31 PM, Robbie Harwood <rharwood@redhat.com> wrote: > - Fixed Windows build. Thanks to Michael for patches. This looks fine. I am not seeing build failures now. > - Fixed buffering of large replies on the serverside. This should fix > the traceback that was being seen. The issue had to do with the > difference between the server and client calling conventions for the > _read and _write functions. This does not fix the issue. I am still able to crash the server with the same trace. > - Move gss_encrypt out of the GUCs and into connection-specific logic. > Thanks to Tom Lane for pointing me in the right direction here. + /* delay processing until after AUTH_REQ_OK has been sent */ + if (port->gss->gss_encrypt != NULL) + port->gss->encrypt = !strcmp(port->gss->gss_encrypt, "on"); You should use parse_bool here, because contrary to libpq, clients should be able to use other values like "1", "0", "off", 'N', 'Y', etc. > - Replace writev() with two calls to _raw_write(). I'm not attached to > this design; if someone has a preference for allocating a buffer and > making a single write from that, I could be persuaded. I don't know > what the performance tradeoffs are. Relying on pqsecure_raw_write and pqsecure_raw_read is a better bet IMO, there is already some low-level error handling. static ConfigVariable *ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel); -/* Spurious noise. -- Michael
Michael Paquier <michael.paquier@gmail.com> writes: > On Fri, Apr 1, 2016 at 12:31 PM, Robbie Harwood <rharwood@redhat.com> wrote: > >> - Fixed buffering of large replies on the serverside. This should fix >> the traceback that was being seen. The issue had to do with the >> difference between the server and client calling conventions for the >> _read and _write functions. > > This does not fix the issue. I am still able to crash the server with > the same trace. Interesting. I guess I'll keep trying to reproduce this. >> - Move gss_encrypt out of the GUCs and into connection-specific logic. >> Thanks to Tom Lane for pointing me in the right direction here. > > + /* delay processing until after AUTH_REQ_OK has been sent */ > + if (port->gss->gss_encrypt != NULL) > + port->gss->encrypt = !strcmp(port->gss->gss_encrypt, "on"); > > You should use parse_bool here, because contrary to libpq, clients > should be able to use other values like "1", "0", "off", 'N', 'Y', > etc. Missed that function somehow. Will fix. >> - Replace writev() with two calls to _raw_write(). I'm not attached to >> this design; if someone has a preference for allocating a buffer and >> making a single write from that, I could be persuaded. I don't know >> what the performance tradeoffs are. > > Relying on pqsecure_raw_write and pqsecure_raw_read is a better bet > IMO, there is already some low-level error handling. Indeed, it looks like it should especially lead to better behavior on Windows. > static ConfigVariable *ProcessConfigFileInternal(GucContext context, > bool applySettings, int elevel); > > - > /* > > Spurious noise. Indeed, will fix.
Hello friends, Song and dance, here's v11 both here and on my github: https://github.com/frozencemetery/postgres/tree/feature/gssencrypt11 Changes from v10: - Attempt to address a crash Michael is observing by switching to using the StringInfo/pqExpBuffer management functions over my own code as much as possible. Michael, if this doesn't fix it, I'm out of ideas. Since I still can't reproduce this locally (left a client machine and a process on the same machine retrying for over an hour on your test case and didn't see it), could you provide me with some more information on why repalloc is complaining? Is this a low memory situation where alloc might have failed? What's your setup look like? That pointer looks like it's on the heap, is that correct? I really don't have a handle on what's gone wrong here. - Switch to using parse_bool for handling gss_encrypt. - Remove accidental whitespace change. Thanks! From 945805d45e8021f92ad73518b3a74ac6bab89525 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. Thanks to Michael Paquier for the Windows fixes. --- 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 | 74 +++++++++++++++++++++++++++++++++ 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 ++++++++++ src/tools/msvc/Mkvcbuild.pm | 18 ++++++-- 12 files changed, 212 insertions(+), 113 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..dc27fa8 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,74 @@ +/*------------------------------------------------------------------------- + * + * 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 "postgres.h" + +#include "libpq/be-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_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 */ diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index e4fb44e..2fa2509 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -155,12 +155,17 @@ sub mkvcbuild $postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); $postgres->FullExportDLL('postgres.lib'); - # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c - # if building without OpenSSL + # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c + # if building without OpenSSL, and be-gssapi-common.c when building with + # GSSAPI. if (!$solution->{options}->{openssl}) { $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', 'src/backend/snowball'); @@ -210,12 +215,17 @@ sub mkvcbuild 'src/interfaces/libpq/libpq.rc'); $libpq->AddReference($libpgport); - # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c - # if building without OpenSSL + # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c + # if building without OpenSSL, and fe-gssapi-common.c when building with + # GSSAPI if (!$solution->{options}->{openssl}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + } my $libpqwalreceiver = $solution->AddProject('libpqwalreceiver', 'dll', '', -- 2.8.0.rc3 From 9bc68d51cd612adf7a96168340377b6fcdb4ff45 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 | 14 +- doc/src/sgml/libpq.sgml | 12 ++ doc/src/sgml/runtime.sgml | 22 ++- src/backend/libpq/Makefile | 2 +- src/backend/libpq/auth.c | 8 +- src/backend/libpq/be-secure-gssapi.c | 316 ++++++++++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 ++ src/backend/libpq/hba.c | 9 + src/backend/postmaster/postmaster.c | 16 ++ src/backend/utils/init/postinit.c | 16 +- src/include/libpq/hba.h | 1 + src/include/libpq/libpq-be.h | 10 + src/include/libpq/libpq.h | 2 + src/interfaces/libpq/Makefile | 2 +- src/interfaces/libpq/fe-connect.c | 99 +++++++++- 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 | 330 ++++++++++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-int.h | 19 +- src/tools/msvc/Mkvcbuild.pm | 8 +- 22 files changed, 920 insertions(+), 16 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..f0d2348 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -915,7 +915,7 @@ 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> or <acronym>GSSAPI</acronym> encryption is in use. </para> <para> @@ -1046,6 +1046,18 @@ omicron bryanh guest1 </para> </listitem> </varlistentry> + + <varlistentry id="gssapi-require-encrypt" xreflabel="require_encrypt"> + <term><literal>require_encrypt</literal></term> + <listitem> + <para> + Require <acronym>GSSAPI</acronym> encryption. Defaults to off, which + enables <acronym>GSSAPI</acronym> encryption only when both available + and requested to maintain compatibility with old clients. This + setting should be enabled 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..5a5dc8f 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-require_encrypt" xreflabel="gss_require_encrypt"> + <term><literal>gss_require_encrypt</literal></term> + <listitem> + <para> + Require <acronym>GSSAPI</acronym> encryption support from the remote + server when set. By default, clients will fall back to not using + <acronym>GSSAPI</acronym> encryption if the server does not support + encryption through <acronym>GSSAPI</acronym>. + </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..d5c2df0 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,17 @@ 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 + called <xref linkend="gssapi-require-encrypt">; unless set, 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 + called <xref linkend="libpq-connect-gss-require-encrypt">. + </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..94d95bd 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -596,10 +596,12 @@ 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. However, if we are doing GSSAPI encryption, that request + * must go out immediately to ensure that all messages which follow the + * AUTH_REQ_OK are not grouped with it and can therefore be encrypted. */ - if (areq != AUTH_REQ_OK) + if (areq != AUTH_REQ_OK || port->gss != NULL) 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..144c2bb --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,316 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gssapi.c + * GSSAPI encryption support + * + * Portions Copyright (c) 2016, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "libpq/be-gssapi-common.h" + +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" + +/* + * Wrapper function indicating whether we are currently performing GSSAPI + * connection encryption. + * + * gss->encrypt is set when connection parameters are processed, which happens + * immediately after AUTH_REQ_OK is sent. + */ +static bool +be_gssapi_should_encrypt(Port *port) +{ + if (port->gss->ctx == GSS_C_NO_CONTEXT) + return false; + return port->gss->encrypt; +} + +/* + * Send a message along the connection, possibly encrypting using GSSAPI. + * + * If we are not encrypting at the moment, we send data plaintext and follow + * the calling conventions of secure_raw_write. Otherwise, the following + * hold: Incomplete writes are buffered using a dedicated StringInfo in the + * port structure. On failure, we return -1; on partial write, we return -1 + * and set errno=EWOULDBLOCK since the translation between plaintext and + * encrypted is indeterminate; on completed write, we return the total number + * of bytes written including any buffering that occurred. Behavior when + * called with a new pointer/length combination after an incomplete write is + * undefined. + */ +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]; + + ret = be_gssapi_should_encrypt(port); + + if (ret == -1) + return -1; + else if (ret == 0) + return secure_raw_write(port, ptr, len); + + /* send any data we have buffered */ + if (port->gss->writebuf.len != 0) + { + ret = secure_raw_write( + port, + port->gss->writebuf.data + port->gss->writebuf.cursor, + port->gss->writebuf.len - port->gss->writebuf.cursor); + if (ret < 0) + return ret; + + /* update and possibly clear buffer state */ + port->gss->writebuf.cursor += ret; + + if (port->gss->writebuf.cursor == port->gss->writebuf.len) + { + resetStringInfo(&port->gss->writebuf); + + /* the entire request has now been written */ + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; + } + + /* encrypt the message */ + 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; + } + + /* format for on-wire: 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + memcpy(lenbuf, &netlen, 4); + + ret = secure_raw_write(port, lenbuf, 4); + if (ret < 0) + { + ereport(FATAL, (errmsg("Failed to send entire GSSAPI blob"))); + return ret; + } + else if (ret < 4) + { + appendBinaryStringInfo(&port->gss->writebuf, lenbuf + ret, 4 - ret); + ret = 0; /* just buffer everything else */ + } + + if (ret != 0) + ret = secure_raw_write(port, output.value, output.length); + + if (ret < 0) + { + ereport(FATAL, (errmsg("Failed to send entire GSSAPI blob"))); + return ret; + } + else if (ret == output.length) + { + /* + * 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; + } + + appendBinaryStringInfo(&port->gss->writebuf, (char *)output.value + ret, + output.length - ret); + + /* set return so that we get retried when the socket becomes writable */ + ret = -1; + errno = EWOULDBLOCK; + cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + + return ret; +} + +/* + * Wrapper function for buffering decrypted data. + * + * This allows us to present a stream-like interface to secure_read. For a + * description of buffering behavior, see comment at be_gssapi_read. + */ +static ssize_t +be_gssapi_read_from_buffer(Port *port, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* Is any data available? */ + if (port->gss->buf.len > 4 && port->gss->buf.cursor < port->gss->buf.len) + { + /* clamp length */ + 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 all data has been read, reset buffer */ + if (port->gss->buf.cursor == port->gss->buf.len) + resetStringInfo(&port->gss->buf); + + 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_encrypt(port); + + if (ret == -1) + return -1; + else if (ret == 0) + return secure_raw_read(port, ptr, len); + + /* ensure proper behavior under recursion */ + if (len == 0) + return 0; + + /* report any buffered data, then recur */ + 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; + + /* write length to buffer */ + port->gss->buf.len += ret; + port->gss->buf.data[port->gss->buf.len] = '\0'; + if (port->gss->buf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + + /* 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); + + /* read the packet into our buffer */ + 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) + { + errno = EWOULDBLOCK; + return -1; + } + + /* decrypt the packet */ + 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; + } + + /* load decrypted packet into our buffer, then recur */ + resetStringInfo(&port->gss->buf); + 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..62cfd4c 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -2036,6 +2036,10 @@ retry1: port->user_name = pstrdup(valptr); else if (strcmp(nameptr, "options") == 0) port->cmdline_options = pstrdup(valptr); +#ifdef ENABLE_GSS + else if (strcmp(nameptr, "gss_encrypt") == 0) + port->gss->gss_encrypt = pstrdup(valptr); +#endif else if (strcmp(nameptr, "replication") == 0) { /* @@ -2355,6 +2359,10 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); +#endif #endif return port; @@ -2371,7 +2379,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..15b1f2c 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" @@ -50,6 +50,7 @@ #include "storage/smgr.h" #include "tcop/tcopprot.h" #include "utils/acl.h" +#include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/memutils.h" @@ -1087,6 +1088,19 @@ process_startup_options(Port *port, bool am_superuser) SetConfigOption(name, value, gucctx, PGC_S_CLIENT); } + +#ifdef ENABLE_GSS + /* delay processing until after AUTH_REQ_OK has been sent */ + if (port->gss->gss_encrypt != NULL) + parse_bool(port->gss->gss_encrypt, &port->gss->encrypt); + + if (!port->gss->encrypt && port->hba->require_encrypt) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("User \"%s\" is required to use GSSAPI encryption but did not request it", + port->user_name))); + } +#endif } /* 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..71c1f95 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,10 @@ 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 */ + char *gss_encrypt; /* GSSAPI encryption literal request */ + bool encrypt; /* GSSAPI encryption has started */ #endif } pg_gssinfo; #endif @@ -214,6 +219,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..8c154ed 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_require_encrypt", "GSS_REQUIRE_ENCRYPT", "0", NULL, + "Require-GSS-encryption", "", 1, /* should be '0' or '1' */ + offsetof(struct pg_conn, gss_require_encrypt)}, +#endif + {"replication", NULL, NULL, NULL, "Replication", "D", 5, offsetof(struct pg_conn, replication)}, @@ -2518,6 +2530,38 @@ 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_encrypt(conn) && conn->inEnd > conn->inStart) + { + /* + * 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. + */ + int n; + + appendBinaryPQExpBuffer(&conn->gbuf, + conn->inBuffer + conn->inStart, + conn->inEnd - conn->inStart); + conn->inEnd = conn->inStart; + + /* Will not block on nonblocking sockets */ + n = pg_GSS_read(conn, conn->inBuffer + conn->inEnd, + conn->inBufSize - conn->inEnd); + /* + * If n < 0, then this wasn't a full request and + * either more data will be available later to + * complete it or we will error out then. + */ + if (n > 0) + conn->inEnd += n; + } +#endif + /* * Set asyncStatus so that PQgetResult will think that * what comes back next is the result of a query. See @@ -2558,6 +2602,47 @@ 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_require_encrypt != '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 support appname will also not support + * GSSAPI encryption. + */ + 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; + conn->gss_auth_done = false; + conn->gss_decrypted = false; + + /* Must drop the old connection */ + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + gss_delete_sec_context(&minor, &conn->gctx, + GSS_C_NO_BUFFER); + + resetPQExpBuffer(&conn->gbuf); + resetPQExpBuffer(&conn->gwritebuf); + goto keep_going; + } + } + else if (*conn->gss_require_encrypt == '1') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("Server does not support required GSSAPI encryption\n")); + } +#endif else if (conn->send_appname && (conn->appname || conn->fbappname)) { @@ -2575,7 +2660,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; @@ -2798,6 +2883,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 +3003,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 +3112,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..22b9104 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 + */ +bool +pg_GSS_should_encrypt(PGconn *conn) +{ + if (conn->gctx == GSS_C_NO_CONTEXT) + return false; + 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..e23f524 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); +bool pg_GSS_should_encrypt(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..7071e5a --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,330 @@ +/*------------------------------------------------------------------------- + * + * 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" + +/* + * Send a message along the connection, possibly using GSSAPI. + * + * If not encrypting at the call-time, we send plaintext following calling + * conventions of pqsecure_raw_write. Partial writes are supported using a + * dedicated PQExpBuffer in conn. A partial write will return 0; otherwise, + * we return -1 (for error) or the number of total bytes written in the write + * of the current ptr. Calling with a new value of ptr after a partial write + * is undefined. + */ +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]; + + ret = pg_GSS_should_encrypt(conn); + + if (ret == -1) + return -1; + else if (ret == 0) + return pqsecure_raw_write(conn, ptr, len); + + /* send any data we have buffered */ + if (conn->gwritebuf.len != 0) + { + ret = pqsecure_raw_write(conn, + conn->gwritebuf.data + conn->gwritecurs, + conn->gwritebuf.len - conn->gwritecurs); + if (ret < 0) + return ret; + + conn->gwritecurs += ret; + + /* update and possibly clear buffer state */ + if (conn->gwritecurs == conn->gwritebuf.len) + { + resetPQExpBuffer(&conn->gwritebuf); + conn->gwritecurs = 0; + + /* The entire request has now been written */ + return len; + } + + /* need to be called again */ + return 0; + } + + /* encrypt the message */ + 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; + } + + /* format for on-wire: 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + memcpy(lenbuf, &netlen, 4); + + ret = pqsecure_raw_write(conn, lenbuf, 4); + if (ret < 0) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI write failed to send everything\n")); + ret = -1; + goto cleanup; + } + + /* buffer any leftovers from a partial write */ + if (ret < 4) + { + appendBinaryPQExpBuffer(&conn->gwritebuf, lenbuf + ret, 4 - ret); + ret = 0; /* just buffer everything else */ + } + + if (ret != 0) + ret = pqsecure_raw_write(conn, output.value, output.length); + + if (ret == output.length) + { + ret = len; /* all requested bytes now written */ + goto cleanup; + } + + 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; +} + +/* + * Wrapper function for buffering decrypted data. + * + * This allows us to present a stream-like interface to pqsecure_read. For a + * description of buffering, see comment at be_gssapi_read (in be-gssapi.c). + */ +static ssize_t +pg_GSS_read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* Is any data available? */ + if (conn->gcursor < conn->gbuf.len) + { + /* clamp length */ + 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 all data has been read, reset buffer */ + if (conn->gcursor == conn->gbuf.len) + { + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + } + + 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_encrypt(conn); + + if (ret == -1) + return -1; + else if (ret == 0) + return pqsecure_raw_read(conn, ptr, len); + + /* ensure proper behavior under recursion */ + if (len == 0) + return 0; + + /* report any buffered data, then recur */ + if (conn->gcursor > 0) + { + ret = pg_GSS_read_from_buffer(conn, ptr, len); + + /* Pass up error message fragments. See comment below. */ + if (!conn->gss_decrypted && conn->gcursor == conn->gbuf.len) + { + /* call _raw_read to get any remaining parts of the message */ + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = 0; + } + + 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; + + /* write length to buffer */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + return 0; + } + + /* + * We can receive error messages from old servers that don't support + * GSSAPI encryption at this time. They need to be passed up so that we + * can potentially reconnect. + * + * This limits the server's first reply to not be between 1157627904 + * (about 2**30) and 1174405119, which are both over a gigabyte in size. + * If the server sends a connection parameter status message of this size, + * there are other problems present. + */ + if (!conn->gss_decrypted && conn->gbuf.data[0] == 'E') + { + ret = pg_GSS_read_from_buffer(conn, ptr, len); + if (conn->gcursor == conn->gbuf.len) + { + /* Call _raw_read to get any remaining parts of the message */ + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = 0; + } + return ret; + } + + /* 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; + } + + /* load any remaining parts of the packet into our buffer */ + if (conn->gbuf.len - 4 < input.length) + { + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + input.length - conn->gbuf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < input.length) + return 0; + } + + /* decrypt the packet */ + 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->gss_decrypted = true; + + /* load decrypted packet into our buffer, then recur */ + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + 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..b00b03b 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,14 @@ 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 */ + bool gss_decrypted; /* GSS decrypted first message */ + char *gss_require_encrypt; /* GSS encryption required */ #endif #ifdef ENABLE_SSPI @@ -620,7 +629,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 +651,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 */ diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 2fa2509..5e30739 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -156,8 +156,7 @@ sub mkvcbuild $postgres->FullExportDLL('postgres.lib'); # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c - # if building without OpenSSL, and be-gssapi-common.c when building with - # GSSAPI. + # if building without OpenSSL, and GSSAPI files when building with it. if (!$solution->{options}->{openssl}) { $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); @@ -165,6 +164,7 @@ sub mkvcbuild if (!$solution->{options}->{gss}) { $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + $postgres->RemoveFile('src/backend/libpq/be-secure-gssapi.c'); } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', @@ -216,8 +216,7 @@ sub mkvcbuild $libpq->AddReference($libpgport); # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c - # if building without OpenSSL, and fe-gssapi-common.c when building with - # GSSAPI + # if building without OpenSSL, and GSSAPI-only files when building with it. if (!$solution->{options}->{openssl}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); @@ -225,6 +224,7 @@ sub mkvcbuild if (!$solution->{options}->{gss}) { $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c'); } my $libpqwalreceiver = -- 2.8.0.rc3 From 6c01db1623e06d8bca084747b3c8a5abc8486960 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. Finally, resolve a pre-existing action item for acquiring all possible GSSAPI error messages on the backend, rather than just the first major and first minor statuses. --- src/backend/libpq/auth.c | 20 ++++++++++++- src/backend/libpq/be-gssapi-common.c | 50 ++++++++++++++++----------------- src/interfaces/libpq/fe-auth.c | 19 +++++++++++-- src/interfaces/libpq/fe-gssapi-common.c | 11 -------- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 94d95bd..661228e 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -719,7 +719,8 @@ pg_GSS_recvauth(Port *port) OM_uint32 maj_stat, min_stat, lmin_s, - gflags; + gflags, + target_flags; int mtype; int ret; StringInfoData buf; @@ -875,6 +876,23 @@ pg_GSS_recvauth(Port *port) } /* + * GSS_C_REPLAY_FLAG (request replay detection) and GSS_C_SEQUENCE_FLAG + * (out-of-sequence detection) are missing for compatibility with older + * clients which do not request these flags. They should be added in as + * soon as we no longer care about pre-9.6 clients. + * + * Newer clients will request both GSS_C_REPLAY_FLAGS and + * GSS_C_SEQUENCE_FLAGS as well as the flags we check for below, so lack + * of these two flags is not the end of the world. + */ + target_flags = GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; + if ((gflags & target_flags) != target_flags) + { + ereport(FATAL, (errmsg("GSSAPI did not provide required flags"))); + return STATUS_ERROR; + } + + /* * GSS_S_COMPLETE indicates that authentication is now complete. * * Get the name of the user that authenticated, and compare it to the pg diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c index dc27fa8..6cf5513 100644 --- a/src/backend/libpq/be-gssapi-common.c +++ b/src/backend/libpq/be-gssapi-common.c @@ -17,17 +17,6 @@ #include "libpq/be-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_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) { @@ -36,31 +25,40 @@ pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat) msg_ctx; char msg_major[128], msg_minor[128]; + short i; + + gmsg.value = NULL; + gmsg.length = 0; /* 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); + i = 0; + do + { + gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(msg_major + i, gmsg.value, sizeof(msg_major) - i); + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < sizeof(msg_major)); - if (msg_ctx) - - /* - * More than one message available. XXX: Should we loop and read all - * messages? (same below) - */ + if (msg_ctx || i == sizeof(msg_major)) 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); + i = 0; + do + { + gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(msg_minor + i, gmsg.value, sizeof(msg_minor) - i); + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < sizeof(msg_minor)); - if (msg_ctx) + if (msg_ctx || i == sizeof(msg_minor)) ereport(WARNING, (errmsg_internal("incomplete GSS minor error report"))); 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 22b9104..446a669 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.8.0.rc3
Attachment
On Sat, Apr 2, 2016 at 7:34 AM, Robbie Harwood <rharwood@redhat.com> wrote: > - Attempt to address a crash Michael is observing by switching to using > the StringInfo/pqExpBuffer management functions over my own code as > much as possible. Michael, if this doesn't fix it, I'm out of ideas. Nope, it doesn't. > Since I still can't reproduce this locally (left a client machine and > a process on the same machine retrying for over an hour on your test > case and didn't see it), could you provide me with some more > information on why repalloc is complaining? > Is this a low memory situation where alloc might have failed? No, this is an assertion failure, and it seems that you are compiling this code without --enable-cassert, without the switch the code actually works. > What's your setup look like? Just a simple Linux VM running krb5kdc with 386MB of memory, with Postgres running locally as well. > That pointer looks like it's on the heap, is that correct? appendBinaryStringInfo depends on palloc calls that allocate memory depending on the memory context used. It looks that what's just missing in your logic is a private memory context that be_gssapi_write and be_gssapi_read can use to handle the allocation of the communication buffers. -- Michael
Michael Paquier <michael.paquier@gmail.com> writes: > On Sat, Apr 2, 2016 at 7:34 AM, Robbie Harwood <rharwood@redhat.com> wrote: > >> Since I still can't reproduce this locally (left a client machine and >> a process on the same machine retrying for over an hour on your test >> case and didn't see it), could you provide me with some more >> information on why repalloc is complaining? >> Is this a low memory situation where alloc might have failed? > > No, this is an assertion failure, and it seems that you are compiling > this code without --enable-cassert, without the switch the code > actually works. You are right. I now see the assertion failure. >> That pointer looks like it's on the heap, is that correct? > > appendBinaryStringInfo depends on palloc calls that allocate memory > depending on the memory context used. It looks that what's just > missing in your logic is a private memory context that be_gssapi_write > and be_gssapi_read can use to handle the allocation of the > communication buffers. Thank you very much for the pointer! I will work in memory context management for the next version.
Hello friends, Here's v12, both here and on my github: https://github.com/frozencemetery/postgres/tree/feature/gssencrypt12 What changed: - The code is aware of memory contexts now. I actually really like the memory context stuff; just didn't see any indication of its existence in the code I had read. Anyway, we allocate server buffers in the connection-lifetime context. The other alternative that we discussed on IRC a bit was to avoid palloc()/pfree() entirely in favor of raw calloc()/free(), but I think if possible I prefer this approach since I find the StringInfo handy to work with. This eliminates the traceback for me with --enable-cassert. - Error cleanup. I've been looking very hard at this code in order to try to fix the assert, and I've fixed up a couple error paths that hadn't been taken. This involves replacing the double-send with a buffer-and-then-send, which turns out to be not only shorter but easier for me to reason about. Thanks! From 945805d45e8021f92ad73518b3a74ac6bab89525 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. Thanks to Michael Paquier for the Windows fixes. --- 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 | 74 +++++++++++++++++++++++++++++++++ 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 ++++++++++ src/tools/msvc/Mkvcbuild.pm | 18 ++++++-- 12 files changed, 212 insertions(+), 113 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..dc27fa8 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,74 @@ +/*------------------------------------------------------------------------- + * + * 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 "postgres.h" + +#include "libpq/be-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_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 */ diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index e4fb44e..2fa2509 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -155,12 +155,17 @@ sub mkvcbuild $postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); $postgres->FullExportDLL('postgres.lib'); - # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c - # if building without OpenSSL + # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c + # if building without OpenSSL, and be-gssapi-common.c when building with + # GSSAPI. if (!$solution->{options}->{openssl}) { $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', 'src/backend/snowball'); @@ -210,12 +215,17 @@ sub mkvcbuild 'src/interfaces/libpq/libpq.rc'); $libpq->AddReference($libpgport); - # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c - # if building without OpenSSL + # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c + # if building without OpenSSL, and fe-gssapi-common.c when building with + # GSSAPI if (!$solution->{options}->{openssl}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + } my $libpqwalreceiver = $solution->AddProject('libpqwalreceiver', 'dll', '', -- 2.8.0.rc3 From d9a828b9e7e7f81fa118f2911b86a4b1f7f142b8 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. --- configure | 24 ++- doc/src/sgml/client-auth.sgml | 14 +- doc/src/sgml/libpq.sgml | 12 ++ doc/src/sgml/runtime.sgml | 22 ++- src/backend/libpq/Makefile | 2 +- src/backend/libpq/auth.c | 8 +- src/backend/libpq/be-secure-gssapi.c | 298 +++++++++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 ++ src/backend/libpq/hba.c | 9 + src/backend/postmaster/postmaster.c | 34 ++++ src/backend/utils/init/postinit.c | 16 +- src/include/libpq/hba.h | 1 + src/include/libpq/libpq-be.h | 10 + src/include/libpq/libpq.h | 2 + src/interfaces/libpq/Makefile | 2 +- src/interfaces/libpq/fe-connect.c | 99 +++++++++- 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 | 327 ++++++++++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-int.h | 19 +- src/tools/msvc/Mkvcbuild.pm | 8 +- 23 files changed, 935 insertions(+), 22 deletions(-) create mode 100644 src/backend/libpq/be-secure-gssapi.c create mode 100644 src/interfaces/libpq/fe-secure-gssapi.c diff --git a/configure b/configure index a5bd629..3a69533 100755 --- a/configure +++ b/configure @@ -775,6 +775,7 @@ infodir docdir oldincludedir includedir +runstatedir localstatedir sharedstatedir sysconfdir @@ -896,6 +897,7 @@ datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' @@ -1148,6 +1150,15 @@ do | -silent | --silent | --silen | --sile | --sil) silent=yes ;; + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ @@ -1285,7 +1296,7 @@ fi for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir + libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. @@ -1438,6 +1449,7 @@ Fine tuning of the installation directories: --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] @@ -11988,7 +12000,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -12034,7 +12046,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -12058,7 +12070,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -12103,7 +12115,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -12127,7 +12139,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 3b2935c..f0d2348 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -915,7 +915,7 @@ 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> or <acronym>GSSAPI</acronym> encryption is in use. </para> <para> @@ -1046,6 +1046,18 @@ omicron bryanh guest1 </para> </listitem> </varlistentry> + + <varlistentry id="gssapi-require-encrypt" xreflabel="require_encrypt"> + <term><literal>require_encrypt</literal></term> + <listitem> + <para> + Require <acronym>GSSAPI</acronym> encryption. Defaults to off, which + enables <acronym>GSSAPI</acronym> encryption only when both available + and requested to maintain compatibility with old clients. This + setting should be enabled 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..5a5dc8f 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-require_encrypt" xreflabel="gss_require_encrypt"> + <term><literal>gss_require_encrypt</literal></term> + <listitem> + <para> + Require <acronym>GSSAPI</acronym> encryption support from the remote + server when set. By default, clients will fall back to not using + <acronym>GSSAPI</acronym> encryption if the server does not support + encryption through <acronym>GSSAPI</acronym>. + </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..d5c2df0 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,17 @@ 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 + called <xref linkend="gssapi-require-encrypt">; unless set, 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 + called <xref linkend="libpq-connect-gss-require-encrypt">. + </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..94d95bd 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -596,10 +596,12 @@ 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. However, if we are doing GSSAPI encryption, that request + * must go out immediately to ensure that all messages which follow the + * AUTH_REQ_OK are not grouped with it and can therefore be encrypted. */ - if (areq != AUTH_REQ_OK) + if (areq != AUTH_REQ_OK || port->gss != NULL) 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..d4b0d3e --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,298 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gssapi.c + * GSSAPI encryption support + * + * Portions Copyright (c) 2016, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "libpq/be-gssapi-common.h" + +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" + +/* + * Wrapper function indicating whether we are currently performing GSSAPI + * connection encryption. + * + * gss->encrypt is set when connection parameters are processed, which happens + * immediately after AUTH_REQ_OK is sent. + */ +static bool +be_gssapi_should_encrypt(Port *port) +{ + if (port->gss->ctx == GSS_C_NO_CONTEXT) + return false; + return port->gss->encrypt; +} + +/* + * Send a message along the connection, possibly encrypting using GSSAPI. + * + * If we are not encrypting at the moment, we send data plaintext and follow + * the calling conventions of secure_raw_write. Otherwise, the following + * hold: Incomplete writes are buffered using a dedicated StringInfo in the + * port structure. On failure, we return -1; on partial write, we return -1 + * and set errno=EWOULDBLOCK since the translation between plaintext and + * encrypted is indeterminate; on completed write, we return the total number + * of bytes written including any buffering that occurred. Behavior when + * called with a new pointer/length combination after an incomplete write is + * undefined. + */ +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]; + + ret = be_gssapi_should_encrypt(port); + + if (ret == -1) + return -1; + else if (ret == 0) + return secure_raw_write(port, ptr, len); + + /* send any data we have buffered */ + if (port->gss->writebuf.len != 0) + { + ret = secure_raw_write( + port, + port->gss->writebuf.data + port->gss->writebuf.cursor, + port->gss->writebuf.len - port->gss->writebuf.cursor); + if (ret < 0) + return ret; + + /* update and possibly clear buffer state */ + port->gss->writebuf.cursor += ret; + + if (port->gss->writebuf.cursor == port->gss->writebuf.len) + { + resetStringInfo(&port->gss->writebuf); + + /* the entire request has now been written */ + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; + } + + /* encrypt the message */ + 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; + } + + /* format for on-wire: 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + memcpy(lenbuf, &netlen, 4); + + appendBinaryStringInfo(&port->gss->writebuf, lenbuf, 4); + appendBinaryStringInfo(&port->gss->writebuf, output.value, output.length); + + /* recur to send any buffered data */ + gss_release_buffer(&minor, &output); + return be_gssapi_write(port, ptr, len); + cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + + return ret; +} + +/* + * Wrapper function for buffering decrypted data. + * + * This allows us to present a stream-like interface to secure_read. For a + * description of buffering behavior, see comment at be_gssapi_read. + */ +static ssize_t +be_gssapi_read_from_buffer(Port *port, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* Is any data available? */ + if (port->gss->buf.len > 4 && port->gss->buf.cursor < port->gss->buf.len) + { + /* clamp length */ + 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 all data has been read, reset buffer */ + if (port->gss->buf.cursor == port->gss->buf.len) + resetStringInfo(&port->gss->buf); + + 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_encrypt(port); + + if (ret == -1) + return -1; + else if (ret == 0) + return secure_raw_read(port, ptr, len); + + /* ensure proper behavior under recursion */ + if (len == 0) + return 0; + + /* report any buffered data, then recur */ + if (port->gss->buf.cursor > 0) + { + ret = be_gssapi_read_from_buffer(port, ptr, len); + if (ret > 0) + { + ssize_t r_ret = + be_gssapi_read(port, (char *)ptr + ret, len - ret); + if (r_ret < 0 && errno != EWOULDBLOCK +#ifdef EAGAIN + && errno != EAGAIN +#endif + ) + /* connection is dead in some way */ + return r_ret; + else if (r_ret < 0) + /* no more data right now */ + return ret; + return ret + r_ret; + } + } + + /* our buffer is now empty */ + if (port->gss->buf.len < 4) + { + enlargeStringInfo(&port->gss->buf, 4 - port->gss->buf.len); + ret = secure_raw_read(port, port->gss->buf.data + port->gss->buf.len, + 4 - port->gss->buf.len); + if (ret < 0) + return ret; + + /* write length to buffer */ + port->gss->buf.len += ret; + port->gss->buf.data[port->gss->buf.len] = '\0'; + if (port->gss->buf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + + /* 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); + + /* read the packet into our buffer */ + 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) + { + errno = EWOULDBLOCK; + return -1; + } + + /* decrypt the packet */ + 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; + } + + /* load decrypted packet into our buffer, then recur */ + resetStringInfo(&port->gss->buf); + 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..23640f7 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -2036,6 +2036,10 @@ retry1: port->user_name = pstrdup(valptr); else if (strcmp(nameptr, "options") == 0) port->cmdline_options = pstrdup(valptr); +#ifdef ENABLE_GSS + else if (strcmp(nameptr, "gss_encrypt") == 0) + port->gss->gss_encrypt = pstrdup(valptr); +#endif else if (strcmp(nameptr, "replication") == 0) { /* @@ -2355,6 +2359,17 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif #endif return port; @@ -2371,7 +2386,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); } @@ -4643,6 +4666,17 @@ SubPostmasterMain(int argc, char *argv[]) (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif /* Check we got appropriate args */ if (argc < 3) diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index e22d4db..15b1f2c 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" @@ -50,6 +50,7 @@ #include "storage/smgr.h" #include "tcop/tcopprot.h" #include "utils/acl.h" +#include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/memutils.h" @@ -1087,6 +1088,19 @@ process_startup_options(Port *port, bool am_superuser) SetConfigOption(name, value, gucctx, PGC_S_CLIENT); } + +#ifdef ENABLE_GSS + /* delay processing until after AUTH_REQ_OK has been sent */ + if (port->gss->gss_encrypt != NULL) + parse_bool(port->gss->gss_encrypt, &port->gss->encrypt); + + if (!port->gss->encrypt && port->hba->require_encrypt) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("User \"%s\" is required to use GSSAPI encryption but did not request it", + port->user_name))); + } +#endif } /* 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..71c1f95 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,10 @@ 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 */ + char *gss_encrypt; /* GSSAPI encryption literal request */ + bool encrypt; /* GSSAPI encryption has started */ #endif } pg_gssinfo; #endif @@ -214,6 +219,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..8c154ed 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_require_encrypt", "GSS_REQUIRE_ENCRYPT", "0", NULL, + "Require-GSS-encryption", "", 1, /* should be '0' or '1' */ + offsetof(struct pg_conn, gss_require_encrypt)}, +#endif + {"replication", NULL, NULL, NULL, "Replication", "D", 5, offsetof(struct pg_conn, replication)}, @@ -2518,6 +2530,38 @@ 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_encrypt(conn) && conn->inEnd > conn->inStart) + { + /* + * 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. + */ + int n; + + appendBinaryPQExpBuffer(&conn->gbuf, + conn->inBuffer + conn->inStart, + conn->inEnd - conn->inStart); + conn->inEnd = conn->inStart; + + /* Will not block on nonblocking sockets */ + n = pg_GSS_read(conn, conn->inBuffer + conn->inEnd, + conn->inBufSize - conn->inEnd); + /* + * If n < 0, then this wasn't a full request and + * either more data will be available later to + * complete it or we will error out then. + */ + if (n > 0) + conn->inEnd += n; + } +#endif + /* * Set asyncStatus so that PQgetResult will think that * what comes back next is the result of a query. See @@ -2558,6 +2602,47 @@ 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_require_encrypt != '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 support appname will also not support + * GSSAPI encryption. + */ + 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; + conn->gss_auth_done = false; + conn->gss_decrypted = false; + + /* Must drop the old connection */ + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + gss_delete_sec_context(&minor, &conn->gctx, + GSS_C_NO_BUFFER); + + resetPQExpBuffer(&conn->gbuf); + resetPQExpBuffer(&conn->gwritebuf); + goto keep_going; + } + } + else if (*conn->gss_require_encrypt == '1') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("Server does not support required GSSAPI encryption\n")); + } +#endif else if (conn->send_appname && (conn->appname || conn->fbappname)) { @@ -2575,7 +2660,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; @@ -2798,6 +2883,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 +3003,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 +3112,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..22b9104 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 + */ +bool +pg_GSS_should_encrypt(PGconn *conn) +{ + if (conn->gctx == GSS_C_NO_CONTEXT) + return false; + 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..e23f524 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); +bool pg_GSS_should_encrypt(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..ec0bab2 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,327 @@ +/*------------------------------------------------------------------------- + * + * 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" + +/* + * Send a message along the connection, possibly using GSSAPI. + * + * If not encrypting at the call-time, we send plaintext following calling + * conventions of pqsecure_raw_write. Partial writes are supported using a + * dedicated PQExpBuffer in conn. A partial write will return -1 and set + * errno=EWOULDBLOCK; otherwise, we return -1 (for error) or the number of + * total bytes written in the write of the current ptr. Calling with a new + * value of ptr after a partial write is undefined. + */ +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]; + + ret = pg_GSS_should_encrypt(conn); + + if (ret == -1) + return -1; + else if (ret == 0) + return pqsecure_raw_write(conn, ptr, len); + + /* send any data we have buffered */ + if (conn->gwritebuf.len != 0) + { + ret = pqsecure_raw_write(conn, + conn->gwritebuf.data + conn->gwritecurs, + conn->gwritebuf.len - conn->gwritecurs); + if (ret < 0) + return ret; + + conn->gwritecurs += ret; + + /* update and possibly clear buffer state */ + if (conn->gwritecurs == conn->gwritebuf.len) + { + resetPQExpBuffer(&conn->gwritebuf); + conn->gwritecurs = 0; + + /* The entire request has now been written */ + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; + } + + /* encrypt the message */ + 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; + } + + /* format for on-wire: 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + memcpy(lenbuf, &netlen, 4); + + appendBinaryPQExpBuffer(&conn->gwritebuf, lenbuf, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + + /* recur to send some buffered data */ + gss_release_buffer(&minor, &output); + return pg_GSS_write(conn, ptr, len); + cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + + return ret; +} + +/* + * Wrapper function for buffering decrypted data. + * + * This allows us to present a stream-like interface to pqsecure_read. For a + * description of buffering, see comment at be_gssapi_read (in be-gssapi.c). + */ +static ssize_t +pg_GSS_read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* Is any data available? */ + if (conn->gcursor < conn->gbuf.len) + { + /* clamp length */ + 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 all data has been read, reset buffer */ + if (conn->gcursor == conn->gbuf.len) + { + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + } + + 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_encrypt(conn); + + if (ret == -1) + return -1; + else if (ret == 0) + return pqsecure_raw_read(conn, ptr, len); + + /* ensure proper behavior under recursion */ + if (len == 0) + return 0; + + /* report any buffered data, then recur */ + if (conn->gcursor > 0) + { + ret = pg_GSS_read_from_buffer(conn, ptr, len); + + /* Pass up error message fragments. See comment below. */ + if (!conn->gss_decrypted && conn->gcursor == conn->gbuf.len) + { + /* call _raw_read to get any remaining parts of the message */ + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = 0; + } + + if (ret > 0) + { + ssize_t r_ret = pg_GSS_read(conn, (char *)ptr + ret, len - ret); + if (r_ret < 0 && errno != EWOULDBLOCK +#ifdef EAGAIN + && errno != EAGAIN +#endif + ) + /* connection is dead in some way */ + return r_ret; + else if (r_ret < 0) + /* no more data right now */ + return ret; + return ret + r_ret; + } + } + + /* our buffer is now empty */ + if (conn->gbuf.len < 4) + { + ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len); + 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 + conn->gbuf.len, + 4 - conn->gbuf.len); + if (ret < 0) + /* error already set by secure_raw_read */ + return ret; + + /* write length to buffer */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + + /* + * We can receive error messages from old servers that don't support + * GSSAPI encryption at this time. They need to be passed up so that we + * can potentially reconnect. + * + * This limits the server's first reply to not be between 1157627904 + * (about 2**30) and 1174405119, which are both over a gigabyte in size. + * If the server sends a connection parameter status message of this size, + * there are other problems present. + */ + if (!conn->gss_decrypted && conn->gbuf.data[0] == 'E') + { + ret = pg_GSS_read_from_buffer(conn, ptr, len); + if (conn->gcursor == conn->gbuf.len) + { + /* Call _raw_read to get any remaining parts of the message */ + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = 0; + } + return ret; + } + + /* 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; + } + + /* load any remaining parts of the packet into our buffer */ + if (conn->gbuf.len - 4 < input.length) + { + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + input.length - conn->gbuf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < input.length) + { + errno = EWOULDBLOCK; + return -1; + } + } + + /* decrypt the packet */ + 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->gss_decrypted = true; + + /* load decrypted packet into our buffer, then recur */ + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + 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..b00b03b 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,14 @@ 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 */ + bool gss_decrypted; /* GSS decrypted first message */ + char *gss_require_encrypt; /* GSS encryption required */ #endif #ifdef ENABLE_SSPI @@ -620,7 +629,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 +651,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 */ diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 2fa2509..5e30739 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -156,8 +156,7 @@ sub mkvcbuild $postgres->FullExportDLL('postgres.lib'); # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c - # if building without OpenSSL, and be-gssapi-common.c when building with - # GSSAPI. + # if building without OpenSSL, and GSSAPI files when building with it. if (!$solution->{options}->{openssl}) { $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); @@ -165,6 +164,7 @@ sub mkvcbuild if (!$solution->{options}->{gss}) { $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + $postgres->RemoveFile('src/backend/libpq/be-secure-gssapi.c'); } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', @@ -216,8 +216,7 @@ sub mkvcbuild $libpq->AddReference($libpgport); # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c - # if building without OpenSSL, and fe-gssapi-common.c when building with - # GSSAPI + # if building without OpenSSL, and GSSAPI-only files when building with it. if (!$solution->{options}->{openssl}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); @@ -225,6 +224,7 @@ sub mkvcbuild if (!$solution->{options}->{gss}) { $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c'); } my $libpqwalreceiver = -- 2.8.0.rc3 From ed471f08b972400c418df0328fdc9c639037b6c9 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. Finally, resolve a pre-existing action item for acquiring all possible GSSAPI error messages on the backend, rather than just the first major and first minor statuses. --- src/backend/libpq/auth.c | 20 ++++++++++++- src/backend/libpq/be-gssapi-common.c | 50 ++++++++++++++++----------------- src/interfaces/libpq/fe-auth.c | 19 +++++++++++-- src/interfaces/libpq/fe-gssapi-common.c | 11 -------- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 94d95bd..661228e 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -719,7 +719,8 @@ pg_GSS_recvauth(Port *port) OM_uint32 maj_stat, min_stat, lmin_s, - gflags; + gflags, + target_flags; int mtype; int ret; StringInfoData buf; @@ -875,6 +876,23 @@ pg_GSS_recvauth(Port *port) } /* + * GSS_C_REPLAY_FLAG (request replay detection) and GSS_C_SEQUENCE_FLAG + * (out-of-sequence detection) are missing for compatibility with older + * clients which do not request these flags. They should be added in as + * soon as we no longer care about pre-9.6 clients. + * + * Newer clients will request both GSS_C_REPLAY_FLAGS and + * GSS_C_SEQUENCE_FLAGS as well as the flags we check for below, so lack + * of these two flags is not the end of the world. + */ + target_flags = GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; + if ((gflags & target_flags) != target_flags) + { + ereport(FATAL, (errmsg("GSSAPI did not provide required flags"))); + return STATUS_ERROR; + } + + /* * GSS_S_COMPLETE indicates that authentication is now complete. * * Get the name of the user that authenticated, and compare it to the pg diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c index dc27fa8..6cf5513 100644 --- a/src/backend/libpq/be-gssapi-common.c +++ b/src/backend/libpq/be-gssapi-common.c @@ -17,17 +17,6 @@ #include "libpq/be-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_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) { @@ -36,31 +25,40 @@ pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat) msg_ctx; char msg_major[128], msg_minor[128]; + short i; + + gmsg.value = NULL; + gmsg.length = 0; /* 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); + i = 0; + do + { + gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(msg_major + i, gmsg.value, sizeof(msg_major) - i); + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < sizeof(msg_major)); - if (msg_ctx) - - /* - * More than one message available. XXX: Should we loop and read all - * messages? (same below) - */ + if (msg_ctx || i == sizeof(msg_major)) 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); + i = 0; + do + { + gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(msg_minor + i, gmsg.value, sizeof(msg_minor) - i); + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < sizeof(msg_minor)); - if (msg_ctx) + if (msg_ctx || i == sizeof(msg_minor)) ereport(WARNING, (errmsg_internal("incomplete GSS minor error report"))); 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 22b9104..446a669 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.8.0.rc3
Attachment
On Tue, Apr 5, 2016 at 9:06 AM, Robbie Harwood <rharwood@redhat.com> wrote: > Here's v12, both here and on my github: > https://github.com/frozencemetery/postgres/tree/feature/gssencrypt12 > > What changed: > > - The code is aware of memory contexts now. I actually really like the > memory context stuff; just didn't see any indication of its existence > in the code I had read. Anyway, we allocate server buffers in the > connection-lifetime context. The other alternative that we discussed > on IRC a bit was to avoid palloc()/pfree() entirely in favor of raw > calloc()/free(), but I think if possible I prefer this approach since > I find the StringInfo handy to work with. This eliminates the > traceback for me with --enable-cassert. Affirnative, I am not seeing the failure anymore. +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif So you are saving everything in the top memory context. I am fine to give the last word to a committer here but I would just go with calloc/free to simplify those hunks. +#ifdef ENABLE_GSS + if (conn->gss->buf.data) + pfree(conn->gss->buf.data); + if (conn->gss->writebuf.data) + pfree(conn->gss->writebuf.data); +#endif This should happen in its own memory context, no > - Error cleanup. I've been looking very hard at this code in order to > try to fix the assert, and I've fixed up a couple error paths that > hadn't been taken. This involves replacing the double-send with a > buffer-and-then-send, which turns out to be not only shorter but > easier for me to reason about. @@ -775,6 +775,7 @@ infodirdocdiroldincludedirincludedir +runstatedirlocalstatedirsharedstatedirsysconfdir @@ -896,6 +897,7 @@ datadir='${datarootdir}'sysconfdir='${prefix}/etc'sharedstatedir='${prefix}/com'localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' What's that? I would recommend re-running autoconf to remove this portion (committers do it at the end btw, so that's actually no bug deal). -#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 Regarding patch 0003 it may be fine to remove that... Robbie, do you know how long ago this has been fixed upstream? I'd rather not have this bit removed if this could impact some users. On 0003, +1 for reading the whole stack of messages. That's definitely worth a separate patch. Btw, those seem like small things to me, and my comments have been addressed, so I have switched the patch as ready for committer. I guess that Stephen would be the one to look at it. -- Michael
Michael Paquier <michael.paquier@gmail.com> writes: > On Tue, Apr 5, 2016 at 9:06 AM, Robbie Harwood <rharwood@redhat.com> wrote: >> Here's v12, both here and on my github: >> https://github.com/frozencemetery/postgres/tree/feature/gssencrypt12 >> > +#ifdef ENABLE_GSS > + { > + MemoryContext save = CurrentMemoryContext; > + CurrentMemoryContext = TopMemoryContext; > + > + initStringInfo(&port->gss->buf); > + initStringInfo(&port->gss->writebuf); > + > + CurrentMemoryContext = save; > + } > +#endif > > So you are saving everything in the top memory context. I am fine to > give the last word to a committer here but I would just go with > calloc/free to simplify those hunks. Yeah, it's definitely worth thinking/talking about; this came up in IRC discussion as well. If we go the memory context route, it has to be TopMemoryContext since nothing else lives long enough (i.e., entire connection). On the other hand, if we go with calloc/free here, I can't use StringInfo for these buffers because StringInfo uses palloc. Using StringInfo is really handy; if I don't use StringInfo, I'd need to reimplement enlargeStringInfo() for the ad-hoc buffer structure. What I'm trying to articulate is that it'd simplify the initialization code since it removes MemoryContext management, but it makes everything else more complicated. I prefer the StringInfo approach since I think the abstraction is a good one, but if you've got a case for explicit calloc()/free() in light of that, I'd be interested to hear it. > +#ifdef ENABLE_GSS > + if (conn->gss->buf.data) > + pfree(conn->gss->buf.data); > + if (conn->gss->writebuf.data) > + pfree(conn->gss->writebuf.data); > +#endif > > This should happen in its own memory context, no It turns out that's not actually required, but could easily be made explicit here. According to the README for the memory context system, pfree() and repalloc() do not require setting CurrentMemoryContext (since 7.1). I'm not opposed to making this one explicit if you think it adds clarity. I would not want to make all the calls to StringInfo functions explicit just because they can call repalloc, though. > @@ -775,6 +775,7 @@ infodir > docdir > oldincludedir > includedir > +runstatedir > localstatedir > sharedstatedir > sysconfdir > @@ -896,6 +897,7 @@ datadir='${datarootdir}' > sysconfdir='${prefix}/etc' > sharedstatedir='${prefix}/com' > localstatedir='${prefix}/var' > +runstatedir='${localstatedir}/run' > What's that? I would recommend re-running autoconf to remove this > portion (committers do it at the end btw, so that's actually no bug > deal). Well, I guess /that/ mistake finally happened. This is what happens when I run autoreconf on my VMs. Because configure is checked in to version control, I accidentally committed the whole thing instead of just the GSSAPI changes. I can back that out. Side note: it's really irritating to work with having this file under version control because of how different it ends up being when I autoreconf (which I need to do because I'm changing the build system). (I'm also idly curious what system/autotools version is generating this file because it doesn't match any that I tried.) > -#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 > Regarding patch 0003 it may be fine to remove that... Robbie, do you > know how long ago this has been fixed upstream? I'd rather not have > this bit removed if this could impact some users. I double-checked with MIT, and we think it was fixed in 2003 in commit 4ce1f7c3a46485e342d3a68b4c60b76c196d1851 which can be viewed at https://github.com/krb5/krb5/commit/4ce1f7c3a46485e342d3a68b4c60b76c196d1851 and the corresponding bug on their bugtracker was http://krbdev.mit.edu/rt/Ticket/Display.html?id=1666 In terms of release versions, this was first included in krb5-1.4. For reference, the oldest Kerberos that MIT upstream supports is 1.12; the oldest Kerberos that I (maintainer for Red Hat) support in production (i.e., RHEL-5) is 1.6.1, and even that is slated to go away early 2017. To my knowledge no one is supporting an older version than that. In the interest of completion, MIT did express concern that this might not have been a bug in MIT at all, but rather a toolchain issue. This workaround has been present since the introduction of GSSAPI auth support back in 2007. I pinged Magnus on IRC, but I think I missed the awake window for Sweden, so he's on CC as well. > On 0003, +1 for reading the whole stack of messages. That's definitely > worth a separate patch. Okay! It makes sense to me to move that over. > Btw, those seem like small things to me, and my comments have been > addressed, so I have switched the patch as ready for committer. I > guess that Stephen would be the one to look at it. Thanks for your comments! This patchset has definitely been much improved by your input, and I really appreciate that.
Robbie Harwood wrote: > Michael Paquier <michael.paquier@gmail.com> writes: > > > On Tue, Apr 5, 2016 at 9:06 AM, Robbie Harwood <rharwood@redhat.com> wrote: > >> Here's v12, both here and on my github: > >> https://github.com/frozencemetery/postgres/tree/feature/gssencrypt12 > > So you are saving everything in the top memory context. I am fine to > > give the last word to a committer here but I would just go with > > calloc/free to simplify those hunks. > > Yeah, it's definitely worth thinking/talking about; this came up in IRC > discussion as well. > > If we go the memory context route, it has to be TopMemoryContext since > nothing else lives long enough (i.e., entire connection). [...] > It turns out that's not actually required, but could easily be made > explicit here. According to the README for the memory context system, > pfree() and repalloc() do not require setting CurrentMemoryContext > (since 7.1). It seems to me that the right solution for this is to create a new memory context which is a direct child of TopMemoryContext, so that palloc can be used, and so that it can be reset separately, and that it doesn't suffer from resets of other contexts. (I think Michael's point is that if those chunks were it a context of their own, you wouldn't need the pfrees but a MemCxtReset would be enough.) > Side note: it's really irritating to work with having this file under > version control because of how different it ends up being when I > autoreconf (which I need to do because I'm changing the build system). > (I'm also idly curious what system/autotools version is generating this > file because it doesn't match any that I tried.) We use stock autoconf from the GNU package and it definitely produces matching output for me. Maybe your Linux distro includes a patched version? I know Debian does, but I suppose you're using some Redhat thing, so no idea. -- Álvaro Herrera http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Alvaro Herrera <alvherre@2ndquadrant.com> writes: > Robbie Harwood wrote: >> Michael Paquier <michael.paquier@gmail.com> writes: >> >> > On Tue, Apr 5, 2016 at 9:06 AM, Robbie Harwood <rharwood@redhat.com> wrote: >> >> Here's v12, both here and on my github: >> >> https://github.com/frozencemetery/postgres/tree/feature/gssencrypt12 > >> > So you are saving everything in the top memory context. I am fine to >> > give the last word to a committer here but I would just go with >> > calloc/free to simplify those hunks. >> >> Yeah, it's definitely worth thinking/talking about; this came up in IRC >> discussion as well. >> >> If we go the memory context route, it has to be TopMemoryContext since >> nothing else lives long enough (i.e., entire connection). > [...] >> It turns out that's not actually required, but could easily be made >> explicit here. According to the README for the memory context system, >> pfree() and repalloc() do not require setting CurrentMemoryContext >> (since 7.1). > > It seems to me that the right solution for this is to create a new > memory context which is a direct child of TopMemoryContext, so that > palloc can be used, and so that it can be reset separately, and that it > doesn't suffer from resets of other contexts. (I think Michael's point > is that if those chunks were it a context of their own, you wouldn't > need the pfrees but a MemCxtReset would be enough.) Hmm, that's also an option. I read Michael's point as arguing for calloc()/free() rather than a new context, but I could be wrong. A question, though: it it valuable for the context to be reset()able separately? If there were more than just these two buffers going into it, I could see it being convenient - especially if it were for different encryption types, for instance - but it seems like it would be overkill? This is all new to me so I may be entirely mistaken though. >> Side note: it's really irritating to work with having this file under >> version control because of how different it ends up being when I >> autoreconf (which I need to do because I'm changing the build system). >> (I'm also idly curious what system/autotools version is generating this >> file because it doesn't match any that I tried.) > > We use stock autoconf from the GNU package and it definitely produces > matching output for me. Maybe your Linux distro includes a patched > version? I know Debian does, but I suppose you're using some Redhat > thing, so no idea. Hmm, that explains the Debian behavior I was seeing (it does the above). The Fedora one adds a couple blank lines in places but it's much less... gratuitous... in its changes.
On 4/5/16 1:25 AM, Michael Paquier wrote: > Btw, those seem like small things to me, and my comments have been > addressed, so I have switched the patch as ready for committer. I > guess that Stephen would be the one to look at it. I have also run this patch through my tests and didn't find any problems. I agree that it is ready for committer. I don't see a problem with using the top memory context but whoever commits it may feel differently. I'm happy to leave that decision up to them. -- -David david@pgmasters.net
Robbie Harwood wrote: > Alvaro Herrera <alvherre@2ndquadrant.com> writes: > > It seems to me that the right solution for this is to create a new > > memory context which is a direct child of TopMemoryContext, so that > > palloc can be used, and so that it can be reset separately, and that it > > doesn't suffer from resets of other contexts. (I think Michael's point > > is that if those chunks were it a context of their own, you wouldn't > > need the pfrees but a MemCxtReset would be enough.) > > Hmm, that's also an option. I read Michael's point as arguing for > calloc()/free() rather than a new context, but I could be wrong. Yeah, if you weren't using stringinfos that would be an option, but since you are I think that's out of the question. > A question, though: it it valuable for the context to be reset()able > separately? If there were more than just these two buffers going into > it, I could see it being convenient - especially if it were for > different encryption types, for instance - but it seems like it would be > overkill? If two buffers is all, I think retail pfrees are fine. I haven't actually read the patch. -- Álvaro Herrera http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Tue, Apr 5, 2016 at 7:58 PM, Robbie Harwood <rharwood@redhat.com> wrote:
> -#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
> Regarding patch 0003 it may be fine to remove that... Robbie, do you
> know how long ago this has been fixed upstream? I'd rather not have
> this bit removed if this could impact some users.
I double-checked with MIT, and we think it was fixed in 2003 in commit
4ce1f7c3a46485e342d3a68b4c60b76c196d1851 which can be viewed at
https://github.com/krb5/krb5/commit/4ce1f7c3a46485e342d3a68b4c60b76c196d1851
and the corresponding bug on their bugtracker was
http://krbdev.mit.edu/rt/Ticket/Display.html?id=1666
That certainly looks like it fixes is. This was way too long ago for me to remember which versions I was using at the time though.
It looks like it was already OK in the MSVC build back then, and only mingw was broken. Which makes it even more reasonable that they might've fixed it now - or a long time ago.
If it works on reasonably modern mingw, then I suggest pushing it to the buildfarm and see what happens. But it definitely needs at least one round of building on mingw..
On Wed, Apr 6, 2016 at 6:15 AM, Magnus Hagander <magnus@hagander.net> wrote: > On Tue, Apr 5, 2016 at 7:58 PM, Robbie Harwood <rharwood@redhat.com> wrote: >> >> > -#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 >> > Regarding patch 0003 it may be fine to remove that... Robbie, do you >> > know how long ago this has been fixed upstream? I'd rather not have >> > this bit removed if this could impact some users. >> >> I double-checked with MIT, and we think it was fixed in 2003 in commit >> 4ce1f7c3a46485e342d3a68b4c60b76c196d1851 which can be viewed at >> >> https://github.com/krb5/krb5/commit/4ce1f7c3a46485e342d3a68b4c60b76c196d1851 >> and the corresponding bug on their bugtracker was >> http://krbdev.mit.edu/rt/Ticket/Display.html?id=1666 Thanks for the investigation! It would be interesting to see what at least narwhal in the buildfarm thinks about that. > That certainly looks like it fixes is. This was way too long ago for me to > remember which versions I was using at the time though. So, this is as well an indication that it would actually be fine :) > It looks like it was already OK in the MSVC build back then, and only mingw > was broken. Which makes it even more reasonable that they might've fixed it > now - or a long time ago. > > If it works on reasonably modern mingw, then I suggest pushing it to the > buildfarm and see what happens. But it definitely needs at least one round > of building on mingw.. What about giving a spin first to patch 0003 as two different patches? One to remove this check, and one to improve the error reporting (which is an improvement in itself). Then we could rebase the rest on top of it. -- Michael
On Wed, Apr 6, 2016 at 5:58 AM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote: > Robbie Harwood wrote: >> Alvaro Herrera <alvherre@2ndquadrant.com> writes: >> > It seems to me that the right solution for this is to create a new >> > memory context which is a direct child of TopMemoryContext, so that >> > palloc can be used, and so that it can be reset separately, and that it >> > doesn't suffer from resets of other contexts. (I think Michael's point >> > is that if those chunks were it a context of their own, you wouldn't >> > need the pfrees but a MemCxtReset would be enough.) >> >> Hmm, that's also an option. I read Michael's point as arguing for >> calloc()/free() rather than a new context, but I could be wrong. > > Yeah, if you weren't using stringinfos that would be an option, but > since you are I think that's out of the question. To be honest, my first thought about that was to create a private memory context only dedicated to those buffers, save it in pg_gssinfo and remove those pfree calls as the memory context would clean up itself with inheritance. Regarding the calloc/free stuff, should I be a committer I would have given it a spin to see how the code gets uglier or better and would have done a final decision depending on that (you are true about the repalloc/pfree calls in memory contexts btw, I forgot that). >> A question, though: it it valuable for the context to be reset()able >> separately? If there were more than just these two buffers going into >> it, I could see it being convenient - especially if it were for >> different encryption types, for instance - but it seems like it would be >> overkill? > > If two buffers is all, I think retail pfrees are fine. I haven't > actually read the patch. Those are the only two buffers present per session. -- Michael
Robbie, Just an initial pass over the patch. * Robbie Harwood (rharwood@redhat.com) wrote: > Here's v12, both here and on my github: > https://github.com/frozencemetery/postgres/tree/feature/gssencrypt12 I've started taking a look at this as it's a capability I've wanted us to support for a *long* time. > Subject: [PATCH 1/3] Move common GSSAPI code into its own files Didn't look too closely at this as it's mostly just moving stuff around. I'll review it more closely once the other items are addressed though. > Subject: [PATCH 2/3] Connection encryption support for GSSAPI > diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c > index 73d493e..94d95bd 100644 > --- a/src/backend/libpq/auth.c > +++ b/src/backend/libpq/auth.c > @@ -596,10 +596,12 @@ 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. However, if we are doing GSSAPI encryption, that request > + * must go out immediately to ensure that all messages which follow the > + * AUTH_REQ_OK are not grouped with it and can therefore be encrypted. > */ > - if (areq != AUTH_REQ_OK) > + if (areq != AUTH_REQ_OK || port->gss != NULL) > pq_flush(); > > CHECK_FOR_INTERRUPTS(); Do we actually need to send pq_flush *whenever* port->gss is not null? Shouldn't this actually be port->gss->encrypt? > diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c [...] > +/* > + * Wrapper function indicating whether we are currently performing GSSAPI > + * connection encryption. > + * > + * gss->encrypt is set when connection parameters are processed, which happens > + * immediately after AUTH_REQ_OK is sent. > + */ > +static bool > +be_gssapi_should_encrypt(Port *port) > +{ > + if (port->gss->ctx == GSS_C_NO_CONTEXT) > + return false; > + return port->gss->encrypt; > +} be_gssapi_should_encrypt returns bool, which seems entirely reasonable, but... > +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]; > + > + ret = be_gssapi_should_encrypt(port); Why are we storing the result into an ssize_t? > + if (ret == -1) > + return -1; > + else if (ret == 0) > + return secure_raw_write(port, ptr, len); And then testing the result against -1...? Or a bare 0 for that matter? > + /* encrypt the message */ > + 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; > + } > + > + /* format for on-wire: 4 network-order bytes of length, then payload */ > + netlen = htonl(output.length); > + memcpy(lenbuf, &netlen, 4); > + > + appendBinaryStringInfo(&port->gss->writebuf, lenbuf, 4); > + appendBinaryStringInfo(&port->gss->writebuf, output.value, output.length); That strikes me as a bit of overkill, we tend to just cast the pointer to a (char *) rather than memcpy'ing the data just to get a different pointer out of it. > + /* recur to send any buffered data */ > + gss_release_buffer(&minor, &output); > + return be_gssapi_write(port, ptr, len); This feels a bit odd to be doing, honestly. We try to take a lot of care to consider low-memory situation and to be careufl when it comes to potential for infinite recursion and there's already a way to ask for this function to be called again, isn't there? > + cleanup: > + if (output.value != NULL) > + gss_release_buffer(&minor, &output); > + > + return ret; > +} There's no need for any of this. This goto will never be reached as either a pg_GSS_error(ERROR) or an ereport(FATAL) isn't going to return control to this path. That's one of the reasons to be careful with memory allocation and to use appropriate memory contexts, we're going to longjmp out of this code path and clean up the memory allocations by free'ing the contexts that we no longer need. That is, on an ERROR level failure, on FATAL, we're just going to exit, so we don't have to worry about memory cleanup in that case. Note that in some cases we'll actually promote an ERROR to a FATAL; that's particularly relevant here as we tend to do that when we're in backend startup. > +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_encrypt(port); > + > + if (ret == -1) > + return -1; > + else if (ret == 0) > + return secure_raw_read(port, ptr, len); This has the same issue as be_gssapi_write(), noted above. > + /* ensure proper behavior under recursion */ > + if (len == 0) > + return 0; > + > + /* report any buffered data, then recur */ > + if (port->gss->buf.cursor > 0) > + { > + ret = be_gssapi_read_from_buffer(port, ptr, len); > + if (ret > 0) > + { > + ssize_t r_ret = > + be_gssapi_read(port, (char *)ptr + ret, len - ret); > + if (r_ret < 0 && errno != EWOULDBLOCK > +#ifdef EAGAIN > + && errno != EAGAIN > +#endif > + ) > + /* connection is dead in some way */ > + return r_ret; > + else if (r_ret < 0) > + /* no more data right now */ > + return ret; > + return ret + r_ret; > + } > + } I'm really not excited by all the recursion. Further, I'd segregate the variable declaration from such a complicated recursive call and probably throw in another comment or comment-paragraph in there about what's going on. > + /* our buffer is now empty */ > + if (port->gss->buf.len < 4) > + { > + enlargeStringInfo(&port->gss->buf, 4 - port->gss->buf.len); > + ret = secure_raw_read(port, port->gss->buf.data + port->gss->buf.len, > + 4 - port->gss->buf.len); > + if (ret < 0) > + return ret; > + > + /* write length to buffer */ > + port->gss->buf.len += ret; > + port->gss->buf.data[port->gss->buf.len] = '\0'; > + if (port->gss->buf.len < 4) > + { > + errno = EWOULDBLOCK; > + return -1; > + } > + } > + > + /* 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); I'm aware that enlargeStringInfo() does check and handle the case where the length ends up >1G, but that feels a bit grotty to me- are you sure you want the generic enlargeStringInfo() to handle that case? > + ret = be_gssapi_read_from_buffer(port, ptr, len); > + cleanup: > + if (output.value != NULL) > + gss_release_buffer(&minor, &output); > + > + return ret; Again, probably not much use using the goto's. Similar comments regarding the libpq side of things. > Subject: [PATCH 3/3] GSSAPI authentication cleanup Why wouldn't this be part of patch #2? Otherwise it looked pretty reasonable. Thanks! Stephen
Stephen Frost <sfrost@snowman.net> writes: > Just an initial pass over the patch. Thanks! In the interest of brevity, if I haven't replied to something, I plan to fix it. >> /* >> - * 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. However, if we are doing GSSAPI encryption, that request >> + * must go out immediately to ensure that all messages which follow the >> + * AUTH_REQ_OK are not grouped with it and can therefore be encrypted. >> */ >> - if (areq != AUTH_REQ_OK) >> + if (areq != AUTH_REQ_OK || port->gss != NULL) >> pq_flush(); >> >> CHECK_FOR_INTERRUPTS(); > > Do we actually need to send pq_flush *whenever* port->gss is not null? > Shouldn't this actually be port->gss->encrypt? I need to flush this any time we might be doing encryption because it needs to be in a separate request to _secure_write() from what follows it. We don't know whether we should be doing encryption until connection parameters are parsed; to put it another way, port->gss->encrypt will never be true here because it hasn't been parsed out of port->gss->gss_encrypt yet. I could parse it earlier, but then I need another variable in the struct (i.e., to hold whether AUTH_REQ_OK has been sent yet) to check as well. Additionally, it seemed to me that there might be some value security-wise in delaying parsing of connection parameters until after auth is complete, though of course for just a bool this may not be as important. >> + /* recur to send any buffered data */ >> + gss_release_buffer(&minor, &output); >> + return be_gssapi_write(port, ptr, len); > > This feels a bit odd to be doing, honestly. We try to take a lot of > care to consider low-memory situation and to be careufl when it comes to > potential for infinite recursion and there's already a way to ask for > this function to be called again, isn't there? This call should be okay because (1) it's a tail call and (2) we know the buffer isn't empty. That said, the other recursion is excessively complicated and I think it's simpler to eliminate it entirely in favor of being called again. I'll see what I can do. >> + /* 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); > > I'm aware that enlargeStringInfo() does check and handle the case where > the length ends up >1G, but that feels a bit grotty to me- are you sure > you want the generic enlargeStringInfo() to handle that case? This is a good point. We definitely have to draw the line somewhere; 1G is a high upper bound. Digging around the server code I don't see a precedent for what's a good size to stop at. There's PG_MAX_AUTH_TOKEN_LENGTH, which is 65k, and the password length in auth.c is 1k. Beyond that, SocketBackend() calls pq_getmessage() with no maximum length, which causes the enlargeStringInfo() size restrictions to be the only control (wrapped in a PG_TRY()). I think what I'm trying to get at here is that I'm open to suggestion, but don't see a clearly better way to do this. We could use the PG_TRY() approach here to preserve sync, though I'm not sure it's worth it considering PQ_RECV_BUFFER_SIZE and PQ_SEND_BUFFER_SIZE are both 8k. >> Subject: [PATCH 3/3] GSSAPI authentication cleanup > > Why wouldn't this be part of patch #2? It's a cleanup of existing code, not my new code, so I thought the separation would make it easier to understand. I'm fine with it as part of #2 so long as it happens, but the impression I had gotten from earlier reviews was that it should have even more division (i.e., move the gss_display_status() wrappers into their own patch), not less. Thanks, --Robbie
Robbie Harwood <rharwood@redhat.com> writes: > I need to flush this any time we might be doing encryption because it > needs to be in a separate request to _secure_write() from what follows > it. We don't know whether we should be doing encryption until > connection parameters are parsed; to put it another way, > port->gss->encrypt will never be true here because it hasn't been parsed > out of port->gss->gss_encrypt yet. Wait a second. So the initial connection-request packet is necessarily unencrypted under this scheme? That seems like a pretty substantial step backwards from what happens with SSL. Even granting that stuff like passwords won't be sent till later, the combination of user name and database name might already be useful info to an eavesdropper. I would think a design similar to the SSL one (special protocol version to cause encryption negotiation before the actual connection request is sent) would be better. (If I'm totally misunderstanding the context here, my apologies. I've not been following this thread.) >> I'm aware that enlargeStringInfo() does check and handle the case where >> the length ends up >1G, but that feels a bit grotty to me- are you sure >> you want the generic enlargeStringInfo() to handle that case? > This is a good point. We definitely have to draw the line somewhere; 1G > is a high upper bound. Digging around the server code I don't see a > precedent for what's a good size to stop at. There's > PG_MAX_AUTH_TOKEN_LENGTH, which is 65k, and the password length in > auth.c is 1k. Beyond that, SocketBackend() calls pq_getmessage() with > no maximum length, which causes the enlargeStringInfo() size > restrictions to be the only control (wrapped in a PG_TRY()). Note that SocketBackend() only runs *after* we've accepted the user as authorized. We should be a lot charier of what we're willing to accept before authorization, IMO. Note MAX_STARTUP_PACKET_LENGTH, which is only 10K. regards, tom lane
Tom Lane <tgl@sss.pgh.pa.us> writes: > Robbie Harwood <rharwood@redhat.com> writes: >> I need to flush this any time we might be doing encryption because it >> needs to be in a separate request to _secure_write() from what follows >> it. We don't know whether we should be doing encryption until >> connection parameters are parsed; to put it another way, >> port->gss->encrypt will never be true here because it hasn't been parsed >> out of port->gss->gss_encrypt yet. > > Wait a second. So the initial connection-request packet is necessarily > unencrypted under this scheme? That seems like a pretty substantial > step backwards from what happens with SSL. Even granting that stuff > like passwords won't be sent till later, the combination of user name > and database name might already be useful info to an eavesdropper. > > I would think a design similar to the SSL one (special protocol version > to cause encryption negotiation before the actual connection request > is sent) would be better. (Apologies for the wall of text that follows. My GSSAPI encryption support has gone through three major redesigns, so I've got a fair bit to say about it at this point, and it's probably better that I say too much than too little. The short version is that GSSAPI works differently than SSL and username is sent in the clear no matter what, but this isn't a problem.) Yes, by necessity. The username must be sent in the clear, even if only as part of the GSSAPI handshake (i.e., the GSSAPI username will appear in plantext in the GSSAPI blobs which are otherwise encrypted). GSSAPI performs authentication before it can start encryption. In this design, the contents of the Startup Message are the only non-authentication related information sent in the clear. This contains: username (which we need anyway), database, application_name, and I add gss_encrypt. Why does it look this way? Fallback support. We already have GSSAPI authentication code in the project, and unfortunately we can't fix the multitude of older clients that won't have encryption support, whatever form it takes. What if we didn't need fallback support, though? Doing it with a special protocol version, as in the SSL/TLS case, would cause a separate path for authentication to occur, during which everything would look pretty much the same, except we wouldn't send database. We would then complete the auth handshake, and in a separate exchange, pass in the database information. Only then could we perform authorization checking. Authorization checking is currently coupled with the authentication as well; we would need a way to bypass the normal auth sequence and enter encryption. Bottom line is that designing similarly to SSL/TLS doesn't really make sense because the two schemes work differently. Typically, usernames are not considered sensitive information unless one is worried about the security of the authentication information that goes with them. For instance, MIT Kerberos will reveal the difference between "username not found" and the equivalent of "bad password". I don't know how much admins care about the database names being in the clear; I suspect it doesn't matter much because just knowing their names isn't enough to connect. Even if it's something people care about, this is still far better than no encryption at all (which is the current GSSAPI behavior), and would be better addressed by supporting connecting without specifying a database immediately anyway. >>> I'm aware that enlargeStringInfo() does check and handle the case where >>> the length ends up >1G, but that feels a bit grotty to me- are you sure >>> you want the generic enlargeStringInfo() to handle that case? > >> This is a good point. We definitely have to draw the line somewhere; 1G >> is a high upper bound. Digging around the server code I don't see a >> precedent for what's a good size to stop at. There's >> PG_MAX_AUTH_TOKEN_LENGTH, which is 65k, and the password length in >> auth.c is 1k. Beyond that, SocketBackend() calls pq_getmessage() with >> no maximum length, which causes the enlargeStringInfo() size >> restrictions to be the only control (wrapped in a PG_TRY()). > > Note that SocketBackend() only runs *after* we've accepted the user as > authorized. We should be a lot charier of what we're willing to accept > before authorization, IMO. Note MAX_STARTUP_PACKET_LENGTH, which is > only 10K. Correct, we're only talking about packets that are sent after authentication+authorization are complete. Before authentication is complete, the GSSAPI encryption codepaths are not taken.
Robbie Harwood <rharwood@redhat.com> writes: > Tom Lane <tgl@sss.pgh.pa.us> writes: >> Wait a second. So the initial connection-request packet is necessarily >> unencrypted under this scheme? > Yes, by necessity. The username must be sent in the clear, even if only > as part of the GSSAPI handshake (i.e., the GSSAPI username will appear > in plantext in the GSSAPI blobs which are otherwise encrypted). GSSAPI > performs authentication before it can start encryption. Ugh. I had thought we were putting work into this because it represented something we could recommend as best practice, but now you're telling me that it's always going to be inferior to what we have already. > In this design, the contents of the Startup Message are the only > non-authentication related information sent in the clear. This > contains: username (which we need anyway), database, application_name, > and I add gss_encrypt. And any other GUC value that the user has decided to send via PGOPTIONS. Whatever the merits of assuming that the username is okay to expose to eavesdroppers, I dislike having to assume that there are not and never will be any GUCs whose settings are potentially security-sensitive. I really think you need to fix this so that the true startup packet is not sent until the connection has been encrypted. People are used to assuming that's true with SSL encryption, and if GSSAPI is less secure that's likely to lead to unexpected security weaknesses somewhere down the line. regards, tom lane
On Thu, Apr 7, 2016 at 8:20 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > Robbie Harwood <rharwood@redhat.com> writes: >> Tom Lane <tgl@sss.pgh.pa.us> writes: >>> Wait a second. So the initial connection-request packet is necessarily >>> unencrypted under this scheme? > >> Yes, by necessity. The username must be sent in the clear, even if only >> as part of the GSSAPI handshake (i.e., the GSSAPI username will appear >> in plantext in the GSSAPI blobs which are otherwise encrypted). GSSAPI >> performs authentication before it can start encryption. > > Ugh. I had thought we were putting work into this because it represented > something we could recommend as best practice, but now you're telling me > that it's always going to be inferior to what we have already. It does not seem necessary to have an equivalent of pqsecure_open_client, just some extra handling in fe-connect.c to set up the initial context with a proper message handling... Not that direct anyway. So should the patch be marked as returned with feedback at this stage? -- Michael
On Thu, Apr 7, 2016 at 10:17 PM, Michael Paquier <michael.paquier@gmail.com> wrote: > On Thu, Apr 7, 2016 at 8:20 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Robbie Harwood <rharwood@redhat.com> writes: >>> Tom Lane <tgl@sss.pgh.pa.us> writes: >>>> Wait a second. So the initial connection-request packet is necessarily >>>> unencrypted under this scheme? >> >>> Yes, by necessity. The username must be sent in the clear, even if only >>> as part of the GSSAPI handshake (i.e., the GSSAPI username will appear >>> in plantext in the GSSAPI blobs which are otherwise encrypted). GSSAPI >>> performs authentication before it can start encryption. >> >> Ugh. I had thought we were putting work into this because it represented >> something we could recommend as best practice, but now you're telling me >> that it's always going to be inferior to what we have already. > > It does not seem necessary to have an equivalent of > pqsecure_open_client, just some extra handling in fe-connect.c to set > up the initial context with a proper message handling... Not that > direct anyway. So should the patch be marked as returned with feedback > at this stage? Yeah, I think so. It doesn't seem we have consensus on this, and it's too late to be trying to build one now. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
* Robert Haas (robertmhaas@gmail.com) wrote: > On Thu, Apr 7, 2016 at 10:17 PM, Michael Paquier > <michael.paquier@gmail.com> wrote: > > On Thu, Apr 7, 2016 at 8:20 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > >> Robbie Harwood <rharwood@redhat.com> writes: > >>> Tom Lane <tgl@sss.pgh.pa.us> writes: > >>>> Wait a second. So the initial connection-request packet is necessarily > >>>> unencrypted under this scheme? > >> > >>> Yes, by necessity. The username must be sent in the clear, even if only > >>> as part of the GSSAPI handshake (i.e., the GSSAPI username will appear > >>> in plantext in the GSSAPI blobs which are otherwise encrypted). GSSAPI > >>> performs authentication before it can start encryption. > >> > >> Ugh. I had thought we were putting work into this because it represented > >> something we could recommend as best practice, but now you're telling me > >> that it's always going to be inferior to what we have already. > > > > It does not seem necessary to have an equivalent of > > pqsecure_open_client, just some extra handling in fe-connect.c to set > > up the initial context with a proper message handling... Not that > > direct anyway. So should the patch be marked as returned with feedback > > at this stage? > > Yeah, I think so. It doesn't seem we have consensus on this, and it's > too late to be trying to build one now. Actually, I chatted with Robbie quite a bit over IRC and he's agreed on reworking this to use the same approach that we use for SSL, but that's expected to take the better part of a week to do. While it seems like this particular patch (with myself as committer) would meet the requirements stated by the RMT for an extension, having considered it over the past day or so, I don't think we should make it a policy to allow an extension when it involves a significant rework of the patch, as is the case here. Robbie, please be sure to add this to the next commitfest and please hound me to review it, you know where to find me. :) Thanks! Stephen
On Fri, Apr 8, 2016 at 2:36 PM, Stephen Frost <sfrost@snowman.net> wrote: > While it seems like this particular patch (with myself as committer) > would meet the requirements stated by the RMT for an extension, having > considered it over the past day or so, I don't think we should make it a > policy to allow an extension when it involves a significant rework of > the patch, as is the case here. I agree. To be clear, those were intended as necessary but not necessarily sufficient reasons for extension. I agree that patches needing significant reworking are not good candidates for extensions. (But that is my feeling as an RMT member, not an RMT official policy upon which we have voted.) -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
Michael Paquier <michael.paquier@gmail.com> writes: > On Thu, Apr 7, 2016 at 8:20 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Robbie Harwood <rharwood@redhat.com> writes: >>> Tom Lane <tgl@sss.pgh.pa.us> writes: >>> >>>> Wait a second. So the initial connection-request packet is >>>> necessarily unencrypted under this scheme? >> >>> Yes, by necessity. The username must be sent in the clear, even if >>> only as part of the GSSAPI handshake (i.e., the GSSAPI username will >>> appear in plantext in the GSSAPI blobs which are otherwise >>> encrypted). GSSAPI performs authentication before it can start >>> encryption. >> >> Ugh. I had thought we were putting work into this because it >> represented something we could recommend as best practice, but now >> you're telling me that it's always going to be inferior to what we >> have already. > > It does not seem necessary to have an equivalent of > pqsecure_open_client, just some extra handling in fe-connect.c to set > up the initial context with a proper message handling... Not that > direct anyway. So should the patch be marked as returned with feedback > at this stage? I think in order to satisfy Tom's (valid) concern, there does need to be a separate handshake - i.e., GSSAPI support in pqsecure_open_client(). If I were to continue as I have been - using the plaintext connection and auth negotiation path - then at the time of startup the client has no way of knowing whether to send connection parameters or not. Personally, I would be in favor of not frontloading these connection parameters over insecure connections, but it is my impression that the project does not want to go this way (which is fine). The way I'm seeing this, when a connection comes in, we take the 'G' character for GSSAPI much as for SSL. At that time, we need to perform an *authentication* handshake (because GSSAPI will not do encryption before authenticating). I expect to use a consistent format for all GSSAPI packets - four bytes for length, and a payload. (I would prefer tagging them, but previously preference for not doing this has been expressed.) Once GSSAPI authentication is complete, the normal handshake process can be tunneled through a GSSAPI encryption layer, as is done with TLS. The server will need to retain some of the earlier authentication data (e.g., to check that the presented user-name matches GSSAPI credentials), but there will be no authentication packets exchanged (more specifically, it will resemble the anonymous case). Authorization will be checked as normal, and we then proceed in the usual fashion, all over the GSSAPI tunnel. On the server, I'll need to implement `hostgss` (by analogy to `hostssl`), and we'll want to lock authentication on those connections to GSSAPI-only. Clients will explicitly probe for GSSAPI support as they do for TLS support (I look forward to the bikeshed on the order of these) and should have a parameter to require said support. One thing I'm not clear on is what our behavior should be when the user doesn't explicitly request GSSAPI and doesn't have a ccache - do we prompt? Skip probing? I'm not sure what the best option there is. Before I implement this design, does anyone have any additional concerns or feedback on it? Thanks, --Robbie
Robbie Harwood <rharwood@redhat.com> writes: > Michael Paquier <michael.paquier@gmail.com> writes: > >> On Thu, Apr 7, 2016 at 8:20 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >>> Robbie Harwood <rharwood@redhat.com> writes: >>>> Tom Lane <tgl@sss.pgh.pa.us> writes: >>>> >>>>> Wait a second. So the initial connection-request packet is >>>>> necessarily unencrypted under this scheme? >>> >>>> Yes, by necessity. The username must be sent in the clear, even if >>>> only as part of the GSSAPI handshake (i.e., the GSSAPI username will >>>> appear in plantext in the GSSAPI blobs which are otherwise >>>> encrypted). GSSAPI performs authentication before it can start >>>> encryption. >>> >>> Ugh. I had thought we were putting work into this because it >>> represented something we could recommend as best practice, but now >>> you're telling me that it's always going to be inferior to what we >>> have already. >> >> It does not seem necessary to have an equivalent of >> pqsecure_open_client, just some extra handling in fe-connect.c to set >> up the initial context with a proper message handling... Not that >> direct anyway. So should the patch be marked as returned with feedback >> at this stage? > > I think in order to satisfy Tom's (valid) concern, there does need to be > a separate handshake - i.e., GSSAPI support in pqsecure_open_client(). > > If I were to continue as I have been - using the plaintext connection > and auth negotiation path - then at the time of startup the client has > no way of knowing whether to send connection parameters or not. > Personally, I would be in favor of not frontloading these connection > parameters over insecure connections, but it is my impression that the > project does not want to go this way (which is fine). > > The way I'm seeing this, when a connection comes in, we take the 'G' > character for GSSAPI much as for SSL. At that time, we need to perform > an *authentication* handshake (because GSSAPI will not do encryption > before authenticating). I expect to use a consistent format for all > GSSAPI packets - four bytes for length, and a payload. (I would prefer > tagging them, but previously preference for not doing this has been > expressed.) > > Once GSSAPI authentication is complete, the normal handshake process can > be tunneled through a GSSAPI encryption layer, as is done with TLS. The > server will need to retain some of the earlier authentication data > (e.g., to check that the presented user-name matches GSSAPI > credentials), but there will be no authentication packets exchanged > (more specifically, it will resemble the anonymous case). Authorization > will be checked as normal, and we then proceed in the usual fashion, all > over the GSSAPI tunnel. > > On the server, I'll need to implement `hostgss` (by analogy to > `hostssl`), and we'll want to lock authentication on those connections > to GSSAPI-only. Clients will explicitly probe for GSSAPI support as > they do for TLS support (I look forward to the bikeshed on the order of > these) and should have a parameter to require said support. One thing > I'm not clear on is what our behavior should be when the user doesn't > explicitly request GSSAPI and doesn't have a ccache - do we prompt? > Skip probing? I'm not sure what the best option there is. > > Before I implement this design, does anyone have any additional concerns > or feedback on it? Does this look reasonable to folks?
On Tue, Jul 26, 2016 at 5:58 AM, Robbie Harwood <rharwood@redhat.com> wrote: > Robbie Harwood <rharwood@redhat.com> writes: Sorry for my late reply. >> I think in order to satisfy Tom's (valid) concern, there does need to be >> a separate handshake - i.e., GSSAPI support in pqsecure_open_client(). Having the communication layer in fe-secure.c definitely makes sense. The startup process though should not use CONNECTION_SSL_STARTUP. >> If I were to continue as I have been - using the plaintext connection >> and auth negotiation path - then at the time of startup the client has >> no way of knowing whether to send connection parameters or not. >> Personally, I would be in favor of not frontloading these connection >> parameters over insecure connections, but it is my impression that the >> project does not want to go this way (which is fine). Per the discussion upthread I got the opposite impression, the startup packet should be sent after the connection has been established. SSL does so: the SSL negotiation goes first, and then the startup packet is sent. That's the flow with the status changing from CONNECTION_SSL_START -> CONNECTION_MADE. >> The way I'm seeing this, when a connection comes in, we take the 'G' >> character for GSSAPI much as for SSL. At that time, we need to perform >> an *authentication* handshake (because GSSAPI will not do encryption >> before authenticating). I expect to use a consistent format for all >> GSSAPI packets - four bytes for length, and a payload. (I would prefer >> tagging them, but previously preference for not doing this has been >> expressed.) OK. >> Once GSSAPI authentication is complete, the normal handshake process can >> be tunneled through a GSSAPI encryption layer, as is done with TLS. The >> server will need to retain some of the earlier authentication data >> (e.g., to check that the presented user-name matches GSSAPI >> credentials), but there will be no authentication packets exchanged >> (more specifically, it will resemble the anonymous case). Authorization >> will be checked as normal, and we then proceed in the usual fashion, all >> over the GSSAPI tunnel. OK, that sounds good. >> On the server, I'll need to implement `hostgss` (by analogy to >> `hostssl`), and we'll want to lock authentication on those connections >> to GSSAPI-only. As well as hostnogss, but I guess that's part of the plan. >> Clients will explicitly probe for GSSAPI support as >> they do for TLS support (I look forward to the bikeshed on the order of >> these) and should have a parameter to require said support. One thing >> I'm not clear on is what our behavior should be when the user doesn't >> explicitly request GSSAPI and doesn't have a ccache - do we prompt? >> Skip probing? I'm not sure what the best option there is. I am not sure I get what you mean here. -- Michael
Michael Paquier <michael.paquier@gmail.com> writes: > On Tue, Jul 26, 2016 at 5:58 AM, Robbie Harwood <rharwood@redhat.com> wrote: >> Robbie Harwood <rharwood@redhat.com> writes: > > Sorry for my late reply. Thanks for the feedback! >>> If I were to continue as I have been - using the plaintext connection >>> and auth negotiation path - then at the time of startup the client has >>> no way of knowing whether to send connection parameters or not. >>> Personally, I would be in favor of not frontloading these connection >>> parameters over insecure connections, but it is my impression that the >>> project does not want to go this way (which is fine). > > Per the discussion upthread I got the opposite impression, the startup > packet should be sent after the connection has been established. SSL > does so: the SSL negotiation goes first, and then the startup packet > is sent. That's the flow with the status changing from > CONNECTION_SSL_START -> CONNECTION_MADE. We are in agreement, I think. I have structured the referenced paragraph badly: for this design, I'm suggesting separate GSS startup path (like SSL) and once a tunnel is established we send the startup packet. I probably should have just left this paragraph out. >>> On the server, I'll need to implement `hostgss` (by analogy to >>> `hostssl`), and we'll want to lock authentication on those connections >>> to GSSAPI-only. > > As well as hostnogss, but I guess that's part of the plan. Sure, `hostnogss` should be fine. This isn't quadratic, right? We don't need hostnogssnossl (or thereabouts)? >>> Clients will explicitly probe for GSSAPI support as they do for TLS >>> support (I look forward to the bikeshed on the order of these) and >>> should have a parameter to require said support. One thing I'm not >>> clear on is what our behavior should be when the user doesn't >>> explicitly request GSSAPI and doesn't have a ccache - do we prompt? >>> Skip probing? I'm not sure what the best option there is. > > I am not sure I get what you mean here. Okay. Let me try again: So there's a connection setting `sslmode` that we'll want something similar to here (`gssapimode` or so). `sslmode` has six settings, but I think we only need three for GSSAPI: "disable", "allow", and "prefer" (which presumably would be the default). Lets suppose we're working with "prefer". GSSAPI will itself check two places for credentials: client keytab and ccache. But if we don't find credentials there, we as the client have two options on how to proceed. - First, we could prompt for a password (and then call gss_acquire_cred_with_password() to get credentials), presumably withan empty password meaning to skip GSSAPI. My memory is that the current behavior for GSSAPI auth-only is to prompt forpassword if we don't find credentials (and if it isn't, there's no reason not to unless we're opposed to handling thepassword). - Second, we could skip GSSAPI and proceed with the next connection method. This might be confusing if the user is thenprompted for a password and expects it to be for GSSAPI, but we could probably make it sensible. I think I prefer thefirst option. Thanks, --Robbie
Robbie Harwood <rharwood@redhat.com> writes: > So there's a connection setting `sslmode` that we'll want something > similar to here (`gssapimode` or so). `sslmode` has six settings, but I > think we only need three for GSSAPI: "disable", "allow", and "prefer" > (which presumably would be the default). Apologies, this should say four; I neglected "require".
Robbie Harwood <rharwood@redhat.com> writes: > So there's a connection setting `sslmode` that we'll want something > similar to here (`gssapimode` or so). `sslmode` has six settings, but I > think we only need three for GSSAPI: "disable", "allow", and "prefer" > (which presumably would be the default). FWIW, there is quite a bit of unhappiness around sslmode=prefer, see for example this thread: https://www.postgresql.org/message-id/flat/2A5EFBDC-41C6-42A8-8B6D-E69DA60E9962%40eggerapps.at I do not know if we can come up with a better answer, but I'd caution you against thinking that that's a problem-free model to emulate. regards, tom lane
Tom Lane <tgl@sss.pgh.pa.us> writes: > Robbie Harwood <rharwood@redhat.com> writes: >> So there's a connection setting `sslmode` that we'll want something >> similar to here (`gssapimode` or so). `sslmode` has six settings, but I >> think we only need three for GSSAPI: "disable", "allow", and "prefer" >> (which presumably would be the default). > > FWIW, there is quite a bit of unhappiness around sslmode=prefer, see > for example this thread: > https://www.postgresql.org/message-id/flat/2A5EFBDC-41C6-42A8-8B6D-E69DA60E9962%40eggerapps.at > > I do not know if we can come up with a better answer, but I'd caution > you against thinking that that's a problem-free model to emulate. Understood. We have the slight simplification for GSSAPI of having mutual authentication always (i.e., no need to worry about unauthenticated-but-encrypted connections). My personal view is that we want to try for as much security as we can without breaking anything [0]. If a user knows that they want a specific security, they can set "require"; if they don't want it, they can set "disable". Setting "require" as the default breaks one class of users; setting "disable" another. And I don't think we can punt the problem to the user and make it a mandatory parameter, either. I'm absolutely open to suggestions for how we could do better here, especially since we're adding support for something new, but having read the thread you mention I don't immediately see a superior design. 0: For what it's worth, I also don't agree with the assertion that having the ability to fallback to plaintext from tamperingmakes the attempt at encryption useless; rather, it still foils a passive adversary, even if it doesn't do anythingagainst an active one.
On Wed, Jul 27, 2016 at 12:22 AM, Robbie Harwood <rharwood@redhat.com> wrote: > Michael Paquier <michael.paquier@gmail.com> writes: > >> On Tue, Jul 26, 2016 at 5:58 AM, Robbie Harwood <rharwood@redhat.com> wrote: >>> Robbie Harwood <rharwood@redhat.com> writes: >> >> Sorry for my late reply. > > Thanks for the feedback! > >>>> If I were to continue as I have been - using the plaintext connection >>>> and auth negotiation path - then at the time of startup the client has >>>> no way of knowing whether to send connection parameters or not. >>>> Personally, I would be in favor of not frontloading these connection >>>> parameters over insecure connections, but it is my impression that the >>>> project does not want to go this way (which is fine). >> >> Per the discussion upthread I got the opposite impression, the startup >> packet should be sent after the connection has been established. SSL >> does so: the SSL negotiation goes first, and then the startup packet >> is sent. That's the flow with the status changing from >> CONNECTION_SSL_START -> CONNECTION_MADE. > > We are in agreement, I think. I have structured the referenced > paragraph badly: for this design, I'm suggesting separate GSS startup > path (like SSL) and once a tunnel is established we send the startup > packet. I probably should have just left this paragraph out. OK we're good then. >>>> On the server, I'll need to implement `hostgss` (by analogy to >>>> `hostssl`), and we'll want to lock authentication on those connections >>>> to GSSAPI-only. >> >> As well as hostnogss, but I guess that's part of the plan. > > Sure, `hostnogss` should be fine. This isn't quadratic, right? We don't > need hostnogssnossl (or thereabouts)? We don't need to do that far, users could still do the same with two different lines in pg_hba.conf. > So there's a connection setting `sslmode` that we'll want something > similar to here (`gssapimode` or so). `sslmode` has six settings, but I > think we only need three for GSSAPI: "disable", "allow", and "prefer" > (which presumably would be the default). Seeing the debate regarding sslmode these days, I would not say that "prefer" would be the default, but that's an implementation detail. > Lets suppose we're working with "prefer". GSSAPI will itself check two > places for credentials: client keytab and ccache. But if we don't find > credentials there, we as the client have two options on how to proceed. > > - First, we could prompt for a password (and then call > gss_acquire_cred_with_password() to get credentials), presumably with > an empty password meaning to skip GSSAPI. My memory is that the > current behavior for GSSAPI auth-only is to prompt for password if we > don't find credentials (and if it isn't, there's no reason not to > unless we're opposed to handling the password). > > - Second, we could skip GSSAPI and proceed with the next connection > method. This might be confusing if the user is then prompted for a > password and expects it to be for GSSAPI, but we could probably make > it sensible. I think I prefer the first option. Ah, right. I completely forgot that GSSAPI had its own handling of passwords for users registered to it... Isn't this distinction a good point for not implementing "prefer", "allow" or any equivalents? By that I mean that we should not have any GSS connection mode that fallbacks to something else if the first one fails. So we would live with the two following modes: - "disable", to only try a non-GSS connection - "enable", or "require", to only try a GSS connection. That seems quite acceptable to me as a first implementation to just have that. -- Michael
Michael Paquier <michael.paquier@gmail.com> writes: > On Wed, Jul 27, 2016 at 12:22 AM, Robbie Harwood <rharwood@redhat.com> wrote: >> Michael Paquier <michael.paquier@gmail.com> writes: >> >> So there's a connection setting `sslmode` that we'll want something >> similar to here (`gssapimode` or so). `sslmode` has six settings, but I >> think we only need three for GSSAPI: "disable", "allow", and "prefer" >> (which presumably would be the default). > > Seeing the debate regarding sslmode these days, I would not say that > "prefer" would be the default, but that's an implementation detail. > >> Lets suppose we're working with "prefer". GSSAPI will itself check two >> places for credentials: client keytab and ccache. But if we don't find >> credentials there, we as the client have two options on how to proceed. >> >> - First, we could prompt for a password (and then call >> gss_acquire_cred_with_password() to get credentials), presumably with >> an empty password meaning to skip GSSAPI. My memory is that the >> current behavior for GSSAPI auth-only is to prompt for password if we >> don't find credentials (and if it isn't, there's no reason not to >> unless we're opposed to handling the password). >> >> - Second, we could skip GSSAPI and proceed with the next connection >> method. This might be confusing if the user is then prompted for a >> password and expects it to be for GSSAPI, but we could probably make >> it sensible. I think I prefer the first option. > > Ah, right. I completely forgot that GSSAPI had its own handling of > passwords for users registered to it... > > Isn't this distinction a good point for not implementing "prefer", > "allow" or any equivalents? By that I mean that we should not have any > GSS connection mode that fallbacks to something else if the first one > fails. So we would live with the two following modes: > - "disable", to only try a non-GSS connection > - "enable", or "require", to only try a GSS connection. > That seems quite acceptable to me as a first implementation to just > have that. If it is the password management that is scary here, we could have a prefer-type mode which does not prompt, but only uses existing credentials. Or we could opt to never prompt, which is totally valid.
Robbie, all, * Robbie Harwood (rharwood@redhat.com) wrote: > Michael Paquier <michael.paquier@gmail.com> writes: > > On Wed, Jul 27, 2016 at 12:22 AM, Robbie Harwood <rharwood@redhat.com> wrote: > >> Michael Paquier <michael.paquier@gmail.com> writes: > >> > >> So there's a connection setting `sslmode` that we'll want something > >> similar to here (`gssapimode` or so). `sslmode` has six settings, but I > >> think we only need three for GSSAPI: "disable", "allow", and "prefer" > >> (which presumably would be the default). > > > > Seeing the debate regarding sslmode these days, I would not say that > > "prefer" would be the default, but that's an implementation detail. > > > >> Lets suppose we're working with "prefer". GSSAPI will itself check two > >> places for credentials: client keytab and ccache. But if we don't find > >> credentials there, we as the client have two options on how to proceed. > >> > >> - First, we could prompt for a password (and then call > >> gss_acquire_cred_with_password() to get credentials), presumably with > >> an empty password meaning to skip GSSAPI. My memory is that the > >> current behavior for GSSAPI auth-only is to prompt for password if we > >> don't find credentials (and if it isn't, there's no reason not to > >> unless we're opposed to handling the password). > >> > >> - Second, we could skip GSSAPI and proceed with the next connection > >> method. This might be confusing if the user is then prompted for a > >> password and expects it to be for GSSAPI, but we could probably make > >> it sensible. I think I prefer the first option. > > > > Ah, right. I completely forgot that GSSAPI had its own handling of > > passwords for users registered to it... > > > > Isn't this distinction a good point for not implementing "prefer", > > "allow" or any equivalents? By that I mean that we should not have any > > GSS connection mode that fallbacks to something else if the first one > > fails. So we would live with the two following modes: > > - "disable", to only try a non-GSS connection > > - "enable", or "require", to only try a GSS connection. > > That seems quite acceptable to me as a first implementation to just > > have that. > > If it is the password management that is scary here, we could have a > prefer-type mode which does not prompt, but only uses existing > credentials. Or we could opt to never prompt, which is totally valid. For my 2c, at least, I'd like the "prefer" option when it comes to encryption where we try to use encryption if we're doing GSSAPI authentication. I'm not a big fan of having the GSSAPI layer doing password prompts, but as long as the *only* thing that does is go through the Kerberos library to acquire the tickets and still completely talks GSSAPI with the server, it seems reasonable. If we end up having to fall back to a non-encrypted GSSAPI authenticated session then we should make noise about that. If the authentication is handled through GSSAPI but the connection is not encrypted, but the user is told that immediately (eg: in the psql startup), it seems like there's relativly little sensitive information which has been exposed at that point and the user could destroy the session then, if they're concerned about it. Of course, that only works for user sessions, such as with psql, and doesn't help with application connections, but hopefully application authors who are using GSSAPI will read the docs sufficiently to know that they should require an encrypted connection, if their environment demands it. Thanks! Stephen
Hello -hackers, Zombie patch is back from the dead. It's been a bit more than two years since v12 (the last major revision) and almost three since it was originally submitted. (I do have enough pride to point out that it did not actually /take/ anywhere close to two years to update it.) CC'd are reviewers from before; I appreciate their input from before, but there is of course no obligation for them to page all this back in, especially if they don't want to. A large chunk of this code is unchanged from previous iterations of the patch, but this is a major re-architect. Various things have also been previously fixed as part of the GSSAPI testing efforts, for which I am grateful. So: this is GSSAPI encryption support for libpq. Based on feedback on previous versions, GSSAPI encryption has a separate negotiation step - similar to SSL negotiation. I've tried to incorporate all other feedback I've received thus far, but very likely missed things (and introduced new problems). To actually see encryption, you'll first need to configure the server as for GSSAPI authentication. You'll also need to ensure the HBA configuration has a rule that will permit it. However, there should hopefully be enough information to set this up in the corresponding docs changes (and if there isn't, I should fix it). The Kerberos/GSSAPI implementation shouldn't matter, but I am testing using MIT krb5 (through freeIPA); I wrote a post a while back for my setup here: https://mivehind.net/2015/06/11/kerberized-postgresql/ Finally, I've submitted this as a single patch because it was requested previously. I'm happy to break it apart into many commits instead, if that's helpful. Thanks, --Robbie From 45de59244e4b9ef887cf910a17cbe63c9043f17e Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Thu, 10 May 2018 16:12:03 -0400 Subject: [PATCH] libpq GSSAPI encryption support On both the frontend and backend, prepare for GSSAPI encryption support by moving common code for error handling into a separate file. Fix a TODO for handling multiple status messages in the process. Eliminate the OIDs, which have not been needed for some time. Add frontend and backend encryption support functions. Keep the context initiation for authentication-only separate on both the frontend and backend in order to avoid concerns about changing the requested flags to include encryption support. In postmaster, pull GSSAPI authorization checking into a shared function. Also share the initiator name between the encryption and non-encryption codepaths. Modify pqsecure_write() to take a non-const pointer. The pointer will not be modified, but this change (or a const-discarding cast, or a malloc()+memcpy()) is necessary for GSSAPI due to const/struct interactions in C. For HBA, add "hostgss" and "hostnogss" entries that behave similarly to their SSL counterparts. "hostgss" requires either "gss", "trust", or "reject" for its authentication. Simiarly, add a "gssmode" parameter to libpq. Supported values are "disable", "require", and "prefer". Notably, negotiation will only be attempted if credentials can be acquired. Move credential acquisition into its own function to support this behavior. Finally, add documentation for everything new, and update existing documentation on connection security. Thanks to Michael Paquier for the Windows fixes. --- doc/src/sgml/client-auth.sgml | 75 ++++-- doc/src/sgml/libpq.sgml | 60 ++++- doc/src/sgml/runtime.sgml | 80 +++++- src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 103 +++---- src/backend/libpq/be-gssapi-common.c | 64 +++++ src/backend/libpq/be-gssapi-common.h | 26 ++ src/backend/libpq/be-secure-gssapi.c | 319 ++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 ++ src/backend/libpq/hba.c | 51 +++- src/backend/postmaster/pgstat.c | 3 + src/backend/postmaster/postmaster.c | 64 ++++- src/include/libpq/hba.h | 4 +- src/include/libpq/libpq-be.h | 11 +- src/include/libpq/libpq.h | 3 + src/include/libpq/pqcomm.h | 5 +- src/include/pgstat.h | 3 +- src/interfaces/libpq/Makefile | 4 + src/interfaces/libpq/fe-auth.c | 90 +------ src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++- src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++ src/interfaces/libpq/fe-gssapi-common.h | 23 ++ src/interfaces/libpq/fe-secure-gssapi.c | 343 ++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-fe.h | 3 +- src/interfaces/libpq/libpq-int.h | 30 ++- src/tools/msvc/Mkvcbuild.pm | 20 +- 27 files changed, 1578 insertions(+), 202 deletions(-) create mode 100644 src/backend/libpq/be-gssapi-common.c create mode 100644 src/backend/libpq/be-gssapi-common.h create mode 100644 src/backend/libpq/be-secure-gssapi.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.h 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 656d5f9417..38cf32e3be 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> </synopsis> The meaning of the fields is as follows: @@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> This record matches connection attempts made using TCP/IP. - <literal>host</literal> records match either + <literal>host</literal> records match <acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection - attempts. + attempts as well as <acronym>GSSAPI</acronym> or + non-<acronym>GSSAPI</acronym> connection attempts. </para> <note> <para> @@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><literal>hostgss</literal></term> + <listitem> + <para> + This record matches connection attempts made using TCP/IP, + but only when the connection is made with <acronym>GSSAPI</acronym> + encryption. + </para> + + <para> + To make use of this option the server must be built with + <acronym>GSSAPI</acronym> support. Otherwise, + the <literal>hostgss</literal> record is ignored except for logging a + warning that it cannot match any connections. + </para> + + <para> + Note that the only supported <xref linkend="auth-methods"/> for use + with <acronym>GSSAPI</acronym> encryption + are <literal>gss</literal>, <literal>reject</literal>, + and <literal>trust</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>hostnogss</literal></term> + <listitem> + <para> + This record type has the opposite behavior of <literal>hostgss</literal>; + it only matches connection attempts made over + TCP/IP that do not use <acronym>GSSAPI</acronym>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable>database</replaceable></term> <listitem> @@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> Use GSSAPI to authenticate the user. This is only - available for TCP/IP connections. See <xref - linkend="gssapi-auth"/> for details. + available for TCP/IP connections . See <xref + linkend="gssapi-auth"/> for details. It can be used in conjunction + with GSSAPI encryption. </para> </listitem> </varlistentry> @@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256 host all mike .example.com md5 host all all .example.com scram-sha-256 -# In the absence of preceding "host" lines, these two lines will +# In the absence of preceding "host" lines, these three lines will # reject all connections from 192.168.54.1 (since that entry will be -# matched first), but allow GSSAPI connections from anywhere else -# on the Internet. The zero mask causes no bits of the host IP +# matched first), but allow GSSAPI-encrypted connections from anywhere else +# on the Internet. Unencrypted GSSAPI connections are allowed from +# 192.168.12.10 only. The zero mask causes no bits of the host IP # address to be considered, so it matches any host. # # TYPE DATABASE USER ADDRESS METHOD host all all 192.168.54.1/32 reject -host all all 0.0.0.0/0 gss +hostgss all all 0.0.0.0/0 gss +host all all 192.168.12.10/32 gss # Allow users from 192.168.x.x hosts to connect to any database, if # they pass the ident check. If, for example, ident says the user is @@ -1051,13 +1093,16 @@ omicron bryanh guest1 <para> <productname>GSSAPI</productname> is an industry-standard protocol for secure authentication defined in RFC 2743. - <productname>PostgreSQL</productname> supports - <productname>GSSAPI</productname> with <productname>Kerberos</productname> - authentication according to RFC 1964. <productname>GSSAPI</productname> - 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. + + <productname>PostgreSQL</productname> + supports <productname>GSSAPI</productname> for use as either an encrypted, + authenticated layer, or as authentication + only. <productname>GSSAPI</productname> provides automatic authentication + (single sign-on) for systems that support it. The authentication itself is + secure. If <productname>GSSAPI</productname> encryption + (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are + used, the data sent along the database connection will be encrypted; + otherwise, it will not. </para> <para> diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 800e68a19e..24bf6d3f44 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1320,6 +1320,64 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode"> + <term><literal>gssmode</literal></term> + <listitem> + <para> + This option determines whether or with what priority a secure + <acronym>GSS</acronym> TCP/IP connection will be negotiated with the + server. There are three modes: + + <variablelist> + <varlistentry> + <term><literal>disable</literal></term> + <listitem> + <para> + only try a non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>prefer</literal> (default)</term> + <listitem> + <para> + if there are <acronym>GSSAPI</acronym> credentials present (i.e., + in a credentials cache), first try + a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or + there are no credentials, try a + non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>require</literal></term> + <listitem> + <para> + only try a <acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + </variablelist> + + See <xref linkend="libpq-gss"/> for a detailed description of how + these options work. + </para> + + <para> + <literal>gssmode</literal> is ignored for Unix domain socket + communication. If <productname>PostgreSQL</productname> is compiled + without GSSAPI support, using the <literal>require</literal> option + will cause an error, while <literal>prefer</literal> will be accepted + but <application>libpq</application> will not actually attempt + a <acronym>GSSAPI</acronym>-encrypted + connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with + libpq</secondary></indexterm> + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-sslmode" xreflabel="sslmode"> <term><literal>sslmode</literal></term> <listitem> @@ -7872,7 +7930,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*) </para> <para> - For a connection to be known secure, SSL usage must be configured + For a connection to be known SSL-secured, SSL usage must be configured on <emphasis>both the client and the server</emphasis> before the connection is made. If it is only configured on the server, the client may end up sending sensitive information (e.g. passwords) before diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 330e38a29e..4e26644b37 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 </para> <para> - To prevent spoofing on TCP connections, the best solution is to use - SSL certificates and make sure that clients check the server's certificate. - To do that, the server + To prevent spoofing on TCP connections, either use + SSL certificates and make sure that clients check the server's certificate, + or use GSSAPI encryption (or both, if they're on separate connections). + </para> + + <para> + To prevent spoofing with SSL, the server must be configured to accept only <literal>hostssl</literal> connections (<xref linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files (<xref linkend="ssl-tcp"/>). The TCP client must connect using @@ -2018,6 +2022,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 <literal>verify-full</literal> and have the appropriate root certificate file installed (<xref linkend="libq-ssl-certificates"/>). </para> + + <para> + To prevent spoofing with GSSAPI, the server must be configured to accept + only <literal>hostgss</literal> connections + (<xref linkend="auth-pg-hba.conf"/>) and use <literal>gss</literal> + authentication with them. The TCP client must connect + using <literal>gssmode=require</literal>. </sect1> <sect1 id="encryption-options"> @@ -2114,8 +2125,28 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 which hosts can use non-encrypted connections (<literal>host</literal>) and which require SSL-encrypted connections (<literal>hostssl</literal>). Also, clients can specify that they - connect to servers only via SSL. <application>Stunnel</application> or - <application>SSH</application> can also be used to encrypt transmissions. + connect to servers only via SSL. + </para> + </listitem> + + <listitem> + <para> + GSSAPI-encrypted connections encrypt all data sent across the network, + including queries and data returned. (No password is sent across the + network.) The <filename>pg_hba.conf</filename> file allows + administrators to specify which hosts can use non-encrypted connections + (<literal>host</literal>) and which require GSSAPI-encrypted connections + (<literal>hostgss</literal>). Also, clients can specify that they + connect to servers only on GSSAPI-encrypted connections + (<literal>gssmode=require</literal>). + </para> + </listitem> + + <listitem> + <para> + <application>Stunnel</application> or + <application>SSH</application> can also be used to encrypt + transmissions. </para> </listitem> </varlistentry> @@ -2505,6 +2536,45 @@ openssl x509 -req -in server.csr -text -days 365 \ </sect1> + <sect1 id="gssapi-enc"> + <title>Secure TCP/IP Connections with GSSAPI encryption</title> + + <indexterm zone="gssapi-enc"> + <primary>gssapi</primary> + </indexterm> + + <para> + <productname>PostgreSQL</productname> also has native support for + using <acronym>GSSAPI</acronym> to encrypt client/server communications for + increased security. Support requires that a <acronym>GSSAPI</acronym> + implementation (such as MIT krb5) is installed on both client and server + systems, and that support in <productname>PostgreSQL</productname> is + enabled at build time (see <xref linked="installation"/>). + </para> + + <sect2 id="gssapi-setup"> + <title>Basic Setup</title> + + <para> + The <productname>PostgreSQL</productname> server will listen for both + normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP + port, and will negotiate with any connecting client on whether to + use <acronym>GSSAPI</acronym> for encryption (and for authentication). By + default, this decision is up to the client (which means it can be + downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about + setting up the server to require the use of <acronym>GSSAPI</acronym> for + some or all conections. + </para> + + <para> + Other than configuration of the negotiation + behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond + that which necessary for GSSAPI authentication. (For more information on + configuring that, see <xref linkend="gssapi-auth"/>.) + </para> + </sect2> + </sect1> + <sect1 id="ssh-tunnels"> <title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title> diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 3dbec23e30..47efef0682 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 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 3014b17a7c..6e4cd66a90 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -173,12 +173,9 @@ bool pg_krb_caseins_users; *---------------------------------------------------------------- */ #ifdef ENABLE_GSS -#if defined(HAVE_GSSAPI_H) -#include <gssapi.h> -#else -#include <gssapi/gssapi.h> -#endif +#include "be-gssapi-common.h" +static int pg_GSS_checkauth(Port *port); static int pg_GSS_recvauth(Port *port); #endif /* ENABLE_GSS */ @@ -384,6 +381,17 @@ ClientAuthentication(Port *port) errmsg("connection requires a valid client certificate"))); } +#ifdef ENABLE_GSS + if (port->gss->enc && port->hba->auth_method != uaReject && + port->hba->auth_method != uaImplicitReject && + port->hba->auth_method != uaTrust && + port->hba->auth_method != uaGSS) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("GSSAPI encryption cannot be combined with non-GSSAPI authentication"))); + } +#endif + /* * Now proceed to do the actual authentication check */ @@ -524,8 +532,13 @@ ClientAuthentication(Port *port) case uaGSS: #ifdef ENABLE_GSS - sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); - status = pg_GSS_recvauth(port); + if (port->gss->enc) + status = pg_GSS_checkauth(port); + else + { + sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); + status = pg_GSS_recvauth(port); + } #else Assert(false); #endif @@ -1044,64 +1057,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) *---------------------------------------------------------------- */ #ifdef ENABLE_GSS - -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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, const 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) { @@ -1110,7 +1065,6 @@ pg_GSS_recvauth(Port *port) lmin_s, gflags; int mtype; - int ret; StringInfoData buf; gss_buffer_desc gbuf; @@ -1263,10 +1217,17 @@ pg_GSS_recvauth(Port *port) */ gss_release_cred(&min_stat, &port->gss->cred); } + return pg_GSS_checkauth(port); +} + +static int +pg_GSS_checkauth(Port *port) +{ + int ret; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc gbuf; /* - * GSS_S_COMPLETE indicates that authentication is now complete. - * * Get the name of the user that authenticated, and compare it to the pg * username that was specified for the connection. */ @@ -1308,7 +1269,7 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI realm (%s) and configured realm (%s) don't match", cp, port->hba->krb_realm); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } } @@ -1318,14 +1279,14 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI did not return realm but realm matching was requested"); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, pg_krb_caseins_users); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return ret; } diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c new file mode 100644 index 0000000000..78d9f5d325 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,64 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.c + * Common code for GSSAPI authentication and encryption + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/libpq/be-gssapi-common.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +static void +pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type) +{ + gss_buffer_desc gmsg; + size_t i = 0; + OM_uint32 lmin_s, msg_ctx = 0; + + gmsg.value = NULL; + gmsg.length = 0; + + do + { + gss_display_status(&lmin_s, stat, type, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(s + i, gmsg.value, len - i); + i += gmsg.length; + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < len); + + if (msg_ctx || i == len) + ereport(WARNING, + (errmsg_internal("incomplete GSS error report"))); +} + +void +pg_GSS_error(int severity, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat) +{ + char msg_major[128], msg_minor[128]; + + /* Fetch major status message */ + pg_GSS_error_int(msg_major, sizeof(msg_major), maj_stat, GSS_C_GSS_CODE); + + /* Fetch mechanism minor status message */ + pg_GSS_error_int(msg_minor, sizeof(msg_minor), min_stat, GSS_C_MECH_CODE); + + /* + * 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/backend/libpq/be-gssapi-common.h b/src/backend/libpq/be-gssapi-common.h new file mode 100644 index 0000000000..c020051d2e --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.h + * Definitions for GSSAPI authentication and encryption handling + * + * Portions Copyright (c) 1996-2018, 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, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat); + +#endif /* BE_GSSAPI_COMMON_H */ diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c new file mode 100644 index 0000000000..d860f71b71 --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,319 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gssapi.c + * GSSAPI encryption support + * + * Portions Copyright (c) 2018-2018, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +#include "libpq/auth.h" +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "pgstat.h" + +#include <unistd.h> + +static ssize_t +send_buffered_data(Port *port, size_t len) +{ + ssize_t ret = secure_raw_write( + port, + port->gss->writebuf.data + port->gss->writebuf.cursor, + port->gss->writebuf.len - port->gss->writebuf.cursor); + if (ret < 0) + return ret; + + /* update and possibly clear buffer state */ + port->gss->writebuf.cursor += ret; + + if (port->gss->writebuf.cursor == port->gss->writebuf.len) + { + /* entire request has now been written */ + resetStringInfo(&port->gss->writebuf); + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +be_gssapi_write(Port *port, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + pg_gssinfo *gss = port->gss; + + if (gss->writebuf.len != 0) + return send_buffered_data(port, len); + + /* encrypt the message */ + output.value = NULL; + output.length = 0; + input.value = ptr; + input.length = len; + + major = gss_wrap(&minor, 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); + goto cleanup; + } else if (conf == 0) + { + ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality"))); + goto cleanup; + } + + /* 4 network-order length bytes, then payload */ + netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, output.value, output.length); + + ret = send_buffered_data(port, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* load up any available data */ + if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len) + { + /* clamp length */ + if (len > gss->buf.len - gss->buf.cursor) + len = gss->buf.len - gss->buf.cursor; + + memcpy(ptr, gss->buf.data + gss->buf.cursor, len); + gss->buf.cursor += len; + ret = len; + } + + /* reset buffer if all data has been read */ + if (gss->buf.cursor == gss->buf.len) + resetStringInfo(&gss->buf); + + return ret; +} + +static ssize_t +load_packetlen(Port *port) +{ + pg_gssinfo *gss = port->gss; + ssize_t ret; + + if (gss->buf.len < 4) + { + enlargeStringInfo(&gss->buf, 4 - gss->buf.len); + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + 4 - gss->buf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(Port *port, size_t len) +{ + ssize_t ret; + pg_gssinfo *gss = port->gss; + + enlargeStringInfo(&gss->buf, len - gss->buf.len + 4); + + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + len - gss->buf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len - 4 < len) + { + errno = EWOULDBLOCK; + return -1; + } + return 0; +} + +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; + pg_gssinfo *gss = port->gss; + + if (gss->buf.cursor > 0) + return read_from_buffer(gss, ptr, len); + + /* load length if not present */ + ret = load_packetlen(port); + if (ret != 0) + return ret; + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + return ret; + + /* decrypt the packet */ + output.value = NULL; + output.length = 0; + input.value = gss->buf.data + 4; + + major = gss_unwrap(&minor, 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; + } + + /* put the decrypted packet in the buffer */ + resetStringInfo(&gss->buf); + enlargeStringInfo(&gss->buf, output.length); + + memcpy(gss->buf.data, output.value, output.length); + gss->buf.len = output.length; + gss->buf.data[gss->buf.len] = '\0'; + + ret = read_from_buffer(gss, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +ssize_t +secure_open_gssapi(Port *port) +{ + pg_gssinfo *gss = port->gss; + bool complete_next = false; + + /* + * Use the configured keytab, if there is one. Unfortunately, Heimdal + * doesn't support the cred store extensions, so use the env var. + */ + if (pg_krb_server_keyfile != NULL && strlen(pg_krb_server_keyfile) > 0) + setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1); + + while (true) + { + OM_uint32 major, minor; + size_t ret; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + + /* Handle any outgoing data */ + if (gss->writebuf.len != 0) + { + ret = send_buffered_data(port, 1); + if (ret != 1) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + } + + if (complete_next) + break; + + /* Load incoming data */ + ret = load_packetlen(port); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + input.value = gss->buf.data + 4; + + /* Process incoming data. (The client sends first.) */ + major = gss_accept_sec_context(&minor, &port->gss->ctx, + GSS_C_NO_CREDENTIAL, &input, + GSS_C_NO_CHANNEL_BINDINGS, + &port->gss->name, NULL, &output, NULL, + NULL, NULL); + resetStringInfo(&gss->buf); + if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, gettext_noop("GSSAPI context error"), + major, minor); + gss_release_buffer(&minor, &output); + return -1; + } + else if (!(major & GSS_S_CONTINUE_NEEDED)) + { + /* + * rfc2744 technically permits context negotiation to be complete + * both with and without a packet to be sent. + */ + complete_next = true; + } + + if (output.length != 0) + { + /* Queue packet for writing */ + uint32 netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, + output.value, output.length); + gss_release_buffer(&minor, &output); + continue; + } + + /* We're done - woohoo! */ + break; + } + port->gss->enc = true; + return 0; +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index edfe2c0751..7dd1cf7090 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -152,6 +152,14 @@ retry: n = be_tls_read(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_read(port, ptr, len); + waitfor = WL_SOCKET_READABLE; + } + else #endif { n = secure_raw_read(port, ptr, len); @@ -255,6 +263,14 @@ retry: n = be_tls_write(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_write(port, ptr, len); + waitfor = WL_SOCKET_WRITEABLE; + } + else #endif { n = secure_raw_write(port, ptr, len); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index acf625e4ec..9a1aa4e5cc 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -994,7 +994,9 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) } else if (strcmp(token->string, "host") == 0 || strcmp(token->string, "hostssl") == 0 || - strcmp(token->string, "hostnossl") == 0) + strcmp(token->string, "hostnossl") == 0 || + strcmp(token->string, "hostgss") == 0 || + strcmp(token->string, "hostnogss") == 0) { if (token->string[4] == 's') /* "hostssl" */ @@ -1022,10 +1024,23 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "hostssl record cannot match because SSL is not supported by this build"; #endif } - else if (token->string[4] == 'n') /* "hostnossl" */ + else if (token->string[4] == 'g') /* "hostgss" */ { - parsedline->conntype = ctHostNoSSL; + parsedline->conntype = ctHostGSS; +#ifndef ENABLE_GSS + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("hostgss record cannot match because GSSAPI is not supported by this build"), + errhint("Compile with --with-gssapi to use GSSAPI connections."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "hostgss record cannot match because GSSAPI is not supported by this build"; +#endif } + else if (token->string[4] == 'n' && token->string[6] == 's') + parsedline->conntype = ctHostNoSSL; + else if (token->string[4] == 'n' && token->string[6] == 'g') + parsedline->conntype = ctHostNoGSS; else { /* "host" */ @@ -1404,6 +1419,19 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "gssapi authentication is not supported on local sockets"; return NULL; } + if (parsedline->conntype == ctHostGSS && + parsedline->auth_method != uaGSS && + parsedline->auth_method != uaReject && + parsedline->auth_method != uaTrust) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("GSSAPI encryption only supports gss, trust, or reject authentication"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "GSSAPI encryption only supports gss, trust, or reject authenticaion"; + return NULL; + } if (parsedline->conntype != ctLocal && parsedline->auth_method == uaPeer) @@ -2060,6 +2088,17 @@ check_hba(hbaPort *port) continue; } + /* Check GSSAPI state */ +#ifdef ENABLE_GSS + if (port->gss->enc && hba->conntype == ctHostNoGSS) + continue; + else if (!port->gss->enc && hba->conntype == ctHostGSS) + continue; +#else + if (hba->conntype == ctHostGSS) + continue; +#endif + /* Check IP address */ switch (hba->ip_cmp_method) { @@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, case ctHostNoSSL: typestr = "hostnossl"; break; + case ctHostGSS: + typestr = "hostgss"; + break; + case ctHostNoGSS: + typestr = "hostnogss"; + break; } if (typestr) values[index++] = CStringGetTextDatum(typestr); diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 084573e77c..72ce147308 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -3562,6 +3562,9 @@ pgstat_get_wait_client(WaitEventClient w) case WAIT_EVENT_WAL_SENDER_WRITE_DATA: event_name = "WalSenderWriteData"; break; + case WAIT_EVENT_GSS_OPEN_SERVER: + event_name = "GSSOpenServer"; + break; /* no default case, so that compiler will warn */ } diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a4b53b33cd..87dbb33a88 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -1894,7 +1894,7 @@ initMasks(fd_set *rmask) * if we detect a communications failure.) */ static int -ProcessStartupPacket(Port *port, bool SSLdone) +ProcessStartupPacket(Port *port, bool secure_done) { int32 len; void *buf; @@ -1905,11 +1905,11 @@ ProcessStartupPacket(Port *port, bool SSLdone) if (pq_getbytes((char *) &len, 4) == EOF) { /* - * EOF after SSLdone probably means the client didn't like our + * EOF after secure_done probably means the client didn't like our * response to NEGOTIATE_SSL_CODE. That's not an error condition, so * don't clutter the log with a complaint. */ - if (!SSLdone) + if (!secure_done) ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("incomplete startup packet"))); @@ -1961,7 +1961,7 @@ ProcessStartupPacket(Port *port, bool SSLdone) return STATUS_ERROR; } - if (proto == NEGOTIATE_SSL_CODE && !SSLdone) + if (proto == NEGOTIATE_SSL_CODE && !secure_done) { char SSLok; @@ -1994,6 +1994,32 @@ retry1: /* but not another SSL negotiation request */ return ProcessStartupPacket(port, true); } + else if (proto == NEGOTIATE_GSS_CODE && !secure_done) + { + char GSSok = 'N'; +#ifdef ENABLE_GSS + /* No GSSAPI encryption when on Unix socket */ + if (!IS_AF_UNIX(port->laddr.addr.ss_family)) + GSSok = 'G'; +#endif + + while (send(port->sock, &GSSok, 1, 0) != 1) + { + if (errno == EINTR) + continue; + ereport(COMMERROR, + (errcode_for_socket_access(), + errmsg("failed to send GSSAPI negotiation response: %m)"))); + return STATUS_ERROR; /* close the connection */ + } + +#ifdef ENABLE_GSS + if (GSSok == 'G' && secure_open_gssapi(port) == -1) + return STATUS_ERROR; +#endif + /* Won't ever see more than one negotiation request */ + return ProcessStartupPacket(port, true); + } /* Could add additional special packet types here */ @@ -2413,6 +2439,17 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif #endif return port; @@ -2429,7 +2466,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); } @@ -4761,6 +4806,17 @@ SubPostmasterMain(int argc, char *argv[]) (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif /* * If appropriate, physically re-attach to shared memory segment. We want diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 5f68f4c666..830ddaa25a 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -55,7 +55,9 @@ typedef enum ConnType ctLocal, ctHost, ctHostSSL, - ctHostNoSSL + ctHostNoSSL, + ctHostGSS, + ctHostNoGSS, } ConnType; typedef struct HbaLine diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 7698cd1f88..3e46ac437a 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -66,7 +66,7 @@ typedef struct #include "datatype/timestamp.h" #include "libpq/hba.h" #include "libpq/pqcomm.h" - +#include "lib/stringinfo.h" typedef enum CAC_state { @@ -86,6 +86,9 @@ 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 */ + bool enc; /* GSSAPI encryption in use */ + StringInfoData buf; /* GSSAPI encryption data buffering */ + StringInfoData writebuf; /* GSSAPI nonblocking write buffering */ #endif } pg_gssinfo; #endif @@ -278,6 +281,12 @@ extern char *be_tls_get_peer_finished(Port *port, size_t *len); extern char *be_tls_get_certificate_hash(Port *port, size_t *len); #endif +#ifdef ENABLE_GSS +/* Read and write to a GSSAPI-encrypted connection. */ +extern ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); +extern 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 7bf06c65e9..e34f552ef7 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -92,6 +92,9 @@ extern ssize_t secure_read(Port *port, void *ptr, size_t len); extern ssize_t secure_write(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_read(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_write(Port *port, const void *ptr, size_t len); +#ifdef ENABLE_GSS +extern ssize_t secure_open_gssapi(Port *port); +#endif extern bool ssl_loaded_verify_locations; diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index cc0e0b32c7..ade1190096 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -199,9 +199,10 @@ typedef struct CancelRequestPacket /* - * A client can also start by sending a SSL negotiation request, to get a - * secure channel. + * A client can also start by sending a SSL or GSSAPI negotiation request to + * get a secure channel. */ #define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679) +#define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680) #endif /* PQCOMM_H */ diff --git a/src/include/pgstat.h b/src/include/pgstat.h index be2f59239b..4f06f7a2bc 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -787,7 +787,8 @@ typedef enum WAIT_EVENT_SSL_OPEN_SERVER, WAIT_EVENT_WAL_RECEIVER_WAIT_START, WAIT_EVENT_WAL_SENDER_WAIT_WAL, - WAIT_EVENT_WAL_SENDER_WRITE_DATA + WAIT_EVENT_WAL_SENDER_WRITE_DATA, + WAIT_EVENT_GSS_OPEN_SERVER, } WaitEventClient; /* ---------- diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index abe0a50e98..c814e5e35a 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -57,6 +57,10 @@ else OBJS += sha2.o endif +ifeq ($(with_gssapi),yes) +OBJS += fe-gssapi-common.o fe-secure-gssapi.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 3b2073a47f..af2ef78d76 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -49,52 +49,7 @@ * GSSAPI authentication system. */ -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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. @@ -195,18 +150,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen) static int pg_GSS_startup(PGconn *conn, int payloadlen) { - OM_uint32 maj_stat, - min_stat; - int maxlen; - gss_buffer_desc temp_gbuf; - char *host = PQhost(conn); - - if (!(host && host[0] != '\0')) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("host name must be specified\n")); - return STATUS_ERROR; - } + int ret; if (conn->gctx) { @@ -215,33 +159,9 @@ pg_GSS_startup(PGconn *conn, int payloadlen) return STATUS_ERROR; } - /* - * Import service principal name so the proper ticket can be acquired by - * the GSSAPI system. - */ - maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; - temp_gbuf.value = (char *) malloc(maxlen); - if (!temp_gbuf.value) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory\n")); - return STATUS_ERROR; - } - snprintf(temp_gbuf.value, maxlen, "%s@%s", - conn->krbsrvname, host); - temp_gbuf.length = strlen(temp_gbuf.value); - - maj_stat = gss_import_name(&min_stat, &temp_gbuf, - GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); - free(temp_gbuf.value); - - if (maj_stat != GSS_S_COMPLETE) - { - pg_GSS_error(libpq_gettext("GSSAPI name import error"), - conn, - maj_stat, min_stat); - return STATUS_ERROR; - } + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return ret; /* * Initial packet is the same as a continuation packet with no initial diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index a7e969d7c1..23270f7f6b 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, #else #define DefaultSSLMode "disable" #endif +#ifdef ENABLE_GSS +#include "fe-gssapi-common.h" +#define DefaultGSSMode "prefer" +#else +#define DefaultGSSMode "disable" +#endif /* ---------- * Definition of the conninfo parameters and their fallback resources. @@ -303,6 +309,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Require-Peer", "", 10, offsetof(struct pg_conn, requirepeer)}, + /* + * Expose gssmode similarly to sslmode - we can stil handle "disable" and + * "prefer". + */ + {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL, + "GSS-Mode", "", 7, /* sizeof("disable") == 7 */ + offsetof(struct pg_conn, gssmode)}, + #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* Kerberos and GSSAPI authentication support specifying the service name */ {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL, @@ -1166,6 +1180,39 @@ connectOptions2(PGconn *conn) goto oom_error; } + /* + * validate gssmode option + */ + if (conn->gssmode) + { + if (strcmp(conn->gssmode, "disable") != 0 && + strcmp(conn->gssmode, "prefer") != 0 && + strcmp(conn->gssmode, "require") != 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid gssmode value: \"%s\"\n"), + conn->gssmode); + return false; + } +#ifndef ENABLE_GSS + if (strcmp(conn->gssmode, "require") != 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer( + &conn->errorMessage, + libpq_gettext("no GSSAPI support; cannot require GSSAPI\n")); + return false; + } +#endif + } + else + { + conn->gssmode = strdup(DefaultGSSMode); + if (!conn->gssmode) + goto oom_error; + } + /* * Resolve special "auto" client_encoding from the locale */ @@ -1804,6 +1851,11 @@ connectDBStart(PGconn *conn) conn->wait_ssl_try = true; #endif +#ifdef ENABLE_GSS + if (conn->gssmode[0] == 'd') /* "disable" */ + conn->try_gss = false; +#endif + /* * Set up to try to connect, with protocol 3.0 as the first attempt. */ @@ -2051,6 +2103,7 @@ PQconnectPoll(PGconn *conn) case CONNECTION_NEEDED: case CONNECTION_CHECK_WRITABLE: case CONNECTION_CONSUME: + case CONNECTION_GSS_STARTUP: break; default: @@ -2426,17 +2479,54 @@ keep_going: /* We will come back to here until there is } #endif /* HAVE_UNIX_SOCKETS */ + if (IS_AF_UNIX(conn->raddr.addr.ss_family)) + { + /* Don't request SSL or GSSAPI over Unix sockets */ #ifdef USE_SSL + conn->allow_ssl_try = false; +#endif +#ifdef ENABLE_GSS + conn->try_gss = false; +#endif + } +#ifdef ENABLE_GSS + /* + * If GSSAPI is enabled and we have a ccache, try to set it up + * before sending startup messages. If it's already + * operating, don't try SSL and instead just build the startup + * packet. + */ + if (conn->try_gss && !conn->gctx) + conn->try_gss = pg_GSS_have_ccache(&conn->gcred); + if (conn->try_gss && !conn->gctx) + { + ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE); + if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not send GSSAPI negotiation packet: %s\n"), + SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + goto error_return; + } + + /* Ok, wait for response */ + conn->status = CONNECTION_GSS_STARTUP; + return PGRES_POLLING_READING; + } + else if (!conn->gctx && conn->gssmode[0] == 'r') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache,no server support, or using a local socket)\n")); + goto error_return; + } +#endif + +#ifdef USE_SSL /* * If SSL is enabled and we haven't already got it running, * request it instead of sending the startup message. */ - if (IS_AF_UNIX(conn->raddr.addr.ss_family)) - { - /* Don't bother requesting SSL over a Unix socket */ - conn->allow_ssl_try = false; - } if (conn->allow_ssl_try && !conn->wait_ssl_try && !conn->ssl_in_use) { @@ -2629,6 +2719,97 @@ keep_going: /* We will come back to here until there is #endif /* USE_SSL */ } + case CONNECTION_GSS_STARTUP: + { +#ifdef ENABLE_GSS + PostgresPollingStatusType pollres; + + /* + * If we haven't yet, get the postmaster's response to our + * negotiation packet + */ + if (conn->try_gss && !conn->gctx) + { + char gss_ok; + int rdresult = pqReadData(conn); + if (rdresult < 0) + /* pqReadData fills in error message */ + goto error_return; + else if (rdresult == 0) + /* caller failed to wait for data */ + return PGRES_POLLING_READING; + if (pqGetc(&gss_ok, conn) < 0) + /* shouldn't happen... */ + return PGRES_POLLING_READING; + + if (gss_ok == 'E') + { + /* + * Server failure of some sort. Assume it's a + * protocol version support failure, and let's see if + * we can't recover (if it's not, we'll get a better + * error message on retry). Server gets fussy if we + * don't hang up the socket, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + + /* mark byte consumed */ + conn->inStart = conn->inCursor; + + if (gss_ok == 'N') + { + /* Server doesn't want GSSAPI; fall back if we can */ + if (conn->gssmode[0] == 'r') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n")); + goto error_return; + } + + conn->try_gss = false; + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (gss_ok != 'G') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"), + gss_ok); + goto error_return; + } + } + + /* Begin or continue GSSAPI negotiation */ + pollres = pqsecure_open_gss(conn); + if (pollres == PGRES_POLLING_OK) + { + /* All set for startup packet */ + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (pollres == PGRES_POLLING_FAILED && + conn->gssmode[0] == 'p') + { + /* + * We failed, but we can retry on "prefer". Have to drop + * the current connection to do so, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + return pollres; +#else /* !ENABLE_GSS */ + /* unreachable */ + goto error_return; +#endif /* ENABLE_GSS */ + } + /* * Handle authentication exchange: wait for postmaster messages * and respond as necessary. @@ -2781,6 +2962,24 @@ keep_going: /* We will come back to here until there is /* OK, we read the message; mark data consumed */ conn->inStart = conn->inCursor; +#ifdef ENABLE_GSS + /* + * If gssmode is "prefer" and we're using GSSAPI, retry + * without it. + */ + if (conn->gssenc && conn->gssmode[0] == 'p') + { + OM_uint32 minor; + /* postmaster expects us to drop the connection */ + conn->try_gss = false; + conn->gssenc = false; + gss_delete_sec_context(&minor, &conn->gctx, NULL); + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } +#endif + #ifdef USE_SSL /* @@ -3361,6 +3560,11 @@ makeEmptyPGconn(void) conn->wait_ssl_try = false; conn->ssl_in_use = false; #endif +#ifdef ENABLE_GSS + conn->try_gss = true; + initPQExpBuffer(&conn->gbuf); + initPQExpBuffer(&conn->gwritebuf); +#endif /* * We try to send at least 8K at a time, which is the usual size of pipe @@ -3492,10 +3696,28 @@ freePGconn(PGconn *conn) free(conn->sslcompression); if (conn->requirepeer) free(conn->requirepeer); + if (conn->gssmode) + free(conn->gssmode); #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) if (conn->krbsrvname) free(conn->krbsrvname); #endif +#ifdef ENABLE_GSS + if (conn->gcred != GSS_C_NO_CREDENTIAL) + { + OM_uint32 minor; + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + } + if (conn->gctx) + { + OM_uint32 minor; + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = NULL; + } + termPQExpBuffer(&conn->gbuf); + termPQExpBuffer(&conn->gwritebuf); +#endif #if defined(ENABLE_GSS) && defined(ENABLE_SSPI) if (conn->gsslib) free(conn->gsslib); diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c new file mode 100644 index 0000000000..35e195b7a6 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.c @@ -0,0 +1,128 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.c + * The front-end (client) GSSAPI common code + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-gssapi-common.c + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "fe-gssapi-common.h" + +#include "libpq-int.h" +#include "pqexpbuffer.h" + +/* + * 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); +} + +/* + * Check if we can acquire credentials at all. + */ +bool +pg_GSS_have_ccache(gss_cred_id_t *cred_out) +{ + OM_uint32 major, minor; + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + + major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &cred, NULL, NULL); + if (GSS_ERROR(major)) + { + *cred_out = NULL; + return false; + } + *cred_out = cred; + return true; +} + +/* + * Try to load service name for a connection + */ +int +pg_GSS_load_servicename(PGconn *conn) +{ + OM_uint32 maj_stat, min_stat; + int maxlen; + gss_buffer_desc temp_gbuf; + char *host; + + if (conn->gtarg_nam != NULL) + /* Already taken care of - move along */ + return STATUS_OK; + + host = PQhost(conn); + if (!(host && host[0] != '\0')) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("host name must be specified\n")); + return STATUS_ERROR; + } + + /* + * Import service principal name so the proper ticket can be acquired by + * the GSSAPI system. + */ + maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; + temp_gbuf.value = (char *) malloc(maxlen); + if (!temp_gbuf.value) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return STATUS_ERROR; + } + snprintf(temp_gbuf.value, maxlen, "%s@%s", + conn->krbsrvname, host); + temp_gbuf.length = strlen(temp_gbuf.value); + + maj_stat = gss_import_name(&min_stat, &temp_gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); + free(temp_gbuf.value); + + if (maj_stat != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI name import error"), + conn, + maj_stat, min_stat); + return STATUS_ERROR; + } + return STATUS_OK; +} diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h new file mode 100644 index 0000000000..58811df9f1 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.h + * + * Definitions for GSSAPI common routines + * + * Portions Copyright (c) 1996-2018, 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 + +#include "libpq-fe.h" +#include "libpq-int.h" + +void pg_GSS_error(const char *mprefix, PGconn *conn, + OM_uint32 maj_stat, OM_uint32 min_stat); +bool pg_GSS_have_ccache(gss_cred_id_t *cred_out); +int pg_GSS_load_servicename(PGconn *conn); +#endif /* FE_GSSAPI_COMMON_H */ diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c new file mode 100644 index 0000000000..a71df69ff2 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,343 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-gssapi.c + * The front-end (client) encryption support for GSSAPI + * + * Portions Copyright (c) 2016-2018, 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" + +/* + * Require encryption support, as well as mutual authentication and + * tamperproofing measures. + */ +#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \ + GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG + +static ssize_t +send_buffered_data(PGconn *conn, size_t len) +{ + ssize_t ret = pqsecure_raw_write(conn, + conn->gwritebuf.data + conn->gwritecurs, + conn->gwritebuf.len - conn->gwritecurs); + if (ret < 0) + return ret; + + conn->gwritecurs += ret; + + if (conn->gwritecurs == conn->gwritebuf.len) + { + /* entire request has now been written */ + resetPQExpBuffer(&conn->gwritebuf); + conn->gwritecurs = 0; + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +pg_GSS_write(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + + if (conn->gwritebuf.len != 0) + return send_buffered_data(conn, len); + + /* encrypt the message */ + input.value = ptr; + input.length = len; + + 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); + goto cleanup; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI did not provide confidentiality\n")); + goto cleanup; + } + + /* 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + + ret = send_buffered_data(conn, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* check for available data */ + if (conn->gcursor < conn->gbuf.len) + { + /* clamp length */ + 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; + } + + /* reset buffer if all data has been read */ + if (conn->gcursor == conn->gbuf.len) + { + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + } + + return ret; +} + +static ssize_t +load_packet_length(PGconn *conn) +{ + ssize_t ret; + if (conn->gbuf.len < 4) + { + ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len); + 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 + conn->gbuf.len, + 4 - conn->gbuf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(PGconn *conn, size_t len) +{ + ssize_t ret; + + ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI encrypted packet length %ld too big\n"), + len); + return -1; + } + + /* load any missing parts of the packet */ + if (conn->gbuf.len - 4 < len) + { + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + len - conn->gbuf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < len) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +ssize_t +pg_GSS_read(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = 0; + int conf = 0; + + /* handle any buffered data */ + if (conn->gcursor != 0) + return read_from_buffer(conn, ptr, len); + + /* load in the packet length, if not yet loaded */ + ret = load_packet_length(conn); + if (ret < 0) + return ret; + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0) + return ret; + + /* decrypt the packet */ + 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; + } + + /* load decrypted packet into our buffer */ + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + ret = enlargePQExpBuffer(&conn->gbuf, output.length); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI decrypted packet length %ld too big\n"), + output.length); + ret = -1; + goto cleanup; + } + + memcpy(conn->gbuf.data, output.value, output.length); + conn->gbuf.len = output.length; + conn->gbuf.data[conn->gbuf.len] = '\0'; + + ret = read_from_buffer(conn, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +PostgresPollingStatusType +pqsecure_open_gss(PGconn *conn) +{ + ssize_t ret; + OM_uint32 major, minor; + uint32 netlen; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + + /* Send out any data we might have */ + if (conn->gwritebuf.len != 0) + { + ret = send_buffered_data(conn, 1); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_WRITING; + else if (ret == 1) + /* sent all data */ + return PGRES_POLLING_READING; + return PGRES_POLLING_FAILED; + } + + /* Client sends first, and sending creates a context */ + if (conn->gctx) + { + /* Process any incoming data we might have */ + ret = load_packet_length(conn); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + if (conn->gbuf.data[0] == 'E') + { + /* + * We can taken an error here, and it's my least favorite thing. + * How long can error messages be? That's a good question, but + * backend's pg_gss_error() caps them at 256. Do a single read + * for that and call it a day. Cope with this here rather than in + * load_packet since expandPQExpBuffer will destroy any data in + * the buffer on failure. + */ + load_packet(conn, 256); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Server error: %s"), + conn->gbuf.data + 1); + return PGRES_POLLING_FAILED; + } + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + input.value = conn->gbuf.data + 4; + } + + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return PGRES_POLLING_FAILED; + + major = gss_init_sec_context(&minor, conn->gcred, &conn->gctx, + conn->gtarg_nam, GSS_C_NO_OID, + GSS_REQUIRED_FLAGS, 0, 0, &input, NULL, + &output, NULL, NULL); + resetPQExpBuffer(&conn->gbuf); + if (GSS_ERROR(major)) + { + pg_GSS_error(libpq_gettext("GSSAPI context establishment error"), + conn, major, minor); + return PGRES_POLLING_FAILED; + } + else if (output.length == 0) + { + /* + * We're done - hooray! Kind of gross, but we need to disable SSL + * here so that we don't accidentally tunnel one over the other. + */ +#ifdef USE_SSL + conn->allow_ssl_try = false; +#endif + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + conn->gssenc = true; + return PGRES_POLLING_OK; + } + + /* Queue the token for writing */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + gss_release_buffer(&minor, &output); + return PGRES_POLLING_WRITING; +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index f7dc249bf0..18155e4b5e 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) n = pgtls_read(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_read(conn, ptr, len); + } + else #endif { n = pqsecure_raw_read(conn, ptr, len); @@ -287,7 +294,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; @@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) n = pgtls_write(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_write(conn, ptr, len); + } + else #endif { n = pqsecure_raw_write(conn, ptr, len); diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index ed9c806861..bdd5a10cd8 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -65,8 +65,9 @@ typedef enum CONNECTION_NEEDED, /* Internal state: connect() needed */ CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable * connection. */ - CONNECTION_CONSUME /* Wait for any pending message and consume + CONNECTION_CONSUME, /* Wait for any pending message and consume * them. */ + CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */ } ConnStatusType; typedef enum diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 9a586ff25a..6cf3459a8c 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -22,6 +22,7 @@ /* We assume libpq-fe.h has already been included. */ #include "libpq-events.h" +#include "lib/stringinfo.h" #include <time.h> #ifndef WIN32 @@ -473,9 +474,19 @@ struct pg_conn #endif /* USE_OPENSSL */ #endif /* USE_SSL */ + char *gssmode; /* GSS mode (require,prefer,disable) */ #ifdef ENABLE_GSS gss_ctx_id_t gctx; /* GSS context */ gss_name_t gtarg_nam; /* GSS target name */ + + /* The following are encryption-only */ + 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 try_gss; /* GSS attempting permitted */ + bool gssenc; /* GSS encryption is usable */ + gss_cred_id_t gcred; /* GSS credential temp storage. */ #endif #ifdef ENABLE_SSPI @@ -651,7 +662,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); @@ -745,6 +756,23 @@ extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, int *names_examined, char **first_name); +/* === GSSAPI === */ + +#ifdef ENABLE_GSS + +/* + * Establish a GSSAPI-encrypted connection. + */ +extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn); + +/* + * Read and write functions for GSSAPI-encrypted connections, with internal + * buffering to handle nonblocking sockets. + */ +extern ssize_t pg_GSS_write(PGconn *conn, void *ptr, size_t len); +extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len); +#endif + /* === miscellaneous macros === */ /* diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 593732fd95..b38a41843d 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -183,13 +183,18 @@ sub mkvcbuild $postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); $postgres->FullExportDLL('postgres.lib'); - # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c - # if building without OpenSSL + # The OBJS scraper doesn't know about ifdefs, so remove + # be-secure-openssl.c if building without OpenSSL, and + # be-gssapi-common.c when building with GSSAPI. if (!$solution->{options}->{openssl}) { $postgres->RemoveFile('src/backend/libpq/be-secure-common.c'); $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $postgres->RemoveFile('src/backennd/libpq/be-gssapi-common.c'); + } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', 'src/backend/snowball'); @@ -243,9 +248,10 @@ sub mkvcbuild 'src/interfaces/libpq/libpq.rc'); $libpq->AddReference($libpgport); - # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c - # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if - # building with OpenSSL. + # The OBJS scraper doesn't know about ifdefs, so remove + # fe-secure-openssl.c and sha2_openssl.c if building without + # OpenSSL, and remove sha2.c if building with OpenSSL. Also + # remove fe-gssapi-common.c when building with GSSAPI. if (!$solution->{options}->{openssl}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c'); @@ -256,6 +262,10 @@ sub mkvcbuild { $libpq->RemoveFile('src/common/sha2.c'); } + if (!$solution->{options}->{gss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + } my $libpqwalreceiver = $solution->AddProject('libpqwalreceiver', 'dll', '', -- 2.17.0
Attachment
Greetings Robbie! * Robbie Harwood (rharwood@redhat.com) wrote: > Zombie patch is back from the dead. It's been a bit more than two years > since v12 (the last major revision) and almost three since it was > originally submitted. (I do have enough pride to point out that it did > not actually /take/ anywhere close to two years to update it.) Neat! [...] > Finally, I've submitted this as a single patch because it was requested > previously. I'm happy to break it apart into many commits instead, if > that's helpful. Please be sure to register this on the commitfest app. I'm definitely interested and likely will have some time to take a look at this before then, but we should get it registered there in any case. Thanks! Stephen
Attachment
On Thu, May 24, 2018 at 8:00 AM, Robbie Harwood <rharwood@redhat.com> wrote: > Zombie patch is back from the dead. Hi Robbie, Robots[1] vs zombies: + $postgres->RemoveFile('src/backennd/libpq/be-gssapi-common.c'); Typo, breaks on Windows. runtime.sgml:2032: parser error : Opening and ending tag mismatch: para line 2026 and sect1 </sect1> ^ Docs malformed. [1] http://cfbot.cputube.org/robbie-harwood.html -- Thomas Munro http://www.enterprisedb.com
On Wed, May 23, 2018 at 04:03:12PM -0400, Stephen Frost wrote: >> Finally, I've submitted this as a single patch because it was requested >> previously. I'm happy to break it apart into many commits instead, if >> that's helpful. > > Please be sure to register this on the commitfest app. I'm definitely > interested and likely will have some time to take a look at this before > then, but we should get it registered there in any case. This patch got a mention in one of my slides for next week actually, glad to see this come back. You will need to wait a it for reviews though, as the actual focus is to stabilize v11. -- Michael
Attachment
Thomas Munro <thomas.munro@enterprisedb.com> writes: > On Thu, May 24, 2018 at 8:00 AM, Robbie Harwood <rharwood@redhat.com> wrote: > >> Zombie patch is back from the dead. > > Hi Robbie, > > Robots[1] vs zombies: > > + $postgres->RemoveFile('src/backennd/libpq/be-gssapi-common.c'); > > Typo, breaks on Windows. > > runtime.sgml:2032: parser error : Opening and ending tag mismatch: > para line 2026 and sect1 > </sect1> > ^ > > Docs malformed. > > [1] http://cfbot.cputube.org/robbie-harwood.html Hah, this is great! Looks like I failed to build the docs. Here's an updated version that should fix those. Thanks, --Robbie From 53235b7922bd37e361338d160120657a757d7448 Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Thu, 10 May 2018 16:12:03 -0400 Subject: [PATCH] libpq GSSAPI encryption support On both the frontend and backend, prepare for GSSAPI encryption support by moving common code for error handling into a separate file. Fix a TODO for handling multiple status messages in the process. Eliminate the OIDs, which have not been needed for some time. Add frontend and backend encryption support functions. Keep the context initiation for authentication-only separate on both the frontend and backend in order to avoid concerns about changing the requested flags to include encryption support. In postmaster, pull GSSAPI authorization checking into a shared function. Also share the initiator name between the encryption and non-encryption codepaths. Modify pqsecure_write() to take a non-const pointer. The pointer will not be modified, but this change (or a const-discarding cast, or a malloc()+memcpy()) is necessary for GSSAPI due to const/struct interactions in C. For HBA, add "hostgss" and "hostnogss" entries that behave similarly to their SSL counterparts. "hostgss" requires either "gss", "trust", or "reject" for its authentication. Simiarly, add a "gssmode" parameter to libpq. Supported values are "disable", "require", and "prefer". Notably, negotiation will only be attempted if credentials can be acquired. Move credential acquisition into its own function to support this behavior. Finally, add documentation for everything new, and update existing documentation on connection security. Thanks to Michael Paquier for the Windows fixes. --- doc/src/sgml/client-auth.sgml | 75 ++++-- doc/src/sgml/libpq.sgml | 57 +++- doc/src/sgml/runtime.sgml | 77 +++++- src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 103 +++---- src/backend/libpq/be-gssapi-common.c | 64 +++++ src/backend/libpq/be-gssapi-common.h | 26 ++ src/backend/libpq/be-secure-gssapi.c | 319 ++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 ++ src/backend/libpq/hba.c | 51 +++- src/backend/postmaster/pgstat.c | 3 + src/backend/postmaster/postmaster.c | 64 ++++- src/include/libpq/hba.h | 4 +- src/include/libpq/libpq-be.h | 11 +- src/include/libpq/libpq.h | 3 + src/include/libpq/pqcomm.h | 5 +- src/include/pgstat.h | 3 +- src/interfaces/libpq/Makefile | 4 + src/interfaces/libpq/fe-auth.c | 90 +------ src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++- src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++ src/interfaces/libpq/fe-gssapi-common.h | 23 ++ src/interfaces/libpq/fe-secure-gssapi.c | 343 ++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-fe.h | 3 +- src/interfaces/libpq/libpq-int.h | 30 ++- src/tools/msvc/Mkvcbuild.pm | 20 +- 27 files changed, 1572 insertions(+), 202 deletions(-) create mode 100644 src/backend/libpq/be-gssapi-common.c create mode 100644 src/backend/libpq/be-gssapi-common.h create mode 100644 src/backend/libpq/be-secure-gssapi.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.h 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 656d5f9417..38cf32e3be 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> </synopsis> The meaning of the fields is as follows: @@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> This record matches connection attempts made using TCP/IP. - <literal>host</literal> records match either + <literal>host</literal> records match <acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection - attempts. + attempts as well as <acronym>GSSAPI</acronym> or + non-<acronym>GSSAPI</acronym> connection attempts. </para> <note> <para> @@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><literal>hostgss</literal></term> + <listitem> + <para> + This record matches connection attempts made using TCP/IP, + but only when the connection is made with <acronym>GSSAPI</acronym> + encryption. + </para> + + <para> + To make use of this option the server must be built with + <acronym>GSSAPI</acronym> support. Otherwise, + the <literal>hostgss</literal> record is ignored except for logging a + warning that it cannot match any connections. + </para> + + <para> + Note that the only supported <xref linkend="auth-methods"/> for use + with <acronym>GSSAPI</acronym> encryption + are <literal>gss</literal>, <literal>reject</literal>, + and <literal>trust</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>hostnogss</literal></term> + <listitem> + <para> + This record type has the opposite behavior of <literal>hostgss</literal>; + it only matches connection attempts made over + TCP/IP that do not use <acronym>GSSAPI</acronym>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable>database</replaceable></term> <listitem> @@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> Use GSSAPI to authenticate the user. This is only - available for TCP/IP connections. See <xref - linkend="gssapi-auth"/> for details. + available for TCP/IP connections . See <xref + linkend="gssapi-auth"/> for details. It can be used in conjunction + with GSSAPI encryption. </para> </listitem> </varlistentry> @@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256 host all mike .example.com md5 host all all .example.com scram-sha-256 -# In the absence of preceding "host" lines, these two lines will +# In the absence of preceding "host" lines, these three lines will # reject all connections from 192.168.54.1 (since that entry will be -# matched first), but allow GSSAPI connections from anywhere else -# on the Internet. The zero mask causes no bits of the host IP +# matched first), but allow GSSAPI-encrypted connections from anywhere else +# on the Internet. Unencrypted GSSAPI connections are allowed from +# 192.168.12.10 only. The zero mask causes no bits of the host IP # address to be considered, so it matches any host. # # TYPE DATABASE USER ADDRESS METHOD host all all 192.168.54.1/32 reject -host all all 0.0.0.0/0 gss +hostgss all all 0.0.0.0/0 gss +host all all 192.168.12.10/32 gss # Allow users from 192.168.x.x hosts to connect to any database, if # they pass the ident check. If, for example, ident says the user is @@ -1051,13 +1093,16 @@ omicron bryanh guest1 <para> <productname>GSSAPI</productname> is an industry-standard protocol for secure authentication defined in RFC 2743. - <productname>PostgreSQL</productname> supports - <productname>GSSAPI</productname> with <productname>Kerberos</productname> - authentication according to RFC 1964. <productname>GSSAPI</productname> - 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. + + <productname>PostgreSQL</productname> + supports <productname>GSSAPI</productname> for use as either an encrypted, + authenticated layer, or as authentication + only. <productname>GSSAPI</productname> provides automatic authentication + (single sign-on) for systems that support it. The authentication itself is + secure. If <productname>GSSAPI</productname> encryption + (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are + used, the data sent along the database connection will be encrypted; + otherwise, it will not. </para> <para> diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 800e68a19e..73d233dc16 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1320,6 +1320,61 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode"> + <term><literal>gssmode</literal></term> + <listitem> + <para> + This option determines whether or with what priority a secure + <acronym>GSS</acronym> TCP/IP connection will be negotiated with the + server. There are three modes: + + <variablelist> + <varlistentry> + <term><literal>disable</literal></term> + <listitem> + <para> + only try a non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>prefer</literal> (default)</term> + <listitem> + <para> + if there are <acronym>GSSAPI</acronym> credentials present (i.e., + in a credentials cache), first try + a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or + there are no credentials, try a + non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>require</literal></term> + <listitem> + <para> + only try a <acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + <literal>gssmode</literal> is ignored for Unix domain socket + communication. If <productname>PostgreSQL</productname> is compiled + without GSSAPI support, using the <literal>require</literal> option + will cause an error, while <literal>prefer</literal> will be accepted + but <application>libpq</application> will not actually attempt + a <acronym>GSSAPI</acronym>-encrypted + connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with + libpq</secondary></indexterm> + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-sslmode" xreflabel="sslmode"> <term><literal>sslmode</literal></term> <listitem> @@ -7872,7 +7927,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*) </para> <para> - For a connection to be known secure, SSL usage must be configured + For a connection to be known SSL-secured, SSL usage must be configured on <emphasis>both the client and the server</emphasis> before the connection is made. If it is only configured on the server, the client may end up sending sensitive information (e.g. passwords) before diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 330e38a29e..06bf56153c 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 </para> <para> - To prevent spoofing on TCP connections, the best solution is to use - SSL certificates and make sure that clients check the server's certificate. - To do that, the server + To prevent spoofing on TCP connections, either use + SSL certificates and make sure that clients check the server's certificate, + or use GSSAPI encryption (or both, if they're on separate connections). + </para> + + <para> + To prevent spoofing with SSL, the server must be configured to accept only <literal>hostssl</literal> connections (<xref linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files (<xref linkend="ssl-tcp"/>). The TCP client must connect using @@ -2018,6 +2022,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 <literal>verify-full</literal> and have the appropriate root certificate file installed (<xref linkend="libq-ssl-certificates"/>). </para> + + <para> + To prevent spoofing with GSSAPI, the server must be configured to accept + only <literal>hostgss</literal> connections + (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal> + authentication with them. The TCP client must connect + using <literal>gssmode=require</literal>. + </para> </sect1> <sect1 id="encryption-options"> @@ -2114,8 +2126,24 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 which hosts can use non-encrypted connections (<literal>host</literal>) and which require SSL-encrypted connections (<literal>hostssl</literal>). Also, clients can specify that they - connect to servers only via SSL. <application>Stunnel</application> or - <application>SSH</application> can also be used to encrypt transmissions. + connect to servers only via SSL. + </para> + + <para> + GSSAPI-encrypted connections encrypt all data sent across the network, + including queries and data returned. (No password is sent across the + network.) The <filename>pg_hba.conf</filename> file allows + administrators to specify which hosts can use non-encrypted connections + (<literal>host</literal>) and which require GSSAPI-encrypted connections + (<literal>hostgss</literal>). Also, clients can specify that they + connect to servers only on GSSAPI-encrypted connections + (<literal>gssmode=require</literal>). + </para> + + <para> + <application>Stunnel</application> or + <application>SSH</application> can also be used to encrypt + transmissions. </para> </listitem> </varlistentry> @@ -2505,6 +2533,45 @@ openssl x509 -req -in server.csr -text -days 365 \ </sect1> + <sect1 id="gssapi-enc"> + <title>Secure TCP/IP Connections with GSSAPI encryption</title> + + <indexterm zone="gssapi-enc"> + <primary>gssapi</primary> + </indexterm> + + <para> + <productname>PostgreSQL</productname> also has native support for + using <acronym>GSSAPI</acronym> to encrypt client/server communications for + increased security. Support requires that a <acronym>GSSAPI</acronym> + implementation (such as MIT krb5) is installed on both client and server + systems, and that support in <productname>PostgreSQL</productname> is + enabled at build time (see <xref linkend="installation"/>). + </para> + + <sect2 id="gssapi-setup"> + <title>Basic Setup</title> + + <para> + The <productname>PostgreSQL</productname> server will listen for both + normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP + port, and will negotiate with any connecting client on whether to + use <acronym>GSSAPI</acronym> for encryption (and for authentication). By + default, this decision is up to the client (which means it can be + downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about + setting up the server to require the use of <acronym>GSSAPI</acronym> for + some or all conections. + </para> + + <para> + Other than configuration of the negotiation + behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond + that which necessary for GSSAPI authentication. (For more information on + configuring that, see <xref linkend="gssapi-auth"/>.) + </para> + </sect2> + </sect1> + <sect1 id="ssh-tunnels"> <title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title> diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 3dbec23e30..47efef0682 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 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 3014b17a7c..6e4cd66a90 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -173,12 +173,9 @@ bool pg_krb_caseins_users; *---------------------------------------------------------------- */ #ifdef ENABLE_GSS -#if defined(HAVE_GSSAPI_H) -#include <gssapi.h> -#else -#include <gssapi/gssapi.h> -#endif +#include "be-gssapi-common.h" +static int pg_GSS_checkauth(Port *port); static int pg_GSS_recvauth(Port *port); #endif /* ENABLE_GSS */ @@ -384,6 +381,17 @@ ClientAuthentication(Port *port) errmsg("connection requires a valid client certificate"))); } +#ifdef ENABLE_GSS + if (port->gss->enc && port->hba->auth_method != uaReject && + port->hba->auth_method != uaImplicitReject && + port->hba->auth_method != uaTrust && + port->hba->auth_method != uaGSS) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("GSSAPI encryption cannot be combined with non-GSSAPI authentication"))); + } +#endif + /* * Now proceed to do the actual authentication check */ @@ -524,8 +532,13 @@ ClientAuthentication(Port *port) case uaGSS: #ifdef ENABLE_GSS - sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); - status = pg_GSS_recvauth(port); + if (port->gss->enc) + status = pg_GSS_checkauth(port); + else + { + sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); + status = pg_GSS_recvauth(port); + } #else Assert(false); #endif @@ -1044,64 +1057,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) *---------------------------------------------------------------- */ #ifdef ENABLE_GSS - -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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, const 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) { @@ -1110,7 +1065,6 @@ pg_GSS_recvauth(Port *port) lmin_s, gflags; int mtype; - int ret; StringInfoData buf; gss_buffer_desc gbuf; @@ -1263,10 +1217,17 @@ pg_GSS_recvauth(Port *port) */ gss_release_cred(&min_stat, &port->gss->cred); } + return pg_GSS_checkauth(port); +} + +static int +pg_GSS_checkauth(Port *port) +{ + int ret; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc gbuf; /* - * GSS_S_COMPLETE indicates that authentication is now complete. - * * Get the name of the user that authenticated, and compare it to the pg * username that was specified for the connection. */ @@ -1308,7 +1269,7 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI realm (%s) and configured realm (%s) don't match", cp, port->hba->krb_realm); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } } @@ -1318,14 +1279,14 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI did not return realm but realm matching was requested"); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, pg_krb_caseins_users); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return ret; } diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c new file mode 100644 index 0000000000..78d9f5d325 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,64 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.c + * Common code for GSSAPI authentication and encryption + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/libpq/be-gssapi-common.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +static void +pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type) +{ + gss_buffer_desc gmsg; + size_t i = 0; + OM_uint32 lmin_s, msg_ctx = 0; + + gmsg.value = NULL; + gmsg.length = 0; + + do + { + gss_display_status(&lmin_s, stat, type, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(s + i, gmsg.value, len - i); + i += gmsg.length; + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < len); + + if (msg_ctx || i == len) + ereport(WARNING, + (errmsg_internal("incomplete GSS error report"))); +} + +void +pg_GSS_error(int severity, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat) +{ + char msg_major[128], msg_minor[128]; + + /* Fetch major status message */ + pg_GSS_error_int(msg_major, sizeof(msg_major), maj_stat, GSS_C_GSS_CODE); + + /* Fetch mechanism minor status message */ + pg_GSS_error_int(msg_minor, sizeof(msg_minor), min_stat, GSS_C_MECH_CODE); + + /* + * 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/backend/libpq/be-gssapi-common.h b/src/backend/libpq/be-gssapi-common.h new file mode 100644 index 0000000000..c020051d2e --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.h + * Definitions for GSSAPI authentication and encryption handling + * + * Portions Copyright (c) 1996-2018, 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, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat); + +#endif /* BE_GSSAPI_COMMON_H */ diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c new file mode 100644 index 0000000000..d860f71b71 --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,319 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gssapi.c + * GSSAPI encryption support + * + * Portions Copyright (c) 2018-2018, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +#include "libpq/auth.h" +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "pgstat.h" + +#include <unistd.h> + +static ssize_t +send_buffered_data(Port *port, size_t len) +{ + ssize_t ret = secure_raw_write( + port, + port->gss->writebuf.data + port->gss->writebuf.cursor, + port->gss->writebuf.len - port->gss->writebuf.cursor); + if (ret < 0) + return ret; + + /* update and possibly clear buffer state */ + port->gss->writebuf.cursor += ret; + + if (port->gss->writebuf.cursor == port->gss->writebuf.len) + { + /* entire request has now been written */ + resetStringInfo(&port->gss->writebuf); + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +be_gssapi_write(Port *port, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + pg_gssinfo *gss = port->gss; + + if (gss->writebuf.len != 0) + return send_buffered_data(port, len); + + /* encrypt the message */ + output.value = NULL; + output.length = 0; + input.value = ptr; + input.length = len; + + major = gss_wrap(&minor, 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); + goto cleanup; + } else if (conf == 0) + { + ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality"))); + goto cleanup; + } + + /* 4 network-order length bytes, then payload */ + netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, output.value, output.length); + + ret = send_buffered_data(port, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* load up any available data */ + if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len) + { + /* clamp length */ + if (len > gss->buf.len - gss->buf.cursor) + len = gss->buf.len - gss->buf.cursor; + + memcpy(ptr, gss->buf.data + gss->buf.cursor, len); + gss->buf.cursor += len; + ret = len; + } + + /* reset buffer if all data has been read */ + if (gss->buf.cursor == gss->buf.len) + resetStringInfo(&gss->buf); + + return ret; +} + +static ssize_t +load_packetlen(Port *port) +{ + pg_gssinfo *gss = port->gss; + ssize_t ret; + + if (gss->buf.len < 4) + { + enlargeStringInfo(&gss->buf, 4 - gss->buf.len); + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + 4 - gss->buf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(Port *port, size_t len) +{ + ssize_t ret; + pg_gssinfo *gss = port->gss; + + enlargeStringInfo(&gss->buf, len - gss->buf.len + 4); + + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + len - gss->buf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len - 4 < len) + { + errno = EWOULDBLOCK; + return -1; + } + return 0; +} + +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; + pg_gssinfo *gss = port->gss; + + if (gss->buf.cursor > 0) + return read_from_buffer(gss, ptr, len); + + /* load length if not present */ + ret = load_packetlen(port); + if (ret != 0) + return ret; + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + return ret; + + /* decrypt the packet */ + output.value = NULL; + output.length = 0; + input.value = gss->buf.data + 4; + + major = gss_unwrap(&minor, 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; + } + + /* put the decrypted packet in the buffer */ + resetStringInfo(&gss->buf); + enlargeStringInfo(&gss->buf, output.length); + + memcpy(gss->buf.data, output.value, output.length); + gss->buf.len = output.length; + gss->buf.data[gss->buf.len] = '\0'; + + ret = read_from_buffer(gss, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +ssize_t +secure_open_gssapi(Port *port) +{ + pg_gssinfo *gss = port->gss; + bool complete_next = false; + + /* + * Use the configured keytab, if there is one. Unfortunately, Heimdal + * doesn't support the cred store extensions, so use the env var. + */ + if (pg_krb_server_keyfile != NULL && strlen(pg_krb_server_keyfile) > 0) + setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1); + + while (true) + { + OM_uint32 major, minor; + size_t ret; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + + /* Handle any outgoing data */ + if (gss->writebuf.len != 0) + { + ret = send_buffered_data(port, 1); + if (ret != 1) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + } + + if (complete_next) + break; + + /* Load incoming data */ + ret = load_packetlen(port); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + input.value = gss->buf.data + 4; + + /* Process incoming data. (The client sends first.) */ + major = gss_accept_sec_context(&minor, &port->gss->ctx, + GSS_C_NO_CREDENTIAL, &input, + GSS_C_NO_CHANNEL_BINDINGS, + &port->gss->name, NULL, &output, NULL, + NULL, NULL); + resetStringInfo(&gss->buf); + if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, gettext_noop("GSSAPI context error"), + major, minor); + gss_release_buffer(&minor, &output); + return -1; + } + else if (!(major & GSS_S_CONTINUE_NEEDED)) + { + /* + * rfc2744 technically permits context negotiation to be complete + * both with and without a packet to be sent. + */ + complete_next = true; + } + + if (output.length != 0) + { + /* Queue packet for writing */ + uint32 netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, + output.value, output.length); + gss_release_buffer(&minor, &output); + continue; + } + + /* We're done - woohoo! */ + break; + } + port->gss->enc = true; + return 0; +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index edfe2c0751..7dd1cf7090 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -152,6 +152,14 @@ retry: n = be_tls_read(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_read(port, ptr, len); + waitfor = WL_SOCKET_READABLE; + } + else #endif { n = secure_raw_read(port, ptr, len); @@ -255,6 +263,14 @@ retry: n = be_tls_write(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_write(port, ptr, len); + waitfor = WL_SOCKET_WRITEABLE; + } + else #endif { n = secure_raw_write(port, ptr, len); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index acf625e4ec..9a1aa4e5cc 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -994,7 +994,9 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) } else if (strcmp(token->string, "host") == 0 || strcmp(token->string, "hostssl") == 0 || - strcmp(token->string, "hostnossl") == 0) + strcmp(token->string, "hostnossl") == 0 || + strcmp(token->string, "hostgss") == 0 || + strcmp(token->string, "hostnogss") == 0) { if (token->string[4] == 's') /* "hostssl" */ @@ -1022,10 +1024,23 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "hostssl record cannot match because SSL is not supported by this build"; #endif } - else if (token->string[4] == 'n') /* "hostnossl" */ + else if (token->string[4] == 'g') /* "hostgss" */ { - parsedline->conntype = ctHostNoSSL; + parsedline->conntype = ctHostGSS; +#ifndef ENABLE_GSS + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("hostgss record cannot match because GSSAPI is not supported by this build"), + errhint("Compile with --with-gssapi to use GSSAPI connections."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "hostgss record cannot match because GSSAPI is not supported by this build"; +#endif } + else if (token->string[4] == 'n' && token->string[6] == 's') + parsedline->conntype = ctHostNoSSL; + else if (token->string[4] == 'n' && token->string[6] == 'g') + parsedline->conntype = ctHostNoGSS; else { /* "host" */ @@ -1404,6 +1419,19 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "gssapi authentication is not supported on local sockets"; return NULL; } + if (parsedline->conntype == ctHostGSS && + parsedline->auth_method != uaGSS && + parsedline->auth_method != uaReject && + parsedline->auth_method != uaTrust) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("GSSAPI encryption only supports gss, trust, or reject authentication"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "GSSAPI encryption only supports gss, trust, or reject authenticaion"; + return NULL; + } if (parsedline->conntype != ctLocal && parsedline->auth_method == uaPeer) @@ -2060,6 +2088,17 @@ check_hba(hbaPort *port) continue; } + /* Check GSSAPI state */ +#ifdef ENABLE_GSS + if (port->gss->enc && hba->conntype == ctHostNoGSS) + continue; + else if (!port->gss->enc && hba->conntype == ctHostGSS) + continue; +#else + if (hba->conntype == ctHostGSS) + continue; +#endif + /* Check IP address */ switch (hba->ip_cmp_method) { @@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, case ctHostNoSSL: typestr = "hostnossl"; break; + case ctHostGSS: + typestr = "hostgss"; + break; + case ctHostNoGSS: + typestr = "hostnogss"; + break; } if (typestr) values[index++] = CStringGetTextDatum(typestr); diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 084573e77c..72ce147308 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -3562,6 +3562,9 @@ pgstat_get_wait_client(WaitEventClient w) case WAIT_EVENT_WAL_SENDER_WRITE_DATA: event_name = "WalSenderWriteData"; break; + case WAIT_EVENT_GSS_OPEN_SERVER: + event_name = "GSSOpenServer"; + break; /* no default case, so that compiler will warn */ } diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a4b53b33cd..87dbb33a88 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -1894,7 +1894,7 @@ initMasks(fd_set *rmask) * if we detect a communications failure.) */ static int -ProcessStartupPacket(Port *port, bool SSLdone) +ProcessStartupPacket(Port *port, bool secure_done) { int32 len; void *buf; @@ -1905,11 +1905,11 @@ ProcessStartupPacket(Port *port, bool SSLdone) if (pq_getbytes((char *) &len, 4) == EOF) { /* - * EOF after SSLdone probably means the client didn't like our + * EOF after secure_done probably means the client didn't like our * response to NEGOTIATE_SSL_CODE. That's not an error condition, so * don't clutter the log with a complaint. */ - if (!SSLdone) + if (!secure_done) ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("incomplete startup packet"))); @@ -1961,7 +1961,7 @@ ProcessStartupPacket(Port *port, bool SSLdone) return STATUS_ERROR; } - if (proto == NEGOTIATE_SSL_CODE && !SSLdone) + if (proto == NEGOTIATE_SSL_CODE && !secure_done) { char SSLok; @@ -1994,6 +1994,32 @@ retry1: /* but not another SSL negotiation request */ return ProcessStartupPacket(port, true); } + else if (proto == NEGOTIATE_GSS_CODE && !secure_done) + { + char GSSok = 'N'; +#ifdef ENABLE_GSS + /* No GSSAPI encryption when on Unix socket */ + if (!IS_AF_UNIX(port->laddr.addr.ss_family)) + GSSok = 'G'; +#endif + + while (send(port->sock, &GSSok, 1, 0) != 1) + { + if (errno == EINTR) + continue; + ereport(COMMERROR, + (errcode_for_socket_access(), + errmsg("failed to send GSSAPI negotiation response: %m)"))); + return STATUS_ERROR; /* close the connection */ + } + +#ifdef ENABLE_GSS + if (GSSok == 'G' && secure_open_gssapi(port) == -1) + return STATUS_ERROR; +#endif + /* Won't ever see more than one negotiation request */ + return ProcessStartupPacket(port, true); + } /* Could add additional special packet types here */ @@ -2413,6 +2439,17 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif #endif return port; @@ -2429,7 +2466,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); } @@ -4761,6 +4806,17 @@ SubPostmasterMain(int argc, char *argv[]) (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif /* * If appropriate, physically re-attach to shared memory segment. We want diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 5f68f4c666..830ddaa25a 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -55,7 +55,9 @@ typedef enum ConnType ctLocal, ctHost, ctHostSSL, - ctHostNoSSL + ctHostNoSSL, + ctHostGSS, + ctHostNoGSS, } ConnType; typedef struct HbaLine diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 7698cd1f88..3e46ac437a 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -66,7 +66,7 @@ typedef struct #include "datatype/timestamp.h" #include "libpq/hba.h" #include "libpq/pqcomm.h" - +#include "lib/stringinfo.h" typedef enum CAC_state { @@ -86,6 +86,9 @@ 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 */ + bool enc; /* GSSAPI encryption in use */ + StringInfoData buf; /* GSSAPI encryption data buffering */ + StringInfoData writebuf; /* GSSAPI nonblocking write buffering */ #endif } pg_gssinfo; #endif @@ -278,6 +281,12 @@ extern char *be_tls_get_peer_finished(Port *port, size_t *len); extern char *be_tls_get_certificate_hash(Port *port, size_t *len); #endif +#ifdef ENABLE_GSS +/* Read and write to a GSSAPI-encrypted connection. */ +extern ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); +extern 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 7bf06c65e9..e34f552ef7 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -92,6 +92,9 @@ extern ssize_t secure_read(Port *port, void *ptr, size_t len); extern ssize_t secure_write(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_read(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_write(Port *port, const void *ptr, size_t len); +#ifdef ENABLE_GSS +extern ssize_t secure_open_gssapi(Port *port); +#endif extern bool ssl_loaded_verify_locations; diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index cc0e0b32c7..ade1190096 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -199,9 +199,10 @@ typedef struct CancelRequestPacket /* - * A client can also start by sending a SSL negotiation request, to get a - * secure channel. + * A client can also start by sending a SSL or GSSAPI negotiation request to + * get a secure channel. */ #define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679) +#define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680) #endif /* PQCOMM_H */ diff --git a/src/include/pgstat.h b/src/include/pgstat.h index be2f59239b..4f06f7a2bc 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -787,7 +787,8 @@ typedef enum WAIT_EVENT_SSL_OPEN_SERVER, WAIT_EVENT_WAL_RECEIVER_WAIT_START, WAIT_EVENT_WAL_SENDER_WAIT_WAL, - WAIT_EVENT_WAL_SENDER_WRITE_DATA + WAIT_EVENT_WAL_SENDER_WRITE_DATA, + WAIT_EVENT_GSS_OPEN_SERVER, } WaitEventClient; /* ---------- diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index abe0a50e98..c814e5e35a 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -57,6 +57,10 @@ else OBJS += sha2.o endif +ifeq ($(with_gssapi),yes) +OBJS += fe-gssapi-common.o fe-secure-gssapi.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 3b2073a47f..af2ef78d76 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -49,52 +49,7 @@ * GSSAPI authentication system. */ -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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. @@ -195,18 +150,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen) static int pg_GSS_startup(PGconn *conn, int payloadlen) { - OM_uint32 maj_stat, - min_stat; - int maxlen; - gss_buffer_desc temp_gbuf; - char *host = PQhost(conn); - - if (!(host && host[0] != '\0')) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("host name must be specified\n")); - return STATUS_ERROR; - } + int ret; if (conn->gctx) { @@ -215,33 +159,9 @@ pg_GSS_startup(PGconn *conn, int payloadlen) return STATUS_ERROR; } - /* - * Import service principal name so the proper ticket can be acquired by - * the GSSAPI system. - */ - maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; - temp_gbuf.value = (char *) malloc(maxlen); - if (!temp_gbuf.value) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory\n")); - return STATUS_ERROR; - } - snprintf(temp_gbuf.value, maxlen, "%s@%s", - conn->krbsrvname, host); - temp_gbuf.length = strlen(temp_gbuf.value); - - maj_stat = gss_import_name(&min_stat, &temp_gbuf, - GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); - free(temp_gbuf.value); - - if (maj_stat != GSS_S_COMPLETE) - { - pg_GSS_error(libpq_gettext("GSSAPI name import error"), - conn, - maj_stat, min_stat); - return STATUS_ERROR; - } + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return ret; /* * Initial packet is the same as a continuation packet with no initial diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index a7e969d7c1..23270f7f6b 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, #else #define DefaultSSLMode "disable" #endif +#ifdef ENABLE_GSS +#include "fe-gssapi-common.h" +#define DefaultGSSMode "prefer" +#else +#define DefaultGSSMode "disable" +#endif /* ---------- * Definition of the conninfo parameters and their fallback resources. @@ -303,6 +309,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Require-Peer", "", 10, offsetof(struct pg_conn, requirepeer)}, + /* + * Expose gssmode similarly to sslmode - we can stil handle "disable" and + * "prefer". + */ + {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL, + "GSS-Mode", "", 7, /* sizeof("disable") == 7 */ + offsetof(struct pg_conn, gssmode)}, + #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* Kerberos and GSSAPI authentication support specifying the service name */ {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL, @@ -1166,6 +1180,39 @@ connectOptions2(PGconn *conn) goto oom_error; } + /* + * validate gssmode option + */ + if (conn->gssmode) + { + if (strcmp(conn->gssmode, "disable") != 0 && + strcmp(conn->gssmode, "prefer") != 0 && + strcmp(conn->gssmode, "require") != 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid gssmode value: \"%s\"\n"), + conn->gssmode); + return false; + } +#ifndef ENABLE_GSS + if (strcmp(conn->gssmode, "require") != 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer( + &conn->errorMessage, + libpq_gettext("no GSSAPI support; cannot require GSSAPI\n")); + return false; + } +#endif + } + else + { + conn->gssmode = strdup(DefaultGSSMode); + if (!conn->gssmode) + goto oom_error; + } + /* * Resolve special "auto" client_encoding from the locale */ @@ -1804,6 +1851,11 @@ connectDBStart(PGconn *conn) conn->wait_ssl_try = true; #endif +#ifdef ENABLE_GSS + if (conn->gssmode[0] == 'd') /* "disable" */ + conn->try_gss = false; +#endif + /* * Set up to try to connect, with protocol 3.0 as the first attempt. */ @@ -2051,6 +2103,7 @@ PQconnectPoll(PGconn *conn) case CONNECTION_NEEDED: case CONNECTION_CHECK_WRITABLE: case CONNECTION_CONSUME: + case CONNECTION_GSS_STARTUP: break; default: @@ -2426,17 +2479,54 @@ keep_going: /* We will come back to here until there is } #endif /* HAVE_UNIX_SOCKETS */ + if (IS_AF_UNIX(conn->raddr.addr.ss_family)) + { + /* Don't request SSL or GSSAPI over Unix sockets */ #ifdef USE_SSL + conn->allow_ssl_try = false; +#endif +#ifdef ENABLE_GSS + conn->try_gss = false; +#endif + } +#ifdef ENABLE_GSS + /* + * If GSSAPI is enabled and we have a ccache, try to set it up + * before sending startup messages. If it's already + * operating, don't try SSL and instead just build the startup + * packet. + */ + if (conn->try_gss && !conn->gctx) + conn->try_gss = pg_GSS_have_ccache(&conn->gcred); + if (conn->try_gss && !conn->gctx) + { + ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE); + if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not send GSSAPI negotiation packet: %s\n"), + SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + goto error_return; + } + + /* Ok, wait for response */ + conn->status = CONNECTION_GSS_STARTUP; + return PGRES_POLLING_READING; + } + else if (!conn->gctx && conn->gssmode[0] == 'r') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache,no server support, or using a local socket)\n")); + goto error_return; + } +#endif + +#ifdef USE_SSL /* * If SSL is enabled and we haven't already got it running, * request it instead of sending the startup message. */ - if (IS_AF_UNIX(conn->raddr.addr.ss_family)) - { - /* Don't bother requesting SSL over a Unix socket */ - conn->allow_ssl_try = false; - } if (conn->allow_ssl_try && !conn->wait_ssl_try && !conn->ssl_in_use) { @@ -2629,6 +2719,97 @@ keep_going: /* We will come back to here until there is #endif /* USE_SSL */ } + case CONNECTION_GSS_STARTUP: + { +#ifdef ENABLE_GSS + PostgresPollingStatusType pollres; + + /* + * If we haven't yet, get the postmaster's response to our + * negotiation packet + */ + if (conn->try_gss && !conn->gctx) + { + char gss_ok; + int rdresult = pqReadData(conn); + if (rdresult < 0) + /* pqReadData fills in error message */ + goto error_return; + else if (rdresult == 0) + /* caller failed to wait for data */ + return PGRES_POLLING_READING; + if (pqGetc(&gss_ok, conn) < 0) + /* shouldn't happen... */ + return PGRES_POLLING_READING; + + if (gss_ok == 'E') + { + /* + * Server failure of some sort. Assume it's a + * protocol version support failure, and let's see if + * we can't recover (if it's not, we'll get a better + * error message on retry). Server gets fussy if we + * don't hang up the socket, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + + /* mark byte consumed */ + conn->inStart = conn->inCursor; + + if (gss_ok == 'N') + { + /* Server doesn't want GSSAPI; fall back if we can */ + if (conn->gssmode[0] == 'r') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n")); + goto error_return; + } + + conn->try_gss = false; + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (gss_ok != 'G') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"), + gss_ok); + goto error_return; + } + } + + /* Begin or continue GSSAPI negotiation */ + pollres = pqsecure_open_gss(conn); + if (pollres == PGRES_POLLING_OK) + { + /* All set for startup packet */ + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (pollres == PGRES_POLLING_FAILED && + conn->gssmode[0] == 'p') + { + /* + * We failed, but we can retry on "prefer". Have to drop + * the current connection to do so, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + return pollres; +#else /* !ENABLE_GSS */ + /* unreachable */ + goto error_return; +#endif /* ENABLE_GSS */ + } + /* * Handle authentication exchange: wait for postmaster messages * and respond as necessary. @@ -2781,6 +2962,24 @@ keep_going: /* We will come back to here until there is /* OK, we read the message; mark data consumed */ conn->inStart = conn->inCursor; +#ifdef ENABLE_GSS + /* + * If gssmode is "prefer" and we're using GSSAPI, retry + * without it. + */ + if (conn->gssenc && conn->gssmode[0] == 'p') + { + OM_uint32 minor; + /* postmaster expects us to drop the connection */ + conn->try_gss = false; + conn->gssenc = false; + gss_delete_sec_context(&minor, &conn->gctx, NULL); + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } +#endif + #ifdef USE_SSL /* @@ -3361,6 +3560,11 @@ makeEmptyPGconn(void) conn->wait_ssl_try = false; conn->ssl_in_use = false; #endif +#ifdef ENABLE_GSS + conn->try_gss = true; + initPQExpBuffer(&conn->gbuf); + initPQExpBuffer(&conn->gwritebuf); +#endif /* * We try to send at least 8K at a time, which is the usual size of pipe @@ -3492,10 +3696,28 @@ freePGconn(PGconn *conn) free(conn->sslcompression); if (conn->requirepeer) free(conn->requirepeer); + if (conn->gssmode) + free(conn->gssmode); #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) if (conn->krbsrvname) free(conn->krbsrvname); #endif +#ifdef ENABLE_GSS + if (conn->gcred != GSS_C_NO_CREDENTIAL) + { + OM_uint32 minor; + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + } + if (conn->gctx) + { + OM_uint32 minor; + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = NULL; + } + termPQExpBuffer(&conn->gbuf); + termPQExpBuffer(&conn->gwritebuf); +#endif #if defined(ENABLE_GSS) && defined(ENABLE_SSPI) if (conn->gsslib) free(conn->gsslib); diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c new file mode 100644 index 0000000000..35e195b7a6 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.c @@ -0,0 +1,128 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.c + * The front-end (client) GSSAPI common code + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-gssapi-common.c + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "fe-gssapi-common.h" + +#include "libpq-int.h" +#include "pqexpbuffer.h" + +/* + * 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); +} + +/* + * Check if we can acquire credentials at all. + */ +bool +pg_GSS_have_ccache(gss_cred_id_t *cred_out) +{ + OM_uint32 major, minor; + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + + major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &cred, NULL, NULL); + if (GSS_ERROR(major)) + { + *cred_out = NULL; + return false; + } + *cred_out = cred; + return true; +} + +/* + * Try to load service name for a connection + */ +int +pg_GSS_load_servicename(PGconn *conn) +{ + OM_uint32 maj_stat, min_stat; + int maxlen; + gss_buffer_desc temp_gbuf; + char *host; + + if (conn->gtarg_nam != NULL) + /* Already taken care of - move along */ + return STATUS_OK; + + host = PQhost(conn); + if (!(host && host[0] != '\0')) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("host name must be specified\n")); + return STATUS_ERROR; + } + + /* + * Import service principal name so the proper ticket can be acquired by + * the GSSAPI system. + */ + maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; + temp_gbuf.value = (char *) malloc(maxlen); + if (!temp_gbuf.value) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return STATUS_ERROR; + } + snprintf(temp_gbuf.value, maxlen, "%s@%s", + conn->krbsrvname, host); + temp_gbuf.length = strlen(temp_gbuf.value); + + maj_stat = gss_import_name(&min_stat, &temp_gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); + free(temp_gbuf.value); + + if (maj_stat != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI name import error"), + conn, + maj_stat, min_stat); + return STATUS_ERROR; + } + return STATUS_OK; +} diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h new file mode 100644 index 0000000000..58811df9f1 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.h + * + * Definitions for GSSAPI common routines + * + * Portions Copyright (c) 1996-2018, 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 + +#include "libpq-fe.h" +#include "libpq-int.h" + +void pg_GSS_error(const char *mprefix, PGconn *conn, + OM_uint32 maj_stat, OM_uint32 min_stat); +bool pg_GSS_have_ccache(gss_cred_id_t *cred_out); +int pg_GSS_load_servicename(PGconn *conn); +#endif /* FE_GSSAPI_COMMON_H */ diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c new file mode 100644 index 0000000000..a71df69ff2 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,343 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-gssapi.c + * The front-end (client) encryption support for GSSAPI + * + * Portions Copyright (c) 2016-2018, 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" + +/* + * Require encryption support, as well as mutual authentication and + * tamperproofing measures. + */ +#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \ + GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG + +static ssize_t +send_buffered_data(PGconn *conn, size_t len) +{ + ssize_t ret = pqsecure_raw_write(conn, + conn->gwritebuf.data + conn->gwritecurs, + conn->gwritebuf.len - conn->gwritecurs); + if (ret < 0) + return ret; + + conn->gwritecurs += ret; + + if (conn->gwritecurs == conn->gwritebuf.len) + { + /* entire request has now been written */ + resetPQExpBuffer(&conn->gwritebuf); + conn->gwritecurs = 0; + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +pg_GSS_write(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + + if (conn->gwritebuf.len != 0) + return send_buffered_data(conn, len); + + /* encrypt the message */ + input.value = ptr; + input.length = len; + + 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); + goto cleanup; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI did not provide confidentiality\n")); + goto cleanup; + } + + /* 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + + ret = send_buffered_data(conn, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* check for available data */ + if (conn->gcursor < conn->gbuf.len) + { + /* clamp length */ + 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; + } + + /* reset buffer if all data has been read */ + if (conn->gcursor == conn->gbuf.len) + { + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + } + + return ret; +} + +static ssize_t +load_packet_length(PGconn *conn) +{ + ssize_t ret; + if (conn->gbuf.len < 4) + { + ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len); + 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 + conn->gbuf.len, + 4 - conn->gbuf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(PGconn *conn, size_t len) +{ + ssize_t ret; + + ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI encrypted packet length %ld too big\n"), + len); + return -1; + } + + /* load any missing parts of the packet */ + if (conn->gbuf.len - 4 < len) + { + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + len - conn->gbuf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < len) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +ssize_t +pg_GSS_read(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = 0; + int conf = 0; + + /* handle any buffered data */ + if (conn->gcursor != 0) + return read_from_buffer(conn, ptr, len); + + /* load in the packet length, if not yet loaded */ + ret = load_packet_length(conn); + if (ret < 0) + return ret; + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0) + return ret; + + /* decrypt the packet */ + 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; + } + + /* load decrypted packet into our buffer */ + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + ret = enlargePQExpBuffer(&conn->gbuf, output.length); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI decrypted packet length %ld too big\n"), + output.length); + ret = -1; + goto cleanup; + } + + memcpy(conn->gbuf.data, output.value, output.length); + conn->gbuf.len = output.length; + conn->gbuf.data[conn->gbuf.len] = '\0'; + + ret = read_from_buffer(conn, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +PostgresPollingStatusType +pqsecure_open_gss(PGconn *conn) +{ + ssize_t ret; + OM_uint32 major, minor; + uint32 netlen; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + + /* Send out any data we might have */ + if (conn->gwritebuf.len != 0) + { + ret = send_buffered_data(conn, 1); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_WRITING; + else if (ret == 1) + /* sent all data */ + return PGRES_POLLING_READING; + return PGRES_POLLING_FAILED; + } + + /* Client sends first, and sending creates a context */ + if (conn->gctx) + { + /* Process any incoming data we might have */ + ret = load_packet_length(conn); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + if (conn->gbuf.data[0] == 'E') + { + /* + * We can taken an error here, and it's my least favorite thing. + * How long can error messages be? That's a good question, but + * backend's pg_gss_error() caps them at 256. Do a single read + * for that and call it a day. Cope with this here rather than in + * load_packet since expandPQExpBuffer will destroy any data in + * the buffer on failure. + */ + load_packet(conn, 256); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Server error: %s"), + conn->gbuf.data + 1); + return PGRES_POLLING_FAILED; + } + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + input.value = conn->gbuf.data + 4; + } + + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return PGRES_POLLING_FAILED; + + major = gss_init_sec_context(&minor, conn->gcred, &conn->gctx, + conn->gtarg_nam, GSS_C_NO_OID, + GSS_REQUIRED_FLAGS, 0, 0, &input, NULL, + &output, NULL, NULL); + resetPQExpBuffer(&conn->gbuf); + if (GSS_ERROR(major)) + { + pg_GSS_error(libpq_gettext("GSSAPI context establishment error"), + conn, major, minor); + return PGRES_POLLING_FAILED; + } + else if (output.length == 0) + { + /* + * We're done - hooray! Kind of gross, but we need to disable SSL + * here so that we don't accidentally tunnel one over the other. + */ +#ifdef USE_SSL + conn->allow_ssl_try = false; +#endif + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + conn->gssenc = true; + return PGRES_POLLING_OK; + } + + /* Queue the token for writing */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + gss_release_buffer(&minor, &output); + return PGRES_POLLING_WRITING; +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index f7dc249bf0..18155e4b5e 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) n = pgtls_read(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_read(conn, ptr, len); + } + else #endif { n = pqsecure_raw_read(conn, ptr, len); @@ -287,7 +294,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; @@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) n = pgtls_write(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_write(conn, ptr, len); + } + else #endif { n = pqsecure_raw_write(conn, ptr, len); diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index ed9c806861..bdd5a10cd8 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -65,8 +65,9 @@ typedef enum CONNECTION_NEEDED, /* Internal state: connect() needed */ CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable * connection. */ - CONNECTION_CONSUME /* Wait for any pending message and consume + CONNECTION_CONSUME, /* Wait for any pending message and consume * them. */ + CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */ } ConnStatusType; typedef enum diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 9a586ff25a..6cf3459a8c 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -22,6 +22,7 @@ /* We assume libpq-fe.h has already been included. */ #include "libpq-events.h" +#include "lib/stringinfo.h" #include <time.h> #ifndef WIN32 @@ -473,9 +474,19 @@ struct pg_conn #endif /* USE_OPENSSL */ #endif /* USE_SSL */ + char *gssmode; /* GSS mode (require,prefer,disable) */ #ifdef ENABLE_GSS gss_ctx_id_t gctx; /* GSS context */ gss_name_t gtarg_nam; /* GSS target name */ + + /* The following are encryption-only */ + 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 try_gss; /* GSS attempting permitted */ + bool gssenc; /* GSS encryption is usable */ + gss_cred_id_t gcred; /* GSS credential temp storage. */ #endif #ifdef ENABLE_SSPI @@ -651,7 +662,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); @@ -745,6 +756,23 @@ extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, int *names_examined, char **first_name); +/* === GSSAPI === */ + +#ifdef ENABLE_GSS + +/* + * Establish a GSSAPI-encrypted connection. + */ +extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn); + +/* + * Read and write functions for GSSAPI-encrypted connections, with internal + * buffering to handle nonblocking sockets. + */ +extern ssize_t pg_GSS_write(PGconn *conn, void *ptr, size_t len); +extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len); +#endif + /* === miscellaneous macros === */ /* diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 593732fd95..ca5c7cea40 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -183,13 +183,18 @@ sub mkvcbuild $postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); $postgres->FullExportDLL('postgres.lib'); - # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c - # if building without OpenSSL + # The OBJS scraper doesn't know about ifdefs, so remove + # be-secure-openssl.c if building without OpenSSL, and + # be-gssapi-common.c when building with GSSAPI. if (!$solution->{options}->{openssl}) { $postgres->RemoveFile('src/backend/libpq/be-secure-common.c'); $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', 'src/backend/snowball'); @@ -243,9 +248,10 @@ sub mkvcbuild 'src/interfaces/libpq/libpq.rc'); $libpq->AddReference($libpgport); - # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c - # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if - # building with OpenSSL. + # The OBJS scraper doesn't know about ifdefs, so remove + # fe-secure-openssl.c and sha2_openssl.c if building without + # OpenSSL, and remove sha2.c if building with OpenSSL. Also + # remove fe-gssapi-common.c when building with GSSAPI. if (!$solution->{options}->{openssl}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c'); @@ -256,6 +262,10 @@ sub mkvcbuild { $libpq->RemoveFile('src/common/sha2.c'); } + if (!$solution->{options}->{gss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + } my $libpqwalreceiver = $solution->AddProject('libpqwalreceiver', 'dll', '', -- 2.17.0
Attachment
Robbie Harwood <rharwood@redhat.com> writes: > Thomas Munro <thomas.munro@enterprisedb.com> writes: > >> On Thu, May 24, 2018 at 8:00 AM, Robbie Harwood <rharwood@redhat.com> wrote: >> >>> Zombie patch is back from the dead. >> >> Hi Robbie, >> >> Robots[1] vs zombies: >> >> + $postgres->RemoveFile('src/backennd/libpq/be-gssapi-common.c'); >> >> Typo, breaks on Windows. >> >> runtime.sgml:2032: parser error : Opening and ending tag mismatch: >> para line 2026 and sect1 >> </sect1> >> ^ >> >> Docs malformed. >> >> [1] http://cfbot.cputube.org/robbie-harwood.html > > Hah, this is great! Looks like I failed to build the docs. > > Here's an updated version that should fix those. Me and the bot are having an argument. This should green Linux but I dunno about Windows. Thanks, --Robbie From 1bf21be9d9cd05bf2bcb37c474888b4d8ff6fb63 Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Thu, 10 May 2018 16:12:03 -0400 Subject: [PATCH] libpq GSSAPI encryption support On both the frontend and backend, prepare for GSSAPI encryption support by moving common code for error handling into a separate file. Fix a TODO for handling multiple status messages in the process. Eliminate the OIDs, which have not been needed for some time. Add frontend and backend encryption support functions. Keep the context initiation for authentication-only separate on both the frontend and backend in order to avoid concerns about changing the requested flags to include encryption support. In postmaster, pull GSSAPI authorization checking into a shared function. Also share the initiator name between the encryption and non-encryption codepaths. Modify pqsecure_write() to take a non-const pointer. The pointer will not be modified, but this change (or a const-discarding cast, or a malloc()+memcpy()) is necessary for GSSAPI due to const/struct interactions in C. For HBA, add "hostgss" and "hostnogss" entries that behave similarly to their SSL counterparts. "hostgss" requires either "gss", "trust", or "reject" for its authentication. Simiarly, add a "gssmode" parameter to libpq. Supported values are "disable", "require", and "prefer". Notably, negotiation will only be attempted if credentials can be acquired. Move credential acquisition into its own function to support this behavior. Finally, add documentation for everything new, and update existing documentation on connection security. Thanks to Michael Paquier for the Windows fixes. --- doc/src/sgml/client-auth.sgml | 75 ++++-- doc/src/sgml/libpq.sgml | 57 +++- doc/src/sgml/runtime.sgml | 77 +++++- src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 103 +++---- src/backend/libpq/be-gssapi-common.c | 64 +++++ src/backend/libpq/be-gssapi-common.h | 26 ++ src/backend/libpq/be-secure-gssapi.c | 319 ++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 ++ src/backend/libpq/hba.c | 51 +++- src/backend/postmaster/pgstat.c | 3 + src/backend/postmaster/postmaster.c | 64 ++++- src/include/libpq/hba.h | 4 +- src/include/libpq/libpq-be.h | 11 +- src/include/libpq/libpq.h | 3 + src/include/libpq/pqcomm.h | 5 +- src/include/pgstat.h | 3 +- src/interfaces/libpq/Makefile | 4 + src/interfaces/libpq/fe-auth.c | 90 +------ src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++- src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++ src/interfaces/libpq/fe-gssapi-common.h | 23 ++ src/interfaces/libpq/fe-secure-gssapi.c | 343 ++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-fe.h | 3 +- src/interfaces/libpq/libpq-int.h | 30 ++- src/tools/msvc/Mkvcbuild.pm | 24 +- 27 files changed, 1576 insertions(+), 202 deletions(-) create mode 100644 src/backend/libpq/be-gssapi-common.c create mode 100644 src/backend/libpq/be-gssapi-common.h create mode 100644 src/backend/libpq/be-secure-gssapi.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.h 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 656d5f9417..38cf32e3be 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> </synopsis> The meaning of the fields is as follows: @@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> This record matches connection attempts made using TCP/IP. - <literal>host</literal> records match either + <literal>host</literal> records match <acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection - attempts. + attempts as well as <acronym>GSSAPI</acronym> or + non-<acronym>GSSAPI</acronym> connection attempts. </para> <note> <para> @@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><literal>hostgss</literal></term> + <listitem> + <para> + This record matches connection attempts made using TCP/IP, + but only when the connection is made with <acronym>GSSAPI</acronym> + encryption. + </para> + + <para> + To make use of this option the server must be built with + <acronym>GSSAPI</acronym> support. Otherwise, + the <literal>hostgss</literal> record is ignored except for logging a + warning that it cannot match any connections. + </para> + + <para> + Note that the only supported <xref linkend="auth-methods"/> for use + with <acronym>GSSAPI</acronym> encryption + are <literal>gss</literal>, <literal>reject</literal>, + and <literal>trust</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>hostnogss</literal></term> + <listitem> + <para> + This record type has the opposite behavior of <literal>hostgss</literal>; + it only matches connection attempts made over + TCP/IP that do not use <acronym>GSSAPI</acronym>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable>database</replaceable></term> <listitem> @@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> Use GSSAPI to authenticate the user. This is only - available for TCP/IP connections. See <xref - linkend="gssapi-auth"/> for details. + available for TCP/IP connections . See <xref + linkend="gssapi-auth"/> for details. It can be used in conjunction + with GSSAPI encryption. </para> </listitem> </varlistentry> @@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256 host all mike .example.com md5 host all all .example.com scram-sha-256 -# In the absence of preceding "host" lines, these two lines will +# In the absence of preceding "host" lines, these three lines will # reject all connections from 192.168.54.1 (since that entry will be -# matched first), but allow GSSAPI connections from anywhere else -# on the Internet. The zero mask causes no bits of the host IP +# matched first), but allow GSSAPI-encrypted connections from anywhere else +# on the Internet. Unencrypted GSSAPI connections are allowed from +# 192.168.12.10 only. The zero mask causes no bits of the host IP # address to be considered, so it matches any host. # # TYPE DATABASE USER ADDRESS METHOD host all all 192.168.54.1/32 reject -host all all 0.0.0.0/0 gss +hostgss all all 0.0.0.0/0 gss +host all all 192.168.12.10/32 gss # Allow users from 192.168.x.x hosts to connect to any database, if # they pass the ident check. If, for example, ident says the user is @@ -1051,13 +1093,16 @@ omicron bryanh guest1 <para> <productname>GSSAPI</productname> is an industry-standard protocol for secure authentication defined in RFC 2743. - <productname>PostgreSQL</productname> supports - <productname>GSSAPI</productname> with <productname>Kerberos</productname> - authentication according to RFC 1964. <productname>GSSAPI</productname> - 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. + + <productname>PostgreSQL</productname> + supports <productname>GSSAPI</productname> for use as either an encrypted, + authenticated layer, or as authentication + only. <productname>GSSAPI</productname> provides automatic authentication + (single sign-on) for systems that support it. The authentication itself is + secure. If <productname>GSSAPI</productname> encryption + (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are + used, the data sent along the database connection will be encrypted; + otherwise, it will not. </para> <para> diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 800e68a19e..73d233dc16 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1320,6 +1320,61 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode"> + <term><literal>gssmode</literal></term> + <listitem> + <para> + This option determines whether or with what priority a secure + <acronym>GSS</acronym> TCP/IP connection will be negotiated with the + server. There are three modes: + + <variablelist> + <varlistentry> + <term><literal>disable</literal></term> + <listitem> + <para> + only try a non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>prefer</literal> (default)</term> + <listitem> + <para> + if there are <acronym>GSSAPI</acronym> credentials present (i.e., + in a credentials cache), first try + a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or + there are no credentials, try a + non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>require</literal></term> + <listitem> + <para> + only try a <acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + <literal>gssmode</literal> is ignored for Unix domain socket + communication. If <productname>PostgreSQL</productname> is compiled + without GSSAPI support, using the <literal>require</literal> option + will cause an error, while <literal>prefer</literal> will be accepted + but <application>libpq</application> will not actually attempt + a <acronym>GSSAPI</acronym>-encrypted + connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with + libpq</secondary></indexterm> + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-sslmode" xreflabel="sslmode"> <term><literal>sslmode</literal></term> <listitem> @@ -7872,7 +7927,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*) </para> <para> - For a connection to be known secure, SSL usage must be configured + For a connection to be known SSL-secured, SSL usage must be configured on <emphasis>both the client and the server</emphasis> before the connection is made. If it is only configured on the server, the client may end up sending sensitive information (e.g. passwords) before diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 330e38a29e..06bf56153c 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 </para> <para> - To prevent spoofing on TCP connections, the best solution is to use - SSL certificates and make sure that clients check the server's certificate. - To do that, the server + To prevent spoofing on TCP connections, either use + SSL certificates and make sure that clients check the server's certificate, + or use GSSAPI encryption (or both, if they're on separate connections). + </para> + + <para> + To prevent spoofing with SSL, the server must be configured to accept only <literal>hostssl</literal> connections (<xref linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files (<xref linkend="ssl-tcp"/>). The TCP client must connect using @@ -2018,6 +2022,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 <literal>verify-full</literal> and have the appropriate root certificate file installed (<xref linkend="libq-ssl-certificates"/>). </para> + + <para> + To prevent spoofing with GSSAPI, the server must be configured to accept + only <literal>hostgss</literal> connections + (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal> + authentication with them. The TCP client must connect + using <literal>gssmode=require</literal>. + </para> </sect1> <sect1 id="encryption-options"> @@ -2114,8 +2126,24 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 which hosts can use non-encrypted connections (<literal>host</literal>) and which require SSL-encrypted connections (<literal>hostssl</literal>). Also, clients can specify that they - connect to servers only via SSL. <application>Stunnel</application> or - <application>SSH</application> can also be used to encrypt transmissions. + connect to servers only via SSL. + </para> + + <para> + GSSAPI-encrypted connections encrypt all data sent across the network, + including queries and data returned. (No password is sent across the + network.) The <filename>pg_hba.conf</filename> file allows + administrators to specify which hosts can use non-encrypted connections + (<literal>host</literal>) and which require GSSAPI-encrypted connections + (<literal>hostgss</literal>). Also, clients can specify that they + connect to servers only on GSSAPI-encrypted connections + (<literal>gssmode=require</literal>). + </para> + + <para> + <application>Stunnel</application> or + <application>SSH</application> can also be used to encrypt + transmissions. </para> </listitem> </varlistentry> @@ -2505,6 +2533,45 @@ openssl x509 -req -in server.csr -text -days 365 \ </sect1> + <sect1 id="gssapi-enc"> + <title>Secure TCP/IP Connections with GSSAPI encryption</title> + + <indexterm zone="gssapi-enc"> + <primary>gssapi</primary> + </indexterm> + + <para> + <productname>PostgreSQL</productname> also has native support for + using <acronym>GSSAPI</acronym> to encrypt client/server communications for + increased security. Support requires that a <acronym>GSSAPI</acronym> + implementation (such as MIT krb5) is installed on both client and server + systems, and that support in <productname>PostgreSQL</productname> is + enabled at build time (see <xref linkend="installation"/>). + </para> + + <sect2 id="gssapi-setup"> + <title>Basic Setup</title> + + <para> + The <productname>PostgreSQL</productname> server will listen for both + normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP + port, and will negotiate with any connecting client on whether to + use <acronym>GSSAPI</acronym> for encryption (and for authentication). By + default, this decision is up to the client (which means it can be + downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about + setting up the server to require the use of <acronym>GSSAPI</acronym> for + some or all conections. + </para> + + <para> + Other than configuration of the negotiation + behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond + that which necessary for GSSAPI authentication. (For more information on + configuring that, see <xref linkend="gssapi-auth"/>.) + </para> + </sect2> + </sect1> + <sect1 id="ssh-tunnels"> <title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title> diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 3dbec23e30..47efef0682 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 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 3014b17a7c..6e4cd66a90 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -173,12 +173,9 @@ bool pg_krb_caseins_users; *---------------------------------------------------------------- */ #ifdef ENABLE_GSS -#if defined(HAVE_GSSAPI_H) -#include <gssapi.h> -#else -#include <gssapi/gssapi.h> -#endif +#include "be-gssapi-common.h" +static int pg_GSS_checkauth(Port *port); static int pg_GSS_recvauth(Port *port); #endif /* ENABLE_GSS */ @@ -384,6 +381,17 @@ ClientAuthentication(Port *port) errmsg("connection requires a valid client certificate"))); } +#ifdef ENABLE_GSS + if (port->gss->enc && port->hba->auth_method != uaReject && + port->hba->auth_method != uaImplicitReject && + port->hba->auth_method != uaTrust && + port->hba->auth_method != uaGSS) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("GSSAPI encryption cannot be combined with non-GSSAPI authentication"))); + } +#endif + /* * Now proceed to do the actual authentication check */ @@ -524,8 +532,13 @@ ClientAuthentication(Port *port) case uaGSS: #ifdef ENABLE_GSS - sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); - status = pg_GSS_recvauth(port); + if (port->gss->enc) + status = pg_GSS_checkauth(port); + else + { + sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); + status = pg_GSS_recvauth(port); + } #else Assert(false); #endif @@ -1044,64 +1057,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) *---------------------------------------------------------------- */ #ifdef ENABLE_GSS - -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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, const 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) { @@ -1110,7 +1065,6 @@ pg_GSS_recvauth(Port *port) lmin_s, gflags; int mtype; - int ret; StringInfoData buf; gss_buffer_desc gbuf; @@ -1263,10 +1217,17 @@ pg_GSS_recvauth(Port *port) */ gss_release_cred(&min_stat, &port->gss->cred); } + return pg_GSS_checkauth(port); +} + +static int +pg_GSS_checkauth(Port *port) +{ + int ret; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc gbuf; /* - * GSS_S_COMPLETE indicates that authentication is now complete. - * * Get the name of the user that authenticated, and compare it to the pg * username that was specified for the connection. */ @@ -1308,7 +1269,7 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI realm (%s) and configured realm (%s) don't match", cp, port->hba->krb_realm); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } } @@ -1318,14 +1279,14 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI did not return realm but realm matching was requested"); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, pg_krb_caseins_users); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return ret; } diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c new file mode 100644 index 0000000000..78d9f5d325 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,64 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.c + * Common code for GSSAPI authentication and encryption + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/libpq/be-gssapi-common.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +static void +pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type) +{ + gss_buffer_desc gmsg; + size_t i = 0; + OM_uint32 lmin_s, msg_ctx = 0; + + gmsg.value = NULL; + gmsg.length = 0; + + do + { + gss_display_status(&lmin_s, stat, type, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(s + i, gmsg.value, len - i); + i += gmsg.length; + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < len); + + if (msg_ctx || i == len) + ereport(WARNING, + (errmsg_internal("incomplete GSS error report"))); +} + +void +pg_GSS_error(int severity, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat) +{ + char msg_major[128], msg_minor[128]; + + /* Fetch major status message */ + pg_GSS_error_int(msg_major, sizeof(msg_major), maj_stat, GSS_C_GSS_CODE); + + /* Fetch mechanism minor status message */ + pg_GSS_error_int(msg_minor, sizeof(msg_minor), min_stat, GSS_C_MECH_CODE); + + /* + * 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/backend/libpq/be-gssapi-common.h b/src/backend/libpq/be-gssapi-common.h new file mode 100644 index 0000000000..c020051d2e --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.h + * Definitions for GSSAPI authentication and encryption handling + * + * Portions Copyright (c) 1996-2018, 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, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat); + +#endif /* BE_GSSAPI_COMMON_H */ diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c new file mode 100644 index 0000000000..d860f71b71 --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,319 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gssapi.c + * GSSAPI encryption support + * + * Portions Copyright (c) 2018-2018, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +#include "libpq/auth.h" +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "pgstat.h" + +#include <unistd.h> + +static ssize_t +send_buffered_data(Port *port, size_t len) +{ + ssize_t ret = secure_raw_write( + port, + port->gss->writebuf.data + port->gss->writebuf.cursor, + port->gss->writebuf.len - port->gss->writebuf.cursor); + if (ret < 0) + return ret; + + /* update and possibly clear buffer state */ + port->gss->writebuf.cursor += ret; + + if (port->gss->writebuf.cursor == port->gss->writebuf.len) + { + /* entire request has now been written */ + resetStringInfo(&port->gss->writebuf); + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +be_gssapi_write(Port *port, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + pg_gssinfo *gss = port->gss; + + if (gss->writebuf.len != 0) + return send_buffered_data(port, len); + + /* encrypt the message */ + output.value = NULL; + output.length = 0; + input.value = ptr; + input.length = len; + + major = gss_wrap(&minor, 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); + goto cleanup; + } else if (conf == 0) + { + ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality"))); + goto cleanup; + } + + /* 4 network-order length bytes, then payload */ + netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, output.value, output.length); + + ret = send_buffered_data(port, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* load up any available data */ + if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len) + { + /* clamp length */ + if (len > gss->buf.len - gss->buf.cursor) + len = gss->buf.len - gss->buf.cursor; + + memcpy(ptr, gss->buf.data + gss->buf.cursor, len); + gss->buf.cursor += len; + ret = len; + } + + /* reset buffer if all data has been read */ + if (gss->buf.cursor == gss->buf.len) + resetStringInfo(&gss->buf); + + return ret; +} + +static ssize_t +load_packetlen(Port *port) +{ + pg_gssinfo *gss = port->gss; + ssize_t ret; + + if (gss->buf.len < 4) + { + enlargeStringInfo(&gss->buf, 4 - gss->buf.len); + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + 4 - gss->buf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(Port *port, size_t len) +{ + ssize_t ret; + pg_gssinfo *gss = port->gss; + + enlargeStringInfo(&gss->buf, len - gss->buf.len + 4); + + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + len - gss->buf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len - 4 < len) + { + errno = EWOULDBLOCK; + return -1; + } + return 0; +} + +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; + pg_gssinfo *gss = port->gss; + + if (gss->buf.cursor > 0) + return read_from_buffer(gss, ptr, len); + + /* load length if not present */ + ret = load_packetlen(port); + if (ret != 0) + return ret; + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + return ret; + + /* decrypt the packet */ + output.value = NULL; + output.length = 0; + input.value = gss->buf.data + 4; + + major = gss_unwrap(&minor, 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; + } + + /* put the decrypted packet in the buffer */ + resetStringInfo(&gss->buf); + enlargeStringInfo(&gss->buf, output.length); + + memcpy(gss->buf.data, output.value, output.length); + gss->buf.len = output.length; + gss->buf.data[gss->buf.len] = '\0'; + + ret = read_from_buffer(gss, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +ssize_t +secure_open_gssapi(Port *port) +{ + pg_gssinfo *gss = port->gss; + bool complete_next = false; + + /* + * Use the configured keytab, if there is one. Unfortunately, Heimdal + * doesn't support the cred store extensions, so use the env var. + */ + if (pg_krb_server_keyfile != NULL && strlen(pg_krb_server_keyfile) > 0) + setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1); + + while (true) + { + OM_uint32 major, minor; + size_t ret; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + + /* Handle any outgoing data */ + if (gss->writebuf.len != 0) + { + ret = send_buffered_data(port, 1); + if (ret != 1) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + } + + if (complete_next) + break; + + /* Load incoming data */ + ret = load_packetlen(port); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + input.value = gss->buf.data + 4; + + /* Process incoming data. (The client sends first.) */ + major = gss_accept_sec_context(&minor, &port->gss->ctx, + GSS_C_NO_CREDENTIAL, &input, + GSS_C_NO_CHANNEL_BINDINGS, + &port->gss->name, NULL, &output, NULL, + NULL, NULL); + resetStringInfo(&gss->buf); + if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, gettext_noop("GSSAPI context error"), + major, minor); + gss_release_buffer(&minor, &output); + return -1; + } + else if (!(major & GSS_S_CONTINUE_NEEDED)) + { + /* + * rfc2744 technically permits context negotiation to be complete + * both with and without a packet to be sent. + */ + complete_next = true; + } + + if (output.length != 0) + { + /* Queue packet for writing */ + uint32 netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, + output.value, output.length); + gss_release_buffer(&minor, &output); + continue; + } + + /* We're done - woohoo! */ + break; + } + port->gss->enc = true; + return 0; +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index edfe2c0751..7dd1cf7090 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -152,6 +152,14 @@ retry: n = be_tls_read(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_read(port, ptr, len); + waitfor = WL_SOCKET_READABLE; + } + else #endif { n = secure_raw_read(port, ptr, len); @@ -255,6 +263,14 @@ retry: n = be_tls_write(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_write(port, ptr, len); + waitfor = WL_SOCKET_WRITEABLE; + } + else #endif { n = secure_raw_write(port, ptr, len); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index acf625e4ec..9a1aa4e5cc 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -994,7 +994,9 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) } else if (strcmp(token->string, "host") == 0 || strcmp(token->string, "hostssl") == 0 || - strcmp(token->string, "hostnossl") == 0) + strcmp(token->string, "hostnossl") == 0 || + strcmp(token->string, "hostgss") == 0 || + strcmp(token->string, "hostnogss") == 0) { if (token->string[4] == 's') /* "hostssl" */ @@ -1022,10 +1024,23 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "hostssl record cannot match because SSL is not supported by this build"; #endif } - else if (token->string[4] == 'n') /* "hostnossl" */ + else if (token->string[4] == 'g') /* "hostgss" */ { - parsedline->conntype = ctHostNoSSL; + parsedline->conntype = ctHostGSS; +#ifndef ENABLE_GSS + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("hostgss record cannot match because GSSAPI is not supported by this build"), + errhint("Compile with --with-gssapi to use GSSAPI connections."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "hostgss record cannot match because GSSAPI is not supported by this build"; +#endif } + else if (token->string[4] == 'n' && token->string[6] == 's') + parsedline->conntype = ctHostNoSSL; + else if (token->string[4] == 'n' && token->string[6] == 'g') + parsedline->conntype = ctHostNoGSS; else { /* "host" */ @@ -1404,6 +1419,19 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "gssapi authentication is not supported on local sockets"; return NULL; } + if (parsedline->conntype == ctHostGSS && + parsedline->auth_method != uaGSS && + parsedline->auth_method != uaReject && + parsedline->auth_method != uaTrust) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("GSSAPI encryption only supports gss, trust, or reject authentication"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "GSSAPI encryption only supports gss, trust, or reject authenticaion"; + return NULL; + } if (parsedline->conntype != ctLocal && parsedline->auth_method == uaPeer) @@ -2060,6 +2088,17 @@ check_hba(hbaPort *port) continue; } + /* Check GSSAPI state */ +#ifdef ENABLE_GSS + if (port->gss->enc && hba->conntype == ctHostNoGSS) + continue; + else if (!port->gss->enc && hba->conntype == ctHostGSS) + continue; +#else + if (hba->conntype == ctHostGSS) + continue; +#endif + /* Check IP address */ switch (hba->ip_cmp_method) { @@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, case ctHostNoSSL: typestr = "hostnossl"; break; + case ctHostGSS: + typestr = "hostgss"; + break; + case ctHostNoGSS: + typestr = "hostnogss"; + break; } if (typestr) values[index++] = CStringGetTextDatum(typestr); diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 084573e77c..72ce147308 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -3562,6 +3562,9 @@ pgstat_get_wait_client(WaitEventClient w) case WAIT_EVENT_WAL_SENDER_WRITE_DATA: event_name = "WalSenderWriteData"; break; + case WAIT_EVENT_GSS_OPEN_SERVER: + event_name = "GSSOpenServer"; + break; /* no default case, so that compiler will warn */ } diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a4b53b33cd..87dbb33a88 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -1894,7 +1894,7 @@ initMasks(fd_set *rmask) * if we detect a communications failure.) */ static int -ProcessStartupPacket(Port *port, bool SSLdone) +ProcessStartupPacket(Port *port, bool secure_done) { int32 len; void *buf; @@ -1905,11 +1905,11 @@ ProcessStartupPacket(Port *port, bool SSLdone) if (pq_getbytes((char *) &len, 4) == EOF) { /* - * EOF after SSLdone probably means the client didn't like our + * EOF after secure_done probably means the client didn't like our * response to NEGOTIATE_SSL_CODE. That's not an error condition, so * don't clutter the log with a complaint. */ - if (!SSLdone) + if (!secure_done) ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("incomplete startup packet"))); @@ -1961,7 +1961,7 @@ ProcessStartupPacket(Port *port, bool SSLdone) return STATUS_ERROR; } - if (proto == NEGOTIATE_SSL_CODE && !SSLdone) + if (proto == NEGOTIATE_SSL_CODE && !secure_done) { char SSLok; @@ -1994,6 +1994,32 @@ retry1: /* but not another SSL negotiation request */ return ProcessStartupPacket(port, true); } + else if (proto == NEGOTIATE_GSS_CODE && !secure_done) + { + char GSSok = 'N'; +#ifdef ENABLE_GSS + /* No GSSAPI encryption when on Unix socket */ + if (!IS_AF_UNIX(port->laddr.addr.ss_family)) + GSSok = 'G'; +#endif + + while (send(port->sock, &GSSok, 1, 0) != 1) + { + if (errno == EINTR) + continue; + ereport(COMMERROR, + (errcode_for_socket_access(), + errmsg("failed to send GSSAPI negotiation response: %m)"))); + return STATUS_ERROR; /* close the connection */ + } + +#ifdef ENABLE_GSS + if (GSSok == 'G' && secure_open_gssapi(port) == -1) + return STATUS_ERROR; +#endif + /* Won't ever see more than one negotiation request */ + return ProcessStartupPacket(port, true); + } /* Could add additional special packet types here */ @@ -2413,6 +2439,17 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif #endif return port; @@ -2429,7 +2466,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); } @@ -4761,6 +4806,17 @@ SubPostmasterMain(int argc, char *argv[]) (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif /* * If appropriate, physically re-attach to shared memory segment. We want diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 5f68f4c666..830ddaa25a 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -55,7 +55,9 @@ typedef enum ConnType ctLocal, ctHost, ctHostSSL, - ctHostNoSSL + ctHostNoSSL, + ctHostGSS, + ctHostNoGSS, } ConnType; typedef struct HbaLine diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 7698cd1f88..3e46ac437a 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -66,7 +66,7 @@ typedef struct #include "datatype/timestamp.h" #include "libpq/hba.h" #include "libpq/pqcomm.h" - +#include "lib/stringinfo.h" typedef enum CAC_state { @@ -86,6 +86,9 @@ 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 */ + bool enc; /* GSSAPI encryption in use */ + StringInfoData buf; /* GSSAPI encryption data buffering */ + StringInfoData writebuf; /* GSSAPI nonblocking write buffering */ #endif } pg_gssinfo; #endif @@ -278,6 +281,12 @@ extern char *be_tls_get_peer_finished(Port *port, size_t *len); extern char *be_tls_get_certificate_hash(Port *port, size_t *len); #endif +#ifdef ENABLE_GSS +/* Read and write to a GSSAPI-encrypted connection. */ +extern ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); +extern 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 7bf06c65e9..e34f552ef7 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -92,6 +92,9 @@ extern ssize_t secure_read(Port *port, void *ptr, size_t len); extern ssize_t secure_write(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_read(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_write(Port *port, const void *ptr, size_t len); +#ifdef ENABLE_GSS +extern ssize_t secure_open_gssapi(Port *port); +#endif extern bool ssl_loaded_verify_locations; diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index cc0e0b32c7..ade1190096 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -199,9 +199,10 @@ typedef struct CancelRequestPacket /* - * A client can also start by sending a SSL negotiation request, to get a - * secure channel. + * A client can also start by sending a SSL or GSSAPI negotiation request to + * get a secure channel. */ #define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679) +#define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680) #endif /* PQCOMM_H */ diff --git a/src/include/pgstat.h b/src/include/pgstat.h index be2f59239b..4f06f7a2bc 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -787,7 +787,8 @@ typedef enum WAIT_EVENT_SSL_OPEN_SERVER, WAIT_EVENT_WAL_RECEIVER_WAIT_START, WAIT_EVENT_WAL_SENDER_WAIT_WAL, - WAIT_EVENT_WAL_SENDER_WRITE_DATA + WAIT_EVENT_WAL_SENDER_WRITE_DATA, + WAIT_EVENT_GSS_OPEN_SERVER, } WaitEventClient; /* ---------- diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index abe0a50e98..c814e5e35a 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -57,6 +57,10 @@ else OBJS += sha2.o endif +ifeq ($(with_gssapi),yes) +OBJS += fe-gssapi-common.o fe-secure-gssapi.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 3b2073a47f..af2ef78d76 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -49,52 +49,7 @@ * GSSAPI authentication system. */ -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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. @@ -195,18 +150,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen) static int pg_GSS_startup(PGconn *conn, int payloadlen) { - OM_uint32 maj_stat, - min_stat; - int maxlen; - gss_buffer_desc temp_gbuf; - char *host = PQhost(conn); - - if (!(host && host[0] != '\0')) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("host name must be specified\n")); - return STATUS_ERROR; - } + int ret; if (conn->gctx) { @@ -215,33 +159,9 @@ pg_GSS_startup(PGconn *conn, int payloadlen) return STATUS_ERROR; } - /* - * Import service principal name so the proper ticket can be acquired by - * the GSSAPI system. - */ - maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; - temp_gbuf.value = (char *) malloc(maxlen); - if (!temp_gbuf.value) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory\n")); - return STATUS_ERROR; - } - snprintf(temp_gbuf.value, maxlen, "%s@%s", - conn->krbsrvname, host); - temp_gbuf.length = strlen(temp_gbuf.value); - - maj_stat = gss_import_name(&min_stat, &temp_gbuf, - GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); - free(temp_gbuf.value); - - if (maj_stat != GSS_S_COMPLETE) - { - pg_GSS_error(libpq_gettext("GSSAPI name import error"), - conn, - maj_stat, min_stat); - return STATUS_ERROR; - } + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return ret; /* * Initial packet is the same as a continuation packet with no initial diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index a7e969d7c1..2bd1be1b97 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, #else #define DefaultSSLMode "disable" #endif +#ifdef ENABLE_GSS +#include "fe-gssapi-common.h" +#define DefaultGSSMode "prefer" +#else +#define DefaultGSSMode "disable" +#endif /* ---------- * Definition of the conninfo parameters and their fallback resources. @@ -303,6 +309,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Require-Peer", "", 10, offsetof(struct pg_conn, requirepeer)}, + /* + * Expose gssmode similarly to sslmode - we can stil handle "disable" and + * "prefer". + */ + {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL, + "GSS-Mode", "", 7, /* sizeof("disable") == 7 */ + offsetof(struct pg_conn, gssmode)}, + #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* Kerberos and GSSAPI authentication support specifying the service name */ {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL, @@ -1166,6 +1180,39 @@ connectOptions2(PGconn *conn) goto oom_error; } + /* + * validate gssmode option + */ + if (conn->gssmode) + { + if (strcmp(conn->gssmode, "disable") != 0 && + strcmp(conn->gssmode, "prefer") != 0 && + strcmp(conn->gssmode, "require") != 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid gssmode value: \"%s\"\n"), + conn->gssmode); + return false; + } +#ifndef ENABLE_GSS + if (strcmp(conn->gssmode, "require") == 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer( + &conn->errorMessage, + libpq_gettext("no GSSAPI support; cannot require GSSAPI\n")); + return false; + } +#endif + } + else + { + conn->gssmode = strdup(DefaultGSSMode); + if (!conn->gssmode) + goto oom_error; + } + /* * Resolve special "auto" client_encoding from the locale */ @@ -1804,6 +1851,11 @@ connectDBStart(PGconn *conn) conn->wait_ssl_try = true; #endif +#ifdef ENABLE_GSS + if (conn->gssmode[0] == 'd') /* "disable" */ + conn->try_gss = false; +#endif + /* * Set up to try to connect, with protocol 3.0 as the first attempt. */ @@ -2051,6 +2103,7 @@ PQconnectPoll(PGconn *conn) case CONNECTION_NEEDED: case CONNECTION_CHECK_WRITABLE: case CONNECTION_CONSUME: + case CONNECTION_GSS_STARTUP: break; default: @@ -2426,17 +2479,54 @@ keep_going: /* We will come back to here until there is } #endif /* HAVE_UNIX_SOCKETS */ + if (IS_AF_UNIX(conn->raddr.addr.ss_family)) + { + /* Don't request SSL or GSSAPI over Unix sockets */ #ifdef USE_SSL + conn->allow_ssl_try = false; +#endif +#ifdef ENABLE_GSS + conn->try_gss = false; +#endif + } +#ifdef ENABLE_GSS + /* + * If GSSAPI is enabled and we have a ccache, try to set it up + * before sending startup messages. If it's already + * operating, don't try SSL and instead just build the startup + * packet. + */ + if (conn->try_gss && !conn->gctx) + conn->try_gss = pg_GSS_have_ccache(&conn->gcred); + if (conn->try_gss && !conn->gctx) + { + ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE); + if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not send GSSAPI negotiation packet: %s\n"), + SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + goto error_return; + } + + /* Ok, wait for response */ + conn->status = CONNECTION_GSS_STARTUP; + return PGRES_POLLING_READING; + } + else if (!conn->gctx && conn->gssmode[0] == 'r') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache,no server support, or using a local socket)\n")); + goto error_return; + } +#endif + +#ifdef USE_SSL /* * If SSL is enabled and we haven't already got it running, * request it instead of sending the startup message. */ - if (IS_AF_UNIX(conn->raddr.addr.ss_family)) - { - /* Don't bother requesting SSL over a Unix socket */ - conn->allow_ssl_try = false; - } if (conn->allow_ssl_try && !conn->wait_ssl_try && !conn->ssl_in_use) { @@ -2629,6 +2719,97 @@ keep_going: /* We will come back to here until there is #endif /* USE_SSL */ } + case CONNECTION_GSS_STARTUP: + { +#ifdef ENABLE_GSS + PostgresPollingStatusType pollres; + + /* + * If we haven't yet, get the postmaster's response to our + * negotiation packet + */ + if (conn->try_gss && !conn->gctx) + { + char gss_ok; + int rdresult = pqReadData(conn); + if (rdresult < 0) + /* pqReadData fills in error message */ + goto error_return; + else if (rdresult == 0) + /* caller failed to wait for data */ + return PGRES_POLLING_READING; + if (pqGetc(&gss_ok, conn) < 0) + /* shouldn't happen... */ + return PGRES_POLLING_READING; + + if (gss_ok == 'E') + { + /* + * Server failure of some sort. Assume it's a + * protocol version support failure, and let's see if + * we can't recover (if it's not, we'll get a better + * error message on retry). Server gets fussy if we + * don't hang up the socket, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + + /* mark byte consumed */ + conn->inStart = conn->inCursor; + + if (gss_ok == 'N') + { + /* Server doesn't want GSSAPI; fall back if we can */ + if (conn->gssmode[0] == 'r') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n")); + goto error_return; + } + + conn->try_gss = false; + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (gss_ok != 'G') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"), + gss_ok); + goto error_return; + } + } + + /* Begin or continue GSSAPI negotiation */ + pollres = pqsecure_open_gss(conn); + if (pollres == PGRES_POLLING_OK) + { + /* All set for startup packet */ + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (pollres == PGRES_POLLING_FAILED && + conn->gssmode[0] == 'p') + { + /* + * We failed, but we can retry on "prefer". Have to drop + * the current connection to do so, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + return pollres; +#else /* !ENABLE_GSS */ + /* unreachable */ + goto error_return; +#endif /* ENABLE_GSS */ + } + /* * Handle authentication exchange: wait for postmaster messages * and respond as necessary. @@ -2781,6 +2962,24 @@ keep_going: /* We will come back to here until there is /* OK, we read the message; mark data consumed */ conn->inStart = conn->inCursor; +#ifdef ENABLE_GSS + /* + * If gssmode is "prefer" and we're using GSSAPI, retry + * without it. + */ + if (conn->gssenc && conn->gssmode[0] == 'p') + { + OM_uint32 minor; + /* postmaster expects us to drop the connection */ + conn->try_gss = false; + conn->gssenc = false; + gss_delete_sec_context(&minor, &conn->gctx, NULL); + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } +#endif + #ifdef USE_SSL /* @@ -3361,6 +3560,11 @@ makeEmptyPGconn(void) conn->wait_ssl_try = false; conn->ssl_in_use = false; #endif +#ifdef ENABLE_GSS + conn->try_gss = true; + initPQExpBuffer(&conn->gbuf); + initPQExpBuffer(&conn->gwritebuf); +#endif /* * We try to send at least 8K at a time, which is the usual size of pipe @@ -3492,10 +3696,28 @@ freePGconn(PGconn *conn) free(conn->sslcompression); if (conn->requirepeer) free(conn->requirepeer); + if (conn->gssmode) + free(conn->gssmode); #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) if (conn->krbsrvname) free(conn->krbsrvname); #endif +#ifdef ENABLE_GSS + if (conn->gcred != GSS_C_NO_CREDENTIAL) + { + OM_uint32 minor; + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + } + if (conn->gctx) + { + OM_uint32 minor; + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = NULL; + } + termPQExpBuffer(&conn->gbuf); + termPQExpBuffer(&conn->gwritebuf); +#endif #if defined(ENABLE_GSS) && defined(ENABLE_SSPI) if (conn->gsslib) free(conn->gsslib); diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c new file mode 100644 index 0000000000..35e195b7a6 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.c @@ -0,0 +1,128 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.c + * The front-end (client) GSSAPI common code + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-gssapi-common.c + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "fe-gssapi-common.h" + +#include "libpq-int.h" +#include "pqexpbuffer.h" + +/* + * 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); +} + +/* + * Check if we can acquire credentials at all. + */ +bool +pg_GSS_have_ccache(gss_cred_id_t *cred_out) +{ + OM_uint32 major, minor; + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + + major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &cred, NULL, NULL); + if (GSS_ERROR(major)) + { + *cred_out = NULL; + return false; + } + *cred_out = cred; + return true; +} + +/* + * Try to load service name for a connection + */ +int +pg_GSS_load_servicename(PGconn *conn) +{ + OM_uint32 maj_stat, min_stat; + int maxlen; + gss_buffer_desc temp_gbuf; + char *host; + + if (conn->gtarg_nam != NULL) + /* Already taken care of - move along */ + return STATUS_OK; + + host = PQhost(conn); + if (!(host && host[0] != '\0')) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("host name must be specified\n")); + return STATUS_ERROR; + } + + /* + * Import service principal name so the proper ticket can be acquired by + * the GSSAPI system. + */ + maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; + temp_gbuf.value = (char *) malloc(maxlen); + if (!temp_gbuf.value) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return STATUS_ERROR; + } + snprintf(temp_gbuf.value, maxlen, "%s@%s", + conn->krbsrvname, host); + temp_gbuf.length = strlen(temp_gbuf.value); + + maj_stat = gss_import_name(&min_stat, &temp_gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); + free(temp_gbuf.value); + + if (maj_stat != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI name import error"), + conn, + maj_stat, min_stat); + return STATUS_ERROR; + } + return STATUS_OK; +} diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h new file mode 100644 index 0000000000..58811df9f1 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.h + * + * Definitions for GSSAPI common routines + * + * Portions Copyright (c) 1996-2018, 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 + +#include "libpq-fe.h" +#include "libpq-int.h" + +void pg_GSS_error(const char *mprefix, PGconn *conn, + OM_uint32 maj_stat, OM_uint32 min_stat); +bool pg_GSS_have_ccache(gss_cred_id_t *cred_out); +int pg_GSS_load_servicename(PGconn *conn); +#endif /* FE_GSSAPI_COMMON_H */ diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c new file mode 100644 index 0000000000..a71df69ff2 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,343 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-gssapi.c + * The front-end (client) encryption support for GSSAPI + * + * Portions Copyright (c) 2016-2018, 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" + +/* + * Require encryption support, as well as mutual authentication and + * tamperproofing measures. + */ +#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \ + GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG + +static ssize_t +send_buffered_data(PGconn *conn, size_t len) +{ + ssize_t ret = pqsecure_raw_write(conn, + conn->gwritebuf.data + conn->gwritecurs, + conn->gwritebuf.len - conn->gwritecurs); + if (ret < 0) + return ret; + + conn->gwritecurs += ret; + + if (conn->gwritecurs == conn->gwritebuf.len) + { + /* entire request has now been written */ + resetPQExpBuffer(&conn->gwritebuf); + conn->gwritecurs = 0; + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +pg_GSS_write(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + + if (conn->gwritebuf.len != 0) + return send_buffered_data(conn, len); + + /* encrypt the message */ + input.value = ptr; + input.length = len; + + 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); + goto cleanup; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI did not provide confidentiality\n")); + goto cleanup; + } + + /* 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + + ret = send_buffered_data(conn, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* check for available data */ + if (conn->gcursor < conn->gbuf.len) + { + /* clamp length */ + 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; + } + + /* reset buffer if all data has been read */ + if (conn->gcursor == conn->gbuf.len) + { + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + } + + return ret; +} + +static ssize_t +load_packet_length(PGconn *conn) +{ + ssize_t ret; + if (conn->gbuf.len < 4) + { + ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len); + 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 + conn->gbuf.len, + 4 - conn->gbuf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(PGconn *conn, size_t len) +{ + ssize_t ret; + + ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI encrypted packet length %ld too big\n"), + len); + return -1; + } + + /* load any missing parts of the packet */ + if (conn->gbuf.len - 4 < len) + { + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + len - conn->gbuf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < len) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +ssize_t +pg_GSS_read(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = 0; + int conf = 0; + + /* handle any buffered data */ + if (conn->gcursor != 0) + return read_from_buffer(conn, ptr, len); + + /* load in the packet length, if not yet loaded */ + ret = load_packet_length(conn); + if (ret < 0) + return ret; + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0) + return ret; + + /* decrypt the packet */ + 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; + } + + /* load decrypted packet into our buffer */ + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + ret = enlargePQExpBuffer(&conn->gbuf, output.length); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI decrypted packet length %ld too big\n"), + output.length); + ret = -1; + goto cleanup; + } + + memcpy(conn->gbuf.data, output.value, output.length); + conn->gbuf.len = output.length; + conn->gbuf.data[conn->gbuf.len] = '\0'; + + ret = read_from_buffer(conn, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +PostgresPollingStatusType +pqsecure_open_gss(PGconn *conn) +{ + ssize_t ret; + OM_uint32 major, minor; + uint32 netlen; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + + /* Send out any data we might have */ + if (conn->gwritebuf.len != 0) + { + ret = send_buffered_data(conn, 1); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_WRITING; + else if (ret == 1) + /* sent all data */ + return PGRES_POLLING_READING; + return PGRES_POLLING_FAILED; + } + + /* Client sends first, and sending creates a context */ + if (conn->gctx) + { + /* Process any incoming data we might have */ + ret = load_packet_length(conn); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + if (conn->gbuf.data[0] == 'E') + { + /* + * We can taken an error here, and it's my least favorite thing. + * How long can error messages be? That's a good question, but + * backend's pg_gss_error() caps them at 256. Do a single read + * for that and call it a day. Cope with this here rather than in + * load_packet since expandPQExpBuffer will destroy any data in + * the buffer on failure. + */ + load_packet(conn, 256); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Server error: %s"), + conn->gbuf.data + 1); + return PGRES_POLLING_FAILED; + } + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + input.value = conn->gbuf.data + 4; + } + + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return PGRES_POLLING_FAILED; + + major = gss_init_sec_context(&minor, conn->gcred, &conn->gctx, + conn->gtarg_nam, GSS_C_NO_OID, + GSS_REQUIRED_FLAGS, 0, 0, &input, NULL, + &output, NULL, NULL); + resetPQExpBuffer(&conn->gbuf); + if (GSS_ERROR(major)) + { + pg_GSS_error(libpq_gettext("GSSAPI context establishment error"), + conn, major, minor); + return PGRES_POLLING_FAILED; + } + else if (output.length == 0) + { + /* + * We're done - hooray! Kind of gross, but we need to disable SSL + * here so that we don't accidentally tunnel one over the other. + */ +#ifdef USE_SSL + conn->allow_ssl_try = false; +#endif + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + conn->gssenc = true; + return PGRES_POLLING_OK; + } + + /* Queue the token for writing */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + gss_release_buffer(&minor, &output); + return PGRES_POLLING_WRITING; +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index f7dc249bf0..18155e4b5e 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) n = pgtls_read(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_read(conn, ptr, len); + } + else #endif { n = pqsecure_raw_read(conn, ptr, len); @@ -287,7 +294,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; @@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) n = pgtls_write(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_write(conn, ptr, len); + } + else #endif { n = pqsecure_raw_write(conn, ptr, len); diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index ed9c806861..bdd5a10cd8 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -65,8 +65,9 @@ typedef enum CONNECTION_NEEDED, /* Internal state: connect() needed */ CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable * connection. */ - CONNECTION_CONSUME /* Wait for any pending message and consume + CONNECTION_CONSUME, /* Wait for any pending message and consume * them. */ + CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */ } ConnStatusType; typedef enum diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 9a586ff25a..6cf3459a8c 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -22,6 +22,7 @@ /* We assume libpq-fe.h has already been included. */ #include "libpq-events.h" +#include "lib/stringinfo.h" #include <time.h> #ifndef WIN32 @@ -473,9 +474,19 @@ struct pg_conn #endif /* USE_OPENSSL */ #endif /* USE_SSL */ + char *gssmode; /* GSS mode (require,prefer,disable) */ #ifdef ENABLE_GSS gss_ctx_id_t gctx; /* GSS context */ gss_name_t gtarg_nam; /* GSS target name */ + + /* The following are encryption-only */ + 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 try_gss; /* GSS attempting permitted */ + bool gssenc; /* GSS encryption is usable */ + gss_cred_id_t gcred; /* GSS credential temp storage. */ #endif #ifdef ENABLE_SSPI @@ -651,7 +662,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); @@ -745,6 +756,23 @@ extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, int *names_examined, char **first_name); +/* === GSSAPI === */ + +#ifdef ENABLE_GSS + +/* + * Establish a GSSAPI-encrypted connection. + */ +extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn); + +/* + * Read and write functions for GSSAPI-encrypted connections, with internal + * buffering to handle nonblocking sockets. + */ +extern ssize_t pg_GSS_write(PGconn *conn, void *ptr, size_t len); +extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len); +#endif + /* === miscellaneous macros === */ /* diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 593732fd95..bb32d4db1d 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -183,13 +183,20 @@ sub mkvcbuild $postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); $postgres->FullExportDLL('postgres.lib'); - # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c - # if building without OpenSSL + # The OBJS scraper doesn't know about ifdefs, so remove + # be-secure-openssl.c if building without OpenSSL, and + # be-gssapi-common.{c,h} and be-secure-gssapi.c when building with GSSAPI. if (!$solution->{options}->{openssl}) { $postgres->RemoveFile('src/backend/libpq/be-secure-common.c'); $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.h'); + $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + $postgres->RemoveFile('src/backend/libpq/be-secure-gssapi.c'); + } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', 'src/backend/snowball'); @@ -243,9 +250,10 @@ sub mkvcbuild 'src/interfaces/libpq/libpq.rc'); $libpq->AddReference($libpgport); - # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c - # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if - # building with OpenSSL. + # The OBJS scraper doesn't know about ifdefs, so remove + # fe-secure-openssl.c and sha2_openssl.c if building without OpenSSL, and + # remove sha2.c if building with OpenSSL. Also remove + # fe-gssapi-common.{c,h} and fe-secure-gssapi.c when building with GSSAPI. if (!$solution->{options}->{openssl}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c'); @@ -256,6 +264,12 @@ sub mkvcbuild { $libpq->RemoveFile('src/common/sha2.c'); } + if (!$solution->{options}->{gss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.h'); + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c'); + } my $libpqwalreceiver = $solution->AddProject('libpqwalreceiver', 'dll', '', -- 2.17.0
Attachment
On Sat, May 26, 2018 at 6:58 AM, Robbie Harwood <rharwood@redhat.com> wrote: > Me and the bot are having an argument. This should green Linux but I > dunno about Windows. BTW if you're looking for a way to try stuff out on Windows exactly the way cfbot does it without posting a new patch to the mailing list, I put some instructions here: https://wiki.postgresql.org/wiki/Continuous_Integration The .patch there could certainly be improved (ideally, I think, it'd run the whole build farm animal script) but it's a start. -- Thomas Munro http://www.enterprisedb.com
Thomas Munro <thomas.munro@enterprisedb.com> writes: > On Sat, May 26, 2018 at 6:58 AM, Robbie Harwood <rharwood@redhat.com> wrote: >> Me and the bot are having an argument. This should green Linux but I >> dunno about Windows. > > BTW if you're looking for a way to try stuff out on Windows exactly > the way cfbot does it without posting a new patch to the mailing list, > I put some instructions here: > > https://wiki.postgresql.org/wiki/Continuous_Integration > > The .patch there could certainly be improved (ideally, I think, it'd > run the whole build farm animal script) but it's a start. Appreciated. I think I've got it now. Thanks, --Robbie From 43a83485a487f8aaf78109dea5d63b26b395c683 Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Thu, 10 May 2018 16:12:03 -0400 Subject: [PATCH] libpq GSSAPI encryption support On both the frontend and backend, prepare for GSSAPI encryption support by moving common code for error handling into a separate file. Fix a TODO for handling multiple status messages in the process. Eliminate the OIDs, which have not been needed for some time. Add frontend and backend encryption support functions. Keep the context initiation for authentication-only separate on both the frontend and backend in order to avoid concerns about changing the requested flags to include encryption support. In postmaster, pull GSSAPI authorization checking into a shared function. Also share the initiator name between the encryption and non-encryption codepaths. Modify pqsecure_write() to take a non-const pointer. The pointer will not be modified, but this change (or a const-discarding cast, or a malloc()+memcpy()) is necessary for GSSAPI due to const/struct interactions in C. For HBA, add "hostgss" and "hostnogss" entries that behave similarly to their SSL counterparts. "hostgss" requires either "gss", "trust", or "reject" for its authentication. Simiarly, add a "gssmode" parameter to libpq. Supported values are "disable", "require", and "prefer". Notably, negotiation will only be attempted if credentials can be acquired. Move credential acquisition into its own function to support this behavior. Finally, add documentation for everything new, and update existing documentation on connection security. Thanks to Michael Paquier for the Windows fixes. --- doc/src/sgml/client-auth.sgml | 75 ++++-- doc/src/sgml/libpq.sgml | 57 +++- doc/src/sgml/runtime.sgml | 77 +++++- src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 103 +++---- src/backend/libpq/be-gssapi-common.c | 64 +++++ src/backend/libpq/be-gssapi-common.h | 26 ++ src/backend/libpq/be-secure-gssapi.c | 319 ++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 ++ src/backend/libpq/hba.c | 51 +++- src/backend/postmaster/pgstat.c | 3 + src/backend/postmaster/postmaster.c | 64 ++++- src/include/libpq/hba.h | 4 +- src/include/libpq/libpq-be.h | 11 +- src/include/libpq/libpq.h | 3 + src/include/libpq/pqcomm.h | 5 +- src/include/pgstat.h | 3 +- src/interfaces/libpq/Makefile | 4 + src/interfaces/libpq/fe-auth.c | 90 +------ src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++- src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++ src/interfaces/libpq/fe-gssapi-common.h | 23 ++ src/interfaces/libpq/fe-secure-gssapi.c | 343 ++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-fe.h | 3 +- src/interfaces/libpq/libpq-int.h | 30 ++- src/tools/msvc/Mkvcbuild.pm | 22 +- 27 files changed, 1574 insertions(+), 202 deletions(-) create mode 100644 src/backend/libpq/be-gssapi-common.c create mode 100644 src/backend/libpq/be-gssapi-common.h create mode 100644 src/backend/libpq/be-secure-gssapi.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.h 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 656d5f9417..38cf32e3be 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> </synopsis> The meaning of the fields is as follows: @@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> This record matches connection attempts made using TCP/IP. - <literal>host</literal> records match either + <literal>host</literal> records match <acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection - attempts. + attempts as well as <acronym>GSSAPI</acronym> or + non-<acronym>GSSAPI</acronym> connection attempts. </para> <note> <para> @@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><literal>hostgss</literal></term> + <listitem> + <para> + This record matches connection attempts made using TCP/IP, + but only when the connection is made with <acronym>GSSAPI</acronym> + encryption. + </para> + + <para> + To make use of this option the server must be built with + <acronym>GSSAPI</acronym> support. Otherwise, + the <literal>hostgss</literal> record is ignored except for logging a + warning that it cannot match any connections. + </para> + + <para> + Note that the only supported <xref linkend="auth-methods"/> for use + with <acronym>GSSAPI</acronym> encryption + are <literal>gss</literal>, <literal>reject</literal>, + and <literal>trust</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>hostnogss</literal></term> + <listitem> + <para> + This record type has the opposite behavior of <literal>hostgss</literal>; + it only matches connection attempts made over + TCP/IP that do not use <acronym>GSSAPI</acronym>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable>database</replaceable></term> <listitem> @@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> Use GSSAPI to authenticate the user. This is only - available for TCP/IP connections. See <xref - linkend="gssapi-auth"/> for details. + available for TCP/IP connections . See <xref + linkend="gssapi-auth"/> for details. It can be used in conjunction + with GSSAPI encryption. </para> </listitem> </varlistentry> @@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256 host all mike .example.com md5 host all all .example.com scram-sha-256 -# In the absence of preceding "host" lines, these two lines will +# In the absence of preceding "host" lines, these three lines will # reject all connections from 192.168.54.1 (since that entry will be -# matched first), but allow GSSAPI connections from anywhere else -# on the Internet. The zero mask causes no bits of the host IP +# matched first), but allow GSSAPI-encrypted connections from anywhere else +# on the Internet. Unencrypted GSSAPI connections are allowed from +# 192.168.12.10 only. The zero mask causes no bits of the host IP # address to be considered, so it matches any host. # # TYPE DATABASE USER ADDRESS METHOD host all all 192.168.54.1/32 reject -host all all 0.0.0.0/0 gss +hostgss all all 0.0.0.0/0 gss +host all all 192.168.12.10/32 gss # Allow users from 192.168.x.x hosts to connect to any database, if # they pass the ident check. If, for example, ident says the user is @@ -1051,13 +1093,16 @@ omicron bryanh guest1 <para> <productname>GSSAPI</productname> is an industry-standard protocol for secure authentication defined in RFC 2743. - <productname>PostgreSQL</productname> supports - <productname>GSSAPI</productname> with <productname>Kerberos</productname> - authentication according to RFC 1964. <productname>GSSAPI</productname> - 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. + + <productname>PostgreSQL</productname> + supports <productname>GSSAPI</productname> for use as either an encrypted, + authenticated layer, or as authentication + only. <productname>GSSAPI</productname> provides automatic authentication + (single sign-on) for systems that support it. The authentication itself is + secure. If <productname>GSSAPI</productname> encryption + (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are + used, the data sent along the database connection will be encrypted; + otherwise, it will not. </para> <para> diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 800e68a19e..73d233dc16 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1320,6 +1320,61 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode"> + <term><literal>gssmode</literal></term> + <listitem> + <para> + This option determines whether or with what priority a secure + <acronym>GSS</acronym> TCP/IP connection will be negotiated with the + server. There are three modes: + + <variablelist> + <varlistentry> + <term><literal>disable</literal></term> + <listitem> + <para> + only try a non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>prefer</literal> (default)</term> + <listitem> + <para> + if there are <acronym>GSSAPI</acronym> credentials present (i.e., + in a credentials cache), first try + a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or + there are no credentials, try a + non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>require</literal></term> + <listitem> + <para> + only try a <acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + <literal>gssmode</literal> is ignored for Unix domain socket + communication. If <productname>PostgreSQL</productname> is compiled + without GSSAPI support, using the <literal>require</literal> option + will cause an error, while <literal>prefer</literal> will be accepted + but <application>libpq</application> will not actually attempt + a <acronym>GSSAPI</acronym>-encrypted + connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with + libpq</secondary></indexterm> + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-sslmode" xreflabel="sslmode"> <term><literal>sslmode</literal></term> <listitem> @@ -7872,7 +7927,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*) </para> <para> - For a connection to be known secure, SSL usage must be configured + For a connection to be known SSL-secured, SSL usage must be configured on <emphasis>both the client and the server</emphasis> before the connection is made. If it is only configured on the server, the client may end up sending sensitive information (e.g. passwords) before diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 330e38a29e..06bf56153c 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 </para> <para> - To prevent spoofing on TCP connections, the best solution is to use - SSL certificates and make sure that clients check the server's certificate. - To do that, the server + To prevent spoofing on TCP connections, either use + SSL certificates and make sure that clients check the server's certificate, + or use GSSAPI encryption (or both, if they're on separate connections). + </para> + + <para> + To prevent spoofing with SSL, the server must be configured to accept only <literal>hostssl</literal> connections (<xref linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files (<xref linkend="ssl-tcp"/>). The TCP client must connect using @@ -2018,6 +2022,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 <literal>verify-full</literal> and have the appropriate root certificate file installed (<xref linkend="libq-ssl-certificates"/>). </para> + + <para> + To prevent spoofing with GSSAPI, the server must be configured to accept + only <literal>hostgss</literal> connections + (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal> + authentication with them. The TCP client must connect + using <literal>gssmode=require</literal>. + </para> </sect1> <sect1 id="encryption-options"> @@ -2114,8 +2126,24 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 which hosts can use non-encrypted connections (<literal>host</literal>) and which require SSL-encrypted connections (<literal>hostssl</literal>). Also, clients can specify that they - connect to servers only via SSL. <application>Stunnel</application> or - <application>SSH</application> can also be used to encrypt transmissions. + connect to servers only via SSL. + </para> + + <para> + GSSAPI-encrypted connections encrypt all data sent across the network, + including queries and data returned. (No password is sent across the + network.) The <filename>pg_hba.conf</filename> file allows + administrators to specify which hosts can use non-encrypted connections + (<literal>host</literal>) and which require GSSAPI-encrypted connections + (<literal>hostgss</literal>). Also, clients can specify that they + connect to servers only on GSSAPI-encrypted connections + (<literal>gssmode=require</literal>). + </para> + + <para> + <application>Stunnel</application> or + <application>SSH</application> can also be used to encrypt + transmissions. </para> </listitem> </varlistentry> @@ -2505,6 +2533,45 @@ openssl x509 -req -in server.csr -text -days 365 \ </sect1> + <sect1 id="gssapi-enc"> + <title>Secure TCP/IP Connections with GSSAPI encryption</title> + + <indexterm zone="gssapi-enc"> + <primary>gssapi</primary> + </indexterm> + + <para> + <productname>PostgreSQL</productname> also has native support for + using <acronym>GSSAPI</acronym> to encrypt client/server communications for + increased security. Support requires that a <acronym>GSSAPI</acronym> + implementation (such as MIT krb5) is installed on both client and server + systems, and that support in <productname>PostgreSQL</productname> is + enabled at build time (see <xref linkend="installation"/>). + </para> + + <sect2 id="gssapi-setup"> + <title>Basic Setup</title> + + <para> + The <productname>PostgreSQL</productname> server will listen for both + normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP + port, and will negotiate with any connecting client on whether to + use <acronym>GSSAPI</acronym> for encryption (and for authentication). By + default, this decision is up to the client (which means it can be + downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about + setting up the server to require the use of <acronym>GSSAPI</acronym> for + some or all conections. + </para> + + <para> + Other than configuration of the negotiation + behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond + that which necessary for GSSAPI authentication. (For more information on + configuring that, see <xref linkend="gssapi-auth"/>.) + </para> + </sect2> + </sect1> + <sect1 id="ssh-tunnels"> <title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title> diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 3dbec23e30..47efef0682 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 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 3014b17a7c..6e4cd66a90 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -173,12 +173,9 @@ bool pg_krb_caseins_users; *---------------------------------------------------------------- */ #ifdef ENABLE_GSS -#if defined(HAVE_GSSAPI_H) -#include <gssapi.h> -#else -#include <gssapi/gssapi.h> -#endif +#include "be-gssapi-common.h" +static int pg_GSS_checkauth(Port *port); static int pg_GSS_recvauth(Port *port); #endif /* ENABLE_GSS */ @@ -384,6 +381,17 @@ ClientAuthentication(Port *port) errmsg("connection requires a valid client certificate"))); } +#ifdef ENABLE_GSS + if (port->gss->enc && port->hba->auth_method != uaReject && + port->hba->auth_method != uaImplicitReject && + port->hba->auth_method != uaTrust && + port->hba->auth_method != uaGSS) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("GSSAPI encryption cannot be combined with non-GSSAPI authentication"))); + } +#endif + /* * Now proceed to do the actual authentication check */ @@ -524,8 +532,13 @@ ClientAuthentication(Port *port) case uaGSS: #ifdef ENABLE_GSS - sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); - status = pg_GSS_recvauth(port); + if (port->gss->enc) + status = pg_GSS_checkauth(port); + else + { + sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); + status = pg_GSS_recvauth(port); + } #else Assert(false); #endif @@ -1044,64 +1057,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) *---------------------------------------------------------------- */ #ifdef ENABLE_GSS - -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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, const 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) { @@ -1110,7 +1065,6 @@ pg_GSS_recvauth(Port *port) lmin_s, gflags; int mtype; - int ret; StringInfoData buf; gss_buffer_desc gbuf; @@ -1263,10 +1217,17 @@ pg_GSS_recvauth(Port *port) */ gss_release_cred(&min_stat, &port->gss->cred); } + return pg_GSS_checkauth(port); +} + +static int +pg_GSS_checkauth(Port *port) +{ + int ret; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc gbuf; /* - * GSS_S_COMPLETE indicates that authentication is now complete. - * * Get the name of the user that authenticated, and compare it to the pg * username that was specified for the connection. */ @@ -1308,7 +1269,7 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI realm (%s) and configured realm (%s) don't match", cp, port->hba->krb_realm); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } } @@ -1318,14 +1279,14 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI did not return realm but realm matching was requested"); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, pg_krb_caseins_users); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return ret; } diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c new file mode 100644 index 0000000000..78d9f5d325 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,64 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.c + * Common code for GSSAPI authentication and encryption + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/libpq/be-gssapi-common.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +static void +pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type) +{ + gss_buffer_desc gmsg; + size_t i = 0; + OM_uint32 lmin_s, msg_ctx = 0; + + gmsg.value = NULL; + gmsg.length = 0; + + do + { + gss_display_status(&lmin_s, stat, type, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(s + i, gmsg.value, len - i); + i += gmsg.length; + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < len); + + if (msg_ctx || i == len) + ereport(WARNING, + (errmsg_internal("incomplete GSS error report"))); +} + +void +pg_GSS_error(int severity, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat) +{ + char msg_major[128], msg_minor[128]; + + /* Fetch major status message */ + pg_GSS_error_int(msg_major, sizeof(msg_major), maj_stat, GSS_C_GSS_CODE); + + /* Fetch mechanism minor status message */ + pg_GSS_error_int(msg_minor, sizeof(msg_minor), min_stat, GSS_C_MECH_CODE); + + /* + * 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/backend/libpq/be-gssapi-common.h b/src/backend/libpq/be-gssapi-common.h new file mode 100644 index 0000000000..c020051d2e --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.h + * Definitions for GSSAPI authentication and encryption handling + * + * Portions Copyright (c) 1996-2018, 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, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat); + +#endif /* BE_GSSAPI_COMMON_H */ diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c new file mode 100644 index 0000000000..d860f71b71 --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,319 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gssapi.c + * GSSAPI encryption support + * + * Portions Copyright (c) 2018-2018, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +#include "libpq/auth.h" +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "pgstat.h" + +#include <unistd.h> + +static ssize_t +send_buffered_data(Port *port, size_t len) +{ + ssize_t ret = secure_raw_write( + port, + port->gss->writebuf.data + port->gss->writebuf.cursor, + port->gss->writebuf.len - port->gss->writebuf.cursor); + if (ret < 0) + return ret; + + /* update and possibly clear buffer state */ + port->gss->writebuf.cursor += ret; + + if (port->gss->writebuf.cursor == port->gss->writebuf.len) + { + /* entire request has now been written */ + resetStringInfo(&port->gss->writebuf); + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +be_gssapi_write(Port *port, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + pg_gssinfo *gss = port->gss; + + if (gss->writebuf.len != 0) + return send_buffered_data(port, len); + + /* encrypt the message */ + output.value = NULL; + output.length = 0; + input.value = ptr; + input.length = len; + + major = gss_wrap(&minor, 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); + goto cleanup; + } else if (conf == 0) + { + ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality"))); + goto cleanup; + } + + /* 4 network-order length bytes, then payload */ + netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, output.value, output.length); + + ret = send_buffered_data(port, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* load up any available data */ + if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len) + { + /* clamp length */ + if (len > gss->buf.len - gss->buf.cursor) + len = gss->buf.len - gss->buf.cursor; + + memcpy(ptr, gss->buf.data + gss->buf.cursor, len); + gss->buf.cursor += len; + ret = len; + } + + /* reset buffer if all data has been read */ + if (gss->buf.cursor == gss->buf.len) + resetStringInfo(&gss->buf); + + return ret; +} + +static ssize_t +load_packetlen(Port *port) +{ + pg_gssinfo *gss = port->gss; + ssize_t ret; + + if (gss->buf.len < 4) + { + enlargeStringInfo(&gss->buf, 4 - gss->buf.len); + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + 4 - gss->buf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(Port *port, size_t len) +{ + ssize_t ret; + pg_gssinfo *gss = port->gss; + + enlargeStringInfo(&gss->buf, len - gss->buf.len + 4); + + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + len - gss->buf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len - 4 < len) + { + errno = EWOULDBLOCK; + return -1; + } + return 0; +} + +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; + pg_gssinfo *gss = port->gss; + + if (gss->buf.cursor > 0) + return read_from_buffer(gss, ptr, len); + + /* load length if not present */ + ret = load_packetlen(port); + if (ret != 0) + return ret; + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + return ret; + + /* decrypt the packet */ + output.value = NULL; + output.length = 0; + input.value = gss->buf.data + 4; + + major = gss_unwrap(&minor, 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; + } + + /* put the decrypted packet in the buffer */ + resetStringInfo(&gss->buf); + enlargeStringInfo(&gss->buf, output.length); + + memcpy(gss->buf.data, output.value, output.length); + gss->buf.len = output.length; + gss->buf.data[gss->buf.len] = '\0'; + + ret = read_from_buffer(gss, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +ssize_t +secure_open_gssapi(Port *port) +{ + pg_gssinfo *gss = port->gss; + bool complete_next = false; + + /* + * Use the configured keytab, if there is one. Unfortunately, Heimdal + * doesn't support the cred store extensions, so use the env var. + */ + if (pg_krb_server_keyfile != NULL && strlen(pg_krb_server_keyfile) > 0) + setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1); + + while (true) + { + OM_uint32 major, minor; + size_t ret; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + + /* Handle any outgoing data */ + if (gss->writebuf.len != 0) + { + ret = send_buffered_data(port, 1); + if (ret != 1) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + } + + if (complete_next) + break; + + /* Load incoming data */ + ret = load_packetlen(port); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + input.value = gss->buf.data + 4; + + /* Process incoming data. (The client sends first.) */ + major = gss_accept_sec_context(&minor, &port->gss->ctx, + GSS_C_NO_CREDENTIAL, &input, + GSS_C_NO_CHANNEL_BINDINGS, + &port->gss->name, NULL, &output, NULL, + NULL, NULL); + resetStringInfo(&gss->buf); + if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, gettext_noop("GSSAPI context error"), + major, minor); + gss_release_buffer(&minor, &output); + return -1; + } + else if (!(major & GSS_S_CONTINUE_NEEDED)) + { + /* + * rfc2744 technically permits context negotiation to be complete + * both with and without a packet to be sent. + */ + complete_next = true; + } + + if (output.length != 0) + { + /* Queue packet for writing */ + uint32 netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, + output.value, output.length); + gss_release_buffer(&minor, &output); + continue; + } + + /* We're done - woohoo! */ + break; + } + port->gss->enc = true; + return 0; +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index edfe2c0751..7dd1cf7090 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -152,6 +152,14 @@ retry: n = be_tls_read(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_read(port, ptr, len); + waitfor = WL_SOCKET_READABLE; + } + else #endif { n = secure_raw_read(port, ptr, len); @@ -255,6 +263,14 @@ retry: n = be_tls_write(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_write(port, ptr, len); + waitfor = WL_SOCKET_WRITEABLE; + } + else #endif { n = secure_raw_write(port, ptr, len); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index acf625e4ec..9a1aa4e5cc 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -994,7 +994,9 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) } else if (strcmp(token->string, "host") == 0 || strcmp(token->string, "hostssl") == 0 || - strcmp(token->string, "hostnossl") == 0) + strcmp(token->string, "hostnossl") == 0 || + strcmp(token->string, "hostgss") == 0 || + strcmp(token->string, "hostnogss") == 0) { if (token->string[4] == 's') /* "hostssl" */ @@ -1022,10 +1024,23 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "hostssl record cannot match because SSL is not supported by this build"; #endif } - else if (token->string[4] == 'n') /* "hostnossl" */ + else if (token->string[4] == 'g') /* "hostgss" */ { - parsedline->conntype = ctHostNoSSL; + parsedline->conntype = ctHostGSS; +#ifndef ENABLE_GSS + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("hostgss record cannot match because GSSAPI is not supported by this build"), + errhint("Compile with --with-gssapi to use GSSAPI connections."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "hostgss record cannot match because GSSAPI is not supported by this build"; +#endif } + else if (token->string[4] == 'n' && token->string[6] == 's') + parsedline->conntype = ctHostNoSSL; + else if (token->string[4] == 'n' && token->string[6] == 'g') + parsedline->conntype = ctHostNoGSS; else { /* "host" */ @@ -1404,6 +1419,19 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "gssapi authentication is not supported on local sockets"; return NULL; } + if (parsedline->conntype == ctHostGSS && + parsedline->auth_method != uaGSS && + parsedline->auth_method != uaReject && + parsedline->auth_method != uaTrust) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("GSSAPI encryption only supports gss, trust, or reject authentication"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "GSSAPI encryption only supports gss, trust, or reject authenticaion"; + return NULL; + } if (parsedline->conntype != ctLocal && parsedline->auth_method == uaPeer) @@ -2060,6 +2088,17 @@ check_hba(hbaPort *port) continue; } + /* Check GSSAPI state */ +#ifdef ENABLE_GSS + if (port->gss->enc && hba->conntype == ctHostNoGSS) + continue; + else if (!port->gss->enc && hba->conntype == ctHostGSS) + continue; +#else + if (hba->conntype == ctHostGSS) + continue; +#endif + /* Check IP address */ switch (hba->ip_cmp_method) { @@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, case ctHostNoSSL: typestr = "hostnossl"; break; + case ctHostGSS: + typestr = "hostgss"; + break; + case ctHostNoGSS: + typestr = "hostnogss"; + break; } if (typestr) values[index++] = CStringGetTextDatum(typestr); diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 084573e77c..72ce147308 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -3562,6 +3562,9 @@ pgstat_get_wait_client(WaitEventClient w) case WAIT_EVENT_WAL_SENDER_WRITE_DATA: event_name = "WalSenderWriteData"; break; + case WAIT_EVENT_GSS_OPEN_SERVER: + event_name = "GSSOpenServer"; + break; /* no default case, so that compiler will warn */ } diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a4b53b33cd..87dbb33a88 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -1894,7 +1894,7 @@ initMasks(fd_set *rmask) * if we detect a communications failure.) */ static int -ProcessStartupPacket(Port *port, bool SSLdone) +ProcessStartupPacket(Port *port, bool secure_done) { int32 len; void *buf; @@ -1905,11 +1905,11 @@ ProcessStartupPacket(Port *port, bool SSLdone) if (pq_getbytes((char *) &len, 4) == EOF) { /* - * EOF after SSLdone probably means the client didn't like our + * EOF after secure_done probably means the client didn't like our * response to NEGOTIATE_SSL_CODE. That's not an error condition, so * don't clutter the log with a complaint. */ - if (!SSLdone) + if (!secure_done) ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("incomplete startup packet"))); @@ -1961,7 +1961,7 @@ ProcessStartupPacket(Port *port, bool SSLdone) return STATUS_ERROR; } - if (proto == NEGOTIATE_SSL_CODE && !SSLdone) + if (proto == NEGOTIATE_SSL_CODE && !secure_done) { char SSLok; @@ -1994,6 +1994,32 @@ retry1: /* but not another SSL negotiation request */ return ProcessStartupPacket(port, true); } + else if (proto == NEGOTIATE_GSS_CODE && !secure_done) + { + char GSSok = 'N'; +#ifdef ENABLE_GSS + /* No GSSAPI encryption when on Unix socket */ + if (!IS_AF_UNIX(port->laddr.addr.ss_family)) + GSSok = 'G'; +#endif + + while (send(port->sock, &GSSok, 1, 0) != 1) + { + if (errno == EINTR) + continue; + ereport(COMMERROR, + (errcode_for_socket_access(), + errmsg("failed to send GSSAPI negotiation response: %m)"))); + return STATUS_ERROR; /* close the connection */ + } + +#ifdef ENABLE_GSS + if (GSSok == 'G' && secure_open_gssapi(port) == -1) + return STATUS_ERROR; +#endif + /* Won't ever see more than one negotiation request */ + return ProcessStartupPacket(port, true); + } /* Could add additional special packet types here */ @@ -2413,6 +2439,17 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif #endif return port; @@ -2429,7 +2466,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); } @@ -4761,6 +4806,17 @@ SubPostmasterMain(int argc, char *argv[]) (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif /* * If appropriate, physically re-attach to shared memory segment. We want diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 5f68f4c666..830ddaa25a 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -55,7 +55,9 @@ typedef enum ConnType ctLocal, ctHost, ctHostSSL, - ctHostNoSSL + ctHostNoSSL, + ctHostGSS, + ctHostNoGSS, } ConnType; typedef struct HbaLine diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 7698cd1f88..3e46ac437a 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -66,7 +66,7 @@ typedef struct #include "datatype/timestamp.h" #include "libpq/hba.h" #include "libpq/pqcomm.h" - +#include "lib/stringinfo.h" typedef enum CAC_state { @@ -86,6 +86,9 @@ 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 */ + bool enc; /* GSSAPI encryption in use */ + StringInfoData buf; /* GSSAPI encryption data buffering */ + StringInfoData writebuf; /* GSSAPI nonblocking write buffering */ #endif } pg_gssinfo; #endif @@ -278,6 +281,12 @@ extern char *be_tls_get_peer_finished(Port *port, size_t *len); extern char *be_tls_get_certificate_hash(Port *port, size_t *len); #endif +#ifdef ENABLE_GSS +/* Read and write to a GSSAPI-encrypted connection. */ +extern ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); +extern 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 7bf06c65e9..e34f552ef7 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -92,6 +92,9 @@ extern ssize_t secure_read(Port *port, void *ptr, size_t len); extern ssize_t secure_write(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_read(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_write(Port *port, const void *ptr, size_t len); +#ifdef ENABLE_GSS +extern ssize_t secure_open_gssapi(Port *port); +#endif extern bool ssl_loaded_verify_locations; diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index cc0e0b32c7..ade1190096 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -199,9 +199,10 @@ typedef struct CancelRequestPacket /* - * A client can also start by sending a SSL negotiation request, to get a - * secure channel. + * A client can also start by sending a SSL or GSSAPI negotiation request to + * get a secure channel. */ #define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679) +#define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680) #endif /* PQCOMM_H */ diff --git a/src/include/pgstat.h b/src/include/pgstat.h index be2f59239b..4f06f7a2bc 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -787,7 +787,8 @@ typedef enum WAIT_EVENT_SSL_OPEN_SERVER, WAIT_EVENT_WAL_RECEIVER_WAIT_START, WAIT_EVENT_WAL_SENDER_WAIT_WAL, - WAIT_EVENT_WAL_SENDER_WRITE_DATA + WAIT_EVENT_WAL_SENDER_WRITE_DATA, + WAIT_EVENT_GSS_OPEN_SERVER, } WaitEventClient; /* ---------- diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index abe0a50e98..c814e5e35a 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -57,6 +57,10 @@ else OBJS += sha2.o endif +ifeq ($(with_gssapi),yes) +OBJS += fe-gssapi-common.o fe-secure-gssapi.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 3b2073a47f..af2ef78d76 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -49,52 +49,7 @@ * GSSAPI authentication system. */ -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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. @@ -195,18 +150,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen) static int pg_GSS_startup(PGconn *conn, int payloadlen) { - OM_uint32 maj_stat, - min_stat; - int maxlen; - gss_buffer_desc temp_gbuf; - char *host = PQhost(conn); - - if (!(host && host[0] != '\0')) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("host name must be specified\n")); - return STATUS_ERROR; - } + int ret; if (conn->gctx) { @@ -215,33 +159,9 @@ pg_GSS_startup(PGconn *conn, int payloadlen) return STATUS_ERROR; } - /* - * Import service principal name so the proper ticket can be acquired by - * the GSSAPI system. - */ - maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; - temp_gbuf.value = (char *) malloc(maxlen); - if (!temp_gbuf.value) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory\n")); - return STATUS_ERROR; - } - snprintf(temp_gbuf.value, maxlen, "%s@%s", - conn->krbsrvname, host); - temp_gbuf.length = strlen(temp_gbuf.value); - - maj_stat = gss_import_name(&min_stat, &temp_gbuf, - GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); - free(temp_gbuf.value); - - if (maj_stat != GSS_S_COMPLETE) - { - pg_GSS_error(libpq_gettext("GSSAPI name import error"), - conn, - maj_stat, min_stat); - return STATUS_ERROR; - } + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return ret; /* * Initial packet is the same as a continuation packet with no initial diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index a7e969d7c1..2bd1be1b97 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, #else #define DefaultSSLMode "disable" #endif +#ifdef ENABLE_GSS +#include "fe-gssapi-common.h" +#define DefaultGSSMode "prefer" +#else +#define DefaultGSSMode "disable" +#endif /* ---------- * Definition of the conninfo parameters and their fallback resources. @@ -303,6 +309,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Require-Peer", "", 10, offsetof(struct pg_conn, requirepeer)}, + /* + * Expose gssmode similarly to sslmode - we can stil handle "disable" and + * "prefer". + */ + {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL, + "GSS-Mode", "", 7, /* sizeof("disable") == 7 */ + offsetof(struct pg_conn, gssmode)}, + #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* Kerberos and GSSAPI authentication support specifying the service name */ {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL, @@ -1166,6 +1180,39 @@ connectOptions2(PGconn *conn) goto oom_error; } + /* + * validate gssmode option + */ + if (conn->gssmode) + { + if (strcmp(conn->gssmode, "disable") != 0 && + strcmp(conn->gssmode, "prefer") != 0 && + strcmp(conn->gssmode, "require") != 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid gssmode value: \"%s\"\n"), + conn->gssmode); + return false; + } +#ifndef ENABLE_GSS + if (strcmp(conn->gssmode, "require") == 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer( + &conn->errorMessage, + libpq_gettext("no GSSAPI support; cannot require GSSAPI\n")); + return false; + } +#endif + } + else + { + conn->gssmode = strdup(DefaultGSSMode); + if (!conn->gssmode) + goto oom_error; + } + /* * Resolve special "auto" client_encoding from the locale */ @@ -1804,6 +1851,11 @@ connectDBStart(PGconn *conn) conn->wait_ssl_try = true; #endif +#ifdef ENABLE_GSS + if (conn->gssmode[0] == 'd') /* "disable" */ + conn->try_gss = false; +#endif + /* * Set up to try to connect, with protocol 3.0 as the first attempt. */ @@ -2051,6 +2103,7 @@ PQconnectPoll(PGconn *conn) case CONNECTION_NEEDED: case CONNECTION_CHECK_WRITABLE: case CONNECTION_CONSUME: + case CONNECTION_GSS_STARTUP: break; default: @@ -2426,17 +2479,54 @@ keep_going: /* We will come back to here until there is } #endif /* HAVE_UNIX_SOCKETS */ + if (IS_AF_UNIX(conn->raddr.addr.ss_family)) + { + /* Don't request SSL or GSSAPI over Unix sockets */ #ifdef USE_SSL + conn->allow_ssl_try = false; +#endif +#ifdef ENABLE_GSS + conn->try_gss = false; +#endif + } +#ifdef ENABLE_GSS + /* + * If GSSAPI is enabled and we have a ccache, try to set it up + * before sending startup messages. If it's already + * operating, don't try SSL and instead just build the startup + * packet. + */ + if (conn->try_gss && !conn->gctx) + conn->try_gss = pg_GSS_have_ccache(&conn->gcred); + if (conn->try_gss && !conn->gctx) + { + ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE); + if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not send GSSAPI negotiation packet: %s\n"), + SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + goto error_return; + } + + /* Ok, wait for response */ + conn->status = CONNECTION_GSS_STARTUP; + return PGRES_POLLING_READING; + } + else if (!conn->gctx && conn->gssmode[0] == 'r') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache,no server support, or using a local socket)\n")); + goto error_return; + } +#endif + +#ifdef USE_SSL /* * If SSL is enabled and we haven't already got it running, * request it instead of sending the startup message. */ - if (IS_AF_UNIX(conn->raddr.addr.ss_family)) - { - /* Don't bother requesting SSL over a Unix socket */ - conn->allow_ssl_try = false; - } if (conn->allow_ssl_try && !conn->wait_ssl_try && !conn->ssl_in_use) { @@ -2629,6 +2719,97 @@ keep_going: /* We will come back to here until there is #endif /* USE_SSL */ } + case CONNECTION_GSS_STARTUP: + { +#ifdef ENABLE_GSS + PostgresPollingStatusType pollres; + + /* + * If we haven't yet, get the postmaster's response to our + * negotiation packet + */ + if (conn->try_gss && !conn->gctx) + { + char gss_ok; + int rdresult = pqReadData(conn); + if (rdresult < 0) + /* pqReadData fills in error message */ + goto error_return; + else if (rdresult == 0) + /* caller failed to wait for data */ + return PGRES_POLLING_READING; + if (pqGetc(&gss_ok, conn) < 0) + /* shouldn't happen... */ + return PGRES_POLLING_READING; + + if (gss_ok == 'E') + { + /* + * Server failure of some sort. Assume it's a + * protocol version support failure, and let's see if + * we can't recover (if it's not, we'll get a better + * error message on retry). Server gets fussy if we + * don't hang up the socket, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + + /* mark byte consumed */ + conn->inStart = conn->inCursor; + + if (gss_ok == 'N') + { + /* Server doesn't want GSSAPI; fall back if we can */ + if (conn->gssmode[0] == 'r') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n")); + goto error_return; + } + + conn->try_gss = false; + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (gss_ok != 'G') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"), + gss_ok); + goto error_return; + } + } + + /* Begin or continue GSSAPI negotiation */ + pollres = pqsecure_open_gss(conn); + if (pollres == PGRES_POLLING_OK) + { + /* All set for startup packet */ + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (pollres == PGRES_POLLING_FAILED && + conn->gssmode[0] == 'p') + { + /* + * We failed, but we can retry on "prefer". Have to drop + * the current connection to do so, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + return pollres; +#else /* !ENABLE_GSS */ + /* unreachable */ + goto error_return; +#endif /* ENABLE_GSS */ + } + /* * Handle authentication exchange: wait for postmaster messages * and respond as necessary. @@ -2781,6 +2962,24 @@ keep_going: /* We will come back to here until there is /* OK, we read the message; mark data consumed */ conn->inStart = conn->inCursor; +#ifdef ENABLE_GSS + /* + * If gssmode is "prefer" and we're using GSSAPI, retry + * without it. + */ + if (conn->gssenc && conn->gssmode[0] == 'p') + { + OM_uint32 minor; + /* postmaster expects us to drop the connection */ + conn->try_gss = false; + conn->gssenc = false; + gss_delete_sec_context(&minor, &conn->gctx, NULL); + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } +#endif + #ifdef USE_SSL /* @@ -3361,6 +3560,11 @@ makeEmptyPGconn(void) conn->wait_ssl_try = false; conn->ssl_in_use = false; #endif +#ifdef ENABLE_GSS + conn->try_gss = true; + initPQExpBuffer(&conn->gbuf); + initPQExpBuffer(&conn->gwritebuf); +#endif /* * We try to send at least 8K at a time, which is the usual size of pipe @@ -3492,10 +3696,28 @@ freePGconn(PGconn *conn) free(conn->sslcompression); if (conn->requirepeer) free(conn->requirepeer); + if (conn->gssmode) + free(conn->gssmode); #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) if (conn->krbsrvname) free(conn->krbsrvname); #endif +#ifdef ENABLE_GSS + if (conn->gcred != GSS_C_NO_CREDENTIAL) + { + OM_uint32 minor; + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + } + if (conn->gctx) + { + OM_uint32 minor; + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = NULL; + } + termPQExpBuffer(&conn->gbuf); + termPQExpBuffer(&conn->gwritebuf); +#endif #if defined(ENABLE_GSS) && defined(ENABLE_SSPI) if (conn->gsslib) free(conn->gsslib); diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c new file mode 100644 index 0000000000..35e195b7a6 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.c @@ -0,0 +1,128 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.c + * The front-end (client) GSSAPI common code + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-gssapi-common.c + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "fe-gssapi-common.h" + +#include "libpq-int.h" +#include "pqexpbuffer.h" + +/* + * 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); +} + +/* + * Check if we can acquire credentials at all. + */ +bool +pg_GSS_have_ccache(gss_cred_id_t *cred_out) +{ + OM_uint32 major, minor; + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + + major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &cred, NULL, NULL); + if (GSS_ERROR(major)) + { + *cred_out = NULL; + return false; + } + *cred_out = cred; + return true; +} + +/* + * Try to load service name for a connection + */ +int +pg_GSS_load_servicename(PGconn *conn) +{ + OM_uint32 maj_stat, min_stat; + int maxlen; + gss_buffer_desc temp_gbuf; + char *host; + + if (conn->gtarg_nam != NULL) + /* Already taken care of - move along */ + return STATUS_OK; + + host = PQhost(conn); + if (!(host && host[0] != '\0')) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("host name must be specified\n")); + return STATUS_ERROR; + } + + /* + * Import service principal name so the proper ticket can be acquired by + * the GSSAPI system. + */ + maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; + temp_gbuf.value = (char *) malloc(maxlen); + if (!temp_gbuf.value) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return STATUS_ERROR; + } + snprintf(temp_gbuf.value, maxlen, "%s@%s", + conn->krbsrvname, host); + temp_gbuf.length = strlen(temp_gbuf.value); + + maj_stat = gss_import_name(&min_stat, &temp_gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); + free(temp_gbuf.value); + + if (maj_stat != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI name import error"), + conn, + maj_stat, min_stat); + return STATUS_ERROR; + } + return STATUS_OK; +} diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h new file mode 100644 index 0000000000..58811df9f1 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.h + * + * Definitions for GSSAPI common routines + * + * Portions Copyright (c) 1996-2018, 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 + +#include "libpq-fe.h" +#include "libpq-int.h" + +void pg_GSS_error(const char *mprefix, PGconn *conn, + OM_uint32 maj_stat, OM_uint32 min_stat); +bool pg_GSS_have_ccache(gss_cred_id_t *cred_out); +int pg_GSS_load_servicename(PGconn *conn); +#endif /* FE_GSSAPI_COMMON_H */ diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c new file mode 100644 index 0000000000..a71df69ff2 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,343 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-gssapi.c + * The front-end (client) encryption support for GSSAPI + * + * Portions Copyright (c) 2016-2018, 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" + +/* + * Require encryption support, as well as mutual authentication and + * tamperproofing measures. + */ +#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \ + GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG + +static ssize_t +send_buffered_data(PGconn *conn, size_t len) +{ + ssize_t ret = pqsecure_raw_write(conn, + conn->gwritebuf.data + conn->gwritecurs, + conn->gwritebuf.len - conn->gwritecurs); + if (ret < 0) + return ret; + + conn->gwritecurs += ret; + + if (conn->gwritecurs == conn->gwritebuf.len) + { + /* entire request has now been written */ + resetPQExpBuffer(&conn->gwritebuf); + conn->gwritecurs = 0; + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +pg_GSS_write(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + + if (conn->gwritebuf.len != 0) + return send_buffered_data(conn, len); + + /* encrypt the message */ + input.value = ptr; + input.length = len; + + 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); + goto cleanup; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI did not provide confidentiality\n")); + goto cleanup; + } + + /* 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + + ret = send_buffered_data(conn, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* check for available data */ + if (conn->gcursor < conn->gbuf.len) + { + /* clamp length */ + 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; + } + + /* reset buffer if all data has been read */ + if (conn->gcursor == conn->gbuf.len) + { + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + } + + return ret; +} + +static ssize_t +load_packet_length(PGconn *conn) +{ + ssize_t ret; + if (conn->gbuf.len < 4) + { + ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len); + 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 + conn->gbuf.len, + 4 - conn->gbuf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(PGconn *conn, size_t len) +{ + ssize_t ret; + + ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI encrypted packet length %ld too big\n"), + len); + return -1; + } + + /* load any missing parts of the packet */ + if (conn->gbuf.len - 4 < len) + { + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + len - conn->gbuf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < len) + { + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +ssize_t +pg_GSS_read(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = 0; + int conf = 0; + + /* handle any buffered data */ + if (conn->gcursor != 0) + return read_from_buffer(conn, ptr, len); + + /* load in the packet length, if not yet loaded */ + ret = load_packet_length(conn); + if (ret < 0) + return ret; + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0) + return ret; + + /* decrypt the packet */ + 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; + } + + /* load decrypted packet into our buffer */ + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + ret = enlargePQExpBuffer(&conn->gbuf, output.length); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI decrypted packet length %ld too big\n"), + output.length); + ret = -1; + goto cleanup; + } + + memcpy(conn->gbuf.data, output.value, output.length); + conn->gbuf.len = output.length; + conn->gbuf.data[conn->gbuf.len] = '\0'; + + ret = read_from_buffer(conn, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +PostgresPollingStatusType +pqsecure_open_gss(PGconn *conn) +{ + ssize_t ret; + OM_uint32 major, minor; + uint32 netlen; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + + /* Send out any data we might have */ + if (conn->gwritebuf.len != 0) + { + ret = send_buffered_data(conn, 1); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_WRITING; + else if (ret == 1) + /* sent all data */ + return PGRES_POLLING_READING; + return PGRES_POLLING_FAILED; + } + + /* Client sends first, and sending creates a context */ + if (conn->gctx) + { + /* Process any incoming data we might have */ + ret = load_packet_length(conn); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + if (conn->gbuf.data[0] == 'E') + { + /* + * We can taken an error here, and it's my least favorite thing. + * How long can error messages be? That's a good question, but + * backend's pg_gss_error() caps them at 256. Do a single read + * for that and call it a day. Cope with this here rather than in + * load_packet since expandPQExpBuffer will destroy any data in + * the buffer on failure. + */ + load_packet(conn, 256); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Server error: %s"), + conn->gbuf.data + 1); + return PGRES_POLLING_FAILED; + } + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + input.value = conn->gbuf.data + 4; + } + + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return PGRES_POLLING_FAILED; + + major = gss_init_sec_context(&minor, conn->gcred, &conn->gctx, + conn->gtarg_nam, GSS_C_NO_OID, + GSS_REQUIRED_FLAGS, 0, 0, &input, NULL, + &output, NULL, NULL); + resetPQExpBuffer(&conn->gbuf); + if (GSS_ERROR(major)) + { + pg_GSS_error(libpq_gettext("GSSAPI context establishment error"), + conn, major, minor); + return PGRES_POLLING_FAILED; + } + else if (output.length == 0) + { + /* + * We're done - hooray! Kind of gross, but we need to disable SSL + * here so that we don't accidentally tunnel one over the other. + */ +#ifdef USE_SSL + conn->allow_ssl_try = false; +#endif + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + conn->gssenc = true; + return PGRES_POLLING_OK; + } + + /* Queue the token for writing */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + gss_release_buffer(&minor, &output); + return PGRES_POLLING_WRITING; +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index f7dc249bf0..18155e4b5e 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) n = pgtls_read(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_read(conn, ptr, len); + } + else #endif { n = pqsecure_raw_read(conn, ptr, len); @@ -287,7 +294,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; @@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) n = pgtls_write(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_write(conn, ptr, len); + } + else #endif { n = pqsecure_raw_write(conn, ptr, len); diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index ed9c806861..bdd5a10cd8 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -65,8 +65,9 @@ typedef enum CONNECTION_NEEDED, /* Internal state: connect() needed */ CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable * connection. */ - CONNECTION_CONSUME /* Wait for any pending message and consume + CONNECTION_CONSUME, /* Wait for any pending message and consume * them. */ + CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */ } ConnStatusType; typedef enum diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 9a586ff25a..6cf3459a8c 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -22,6 +22,7 @@ /* We assume libpq-fe.h has already been included. */ #include "libpq-events.h" +#include "lib/stringinfo.h" #include <time.h> #ifndef WIN32 @@ -473,9 +474,19 @@ struct pg_conn #endif /* USE_OPENSSL */ #endif /* USE_SSL */ + char *gssmode; /* GSS mode (require,prefer,disable) */ #ifdef ENABLE_GSS gss_ctx_id_t gctx; /* GSS context */ gss_name_t gtarg_nam; /* GSS target name */ + + /* The following are encryption-only */ + 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 try_gss; /* GSS attempting permitted */ + bool gssenc; /* GSS encryption is usable */ + gss_cred_id_t gcred; /* GSS credential temp storage. */ #endif #ifdef ENABLE_SSPI @@ -651,7 +662,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); @@ -745,6 +756,23 @@ extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, int *names_examined, char **first_name); +/* === GSSAPI === */ + +#ifdef ENABLE_GSS + +/* + * Establish a GSSAPI-encrypted connection. + */ +extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn); + +/* + * Read and write functions for GSSAPI-encrypted connections, with internal + * buffering to handle nonblocking sockets. + */ +extern ssize_t pg_GSS_write(PGconn *conn, void *ptr, size_t len); +extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len); +#endif + /* === miscellaneous macros === */ /* diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 593732fd95..e152d7056b 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -183,13 +183,19 @@ sub mkvcbuild $postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); $postgres->FullExportDLL('postgres.lib'); - # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c - # if building without OpenSSL + # The OBJS scraper doesn't know about ifdefs, so remove + # be-secure-openssl.c if building without OpenSSL, and + # be-gssapi-common.c and be-secure-gssapi.c when building with GSSAPI. if (!$solution->{options}->{openssl}) { $postgres->RemoveFile('src/backend/libpq/be-secure-common.c'); $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + $postgres->RemoveFile('src/backend/libpq/be-secure-gssapi.c'); + } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', 'src/backend/snowball'); @@ -243,9 +249,10 @@ sub mkvcbuild 'src/interfaces/libpq/libpq.rc'); $libpq->AddReference($libpgport); - # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c - # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if - # building with OpenSSL. + # The OBJS scraper doesn't know about ifdefs, so remove + # fe-secure-openssl.c and sha2_openssl.c if building without OpenSSL, and + # remove sha2.c if building with OpenSSL. Also remove + # fe-gssapi-common.c and fe-secure-gssapi.c when building with GSSAPI. if (!$solution->{options}->{openssl}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c'); @@ -256,6 +263,11 @@ sub mkvcbuild { $libpq->RemoveFile('src/common/sha2.c'); } + if (!$solution->{options}->{gss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c'); + } my $libpqwalreceiver = $solution->AddProject('libpqwalreceiver', 'dll', '', -- 2.17.1
Attachment
On Tue, Jun 05, 2018 at 12:16:31PM +1200, Thomas Munro wrote: > On Sat, May 26, 2018 at 6:58 AM, Robbie Harwood <rharwood@redhat.com> wrote: > > Me and the bot are having an argument. This should green Linux but I > > dunno about Windows. > > BTW if you're looking for a way to try stuff out on Windows exactly > the way cfbot does it without posting a new patch to the mailing list, > I put some instructions here: > > https://wiki.postgresql.org/wiki/Continuous_Integration > > The .patch there could certainly be improved (ideally, I think, it'd > run the whole build farm animal script) but it's a start. Cool! Is there any reason that your patch for Travis and AppVeyor integration is not just committed to master? Is there a way to get my CF entry building in cfbot, or will it get there when it gets there? I know I can just apply your patch, push to my fork, and have Travis and AppVeyor build it. And I just might, but cfbot is a neato service! Nico --
On Fri, Jun 8, 2018 at 9:00 AM, Nico Williams <nico@cryptonector.com> wrote: > On Tue, Jun 05, 2018 at 12:16:31PM +1200, Thomas Munro wrote: >> On Sat, May 26, 2018 at 6:58 AM, Robbie Harwood <rharwood@redhat.com> wrote: >> > Me and the bot are having an argument. This should green Linux but I >> > dunno about Windows. >> >> BTW if you're looking for a way to try stuff out on Windows exactly >> the way cfbot does it without posting a new patch to the mailing list, >> I put some instructions here: >> >> https://wiki.postgresql.org/wiki/Continuous_Integration >> >> The .patch there could certainly be improved (ideally, I think, it'd >> run the whole build farm animal script) but it's a start. > > Cool! Is there any reason that your patch for Travis and AppVeyor > integration is not just committed to master? I think that's a good idea and I know that some others are in favour. The AppVeyor one is not good enough to propose just yet: it's cargo-culted and skips a test that doesn't pass and only runs the basic check tests (not contribs, isolation, TAP etc). Fortunately Andrew Dunstan has recently figured out how to run the official build farm script inside AppVeyor[1], and it looks like we might be close to figuring out that one test that doesn't work. That makes me wonder if we should get the Travis version to use the build farm scripts too. If we can get all of that sorted out, then yes, I would like to propose that we stick the .XXX.yml files in the tree. Another thing not yet explored is Travis's macOS build support. Someone might argue that we shouldn't depend on particular external services, or that there are other CI services out there and we should use those too/instead for some reason, or that we don't want all that junk at top level in the tree. But it seems to me that as long as they're dot prefixed files, we could carry control files for any number of CI services without upsetting anyone. Having them in the tree would allow anyone who has a publicly accessible git repo (bitbucket, GitHub, ...) to go to any CI service that interests them and enable it with a couple of clicks. Then cfbot would still need to create new branches automatically (that is fundamentally what it does: converts patches on the mailing list into branches on GitHub), but it wouldn't need to add those control files anymore, just the submitted patches. > Is there a way to get my CF entry building in cfbot, or will it get > there when it gets there? Urgh, due to a bug in my new rate limiting logic it stopped pushing new branches for a day or two. Fixed, and I see it's just picked up your submission #1319. Usually it picks things up within minutes (it rescans threads whenever the 'last mail' date changes on the Commitfest web page), and then also rechecks each submission every couple of days. In a nice demonstration of the complexity of these systems, I see that the build for your submission on Travis failed because apt couldn't update its package index because repo.mongodb.org's key has expired. Other recent builds are OK so that seems to be a weird transient failure; possibly out of data in some cache, or some fraction of their repo server farm hasn't been updated yet or ... whatever. Bleugh. > I know I can just apply your patch, push to my fork, and have Travis and > AppVeyor build it. And I just might, but cfbot is a neato service! Thanks. The next step is to show cfbot's results in the Commitfest app, and Magnus and I have started working on that. I gave a talk about all this at PGCon last week, and the slides are up[2] in case anyone is interested: [1] https://www.postgresql.org/message-id/flat/0f3d44a1-1ac5-599c-3e15-16d058d54e9a%402ndQuadrant.com [2] https://www.pgcon.org/2018/schedule/events/1234.en.html -- Thomas Munro http://www.enterprisedb.com
On Fri, Jun 08, 2018 at 10:11:52AM +1200, Thomas Munro wrote: > On Fri, Jun 8, 2018 at 9:00 AM, Nico Williams <nico@cryptonector.com> wrote: > > Cool! Is there any reason that your patch for Travis and AppVeyor > > integration is not just committed to master? > > I think that's a good idea and I know that some others are in favour. > The AppVeyor one is not good enough to propose just yet: it's > cargo-culted and skips a test that doesn't pass and only runs the > basic check tests (not contribs, isolation, TAP etc). Fortunately > Andrew Dunstan has recently figured out how to run the official build > farm script inside AppVeyor[1], and it looks like we might be close to > figuring out that one test that doesn't work. That makes me wonder if > we should get the Travis version to use the build farm scripts too. > If we can get all of that sorted out, then yes, I would like to > propose that we stick the .XXX.yml files in the tree. Another thing > not yet explored is Travis's macOS build support. I use AppVeyor for Heimdal and for jq... Maybe I can help out. As for Travis' OS X support... the problem there is that their build farm is very small, so using theirs means waiting and waiting. > Someone might argue that we shouldn't depend on particular external > services, or that there are other CI services out there and we should > use those too/instead for some reason, or that we don't want all that > junk at top level in the tree. But it seems to me that as long as > they're dot prefixed files, we could carry control files for any > number of CI services without upsetting anyone. Having them in the > tree would allow anyone who has a publicly accessible git repo > (bitbucket, GitHub, ...) to go to any CI service that interests them > and enable it with a couple of clicks. Carrying the .yml files causes no harm beyond dependence, but that's a nice problem to have when the alternative is to not have a CI at all. > Then cfbot would still need to create new branches automatically (that > is fundamentally what it does: converts patches on the mailing list > into branches on GitHub), but it wouldn't need to add those control > files anymore, just the submitted patches. You wouldn't need it to. Instead the CF page could let submitters link their CI status pages/buttons... > > Is there a way to get my CF entry building in cfbot, or will it get > > there when it gets there? > > Urgh, due to a bug in my new rate limiting logic it stopped pushing > new branches for a day or two. Fixed, and I see it's just picked up > your submission #1319. Usually it picks things up within minutes (it > rescans threads whenever the 'last mail' date changes on the > Commitfest web page), and then also rechecks each submission every > couple of days. Thanks! > In a nice demonstration of the complexity of these systems, I see that > the build for your submission on Travis failed because apt couldn't > update its package index because repo.mongodb.org's key has expired. > Other recent builds are OK so that seems to be a weird transient > failure; possibly out of data in some cache, or some fraction of their > repo server farm hasn't been updated yet or ... whatever. Bleugh. Oof. > > I know I can just apply your patch, push to my fork, and have Travis and > > AppVeyor build it. And I just might, but cfbot is a neato service! > > Thanks. The next step is to show cfbot's results in the Commitfest > app, and Magnus and I have started working on that. I gave a talk > about all this at PGCon last week, and the slides are up[2] in case > anyone is interested: OK. I think that will be a huge improvement. I find CF to be fantastic as it is, but this will make it even better. Thanks, Nico --
On Thu, Jun 7, 2018 at 6:11 PM, Thomas Munro <thomas.munro@enterprisedb.com> wrote: >> Cool! Is there any reason that your patch for Travis and AppVeyor >> integration is not just committed to master? > > I think that's a good idea and I know that some others are in favour. One problem is that was discussed at PGCon it commits us to one particular build configuration i.e. one set of --with-whatever options to configure. It's not bad to provide a reasonable set of defaults, but it means that patches which are best tested with some other set of values will have to modify the file (I guess?). Patches that need to be tested with multiple sets of flags are ... maybe just out of luck? I really don't understand the notion of putting the build script inside the source tree. It's all fine if there's One Way To Do It but often TMTOWTDII. If the build configurations are described outside the source tree then you can have as many of them as you need. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
On Mon, Jun 11, 2018 at 09:27:17AM -0400, Robert Haas wrote: > On Thu, Jun 7, 2018 at 6:11 PM, Thomas Munro > <thomas.munro@enterprisedb.com> wrote: > >> Cool! Is there any reason that your patch for Travis and AppVeyor > >> integration is not just committed to master? > > > > I think that's a good idea and I know that some others are in favour. > > One problem is that was discussed at PGCon it commits us to one > particular build configuration i.e. one set of --with-whatever options > to configure. It's not bad to provide a reasonable set of defaults, > but it means that patches which are best tested with some other set of > values will have to modify the file (I guess?). Patches that need to > be tested with multiple sets of flags are ... maybe just out of luck? Hmm, that's not really true. You can have a build and test matrix with more than one row in it. For example, look at: https://travis-ci.org/heimdal/heimdal You'll see that Heimdal's Travis-CI integration has four builds: - Linux w/ GCC - Linux w/ Clang - OS X w/ Clang - Linux code coverage w/ GCC We could easily add more options for Heimdal, if we felt we needed to build and test more with Travis-CI. Appveyor also has matrix support (though I'm not using it in Heimdal's Appveyor-CI integration). Now, of course if we had a very large set of configurations to test, things might get slow, and the CIs might find it abusive. It would be best to use a maximal build configuration and go from there. So, for example, two configurations, one with and w/o JIT, but with all the optional libraries (GSS, LDAP, ICU, Perl, Python, ...), and with two different compilers (GCC and Clang, with Clang only for the JIT), plus one OS X build (with JIT), and so on: - Linux w/ GCC - Linux w/ Clang ( JIT) - Linux w/ Clang (no JIT) - Linux code coverage - OS X w/ Clang ( JIT) and similarly for Windows on Appveyor. > I really don't understand the notion of putting the build script > inside the source tree. It's all fine if there's One Way To Do It but > often TMTOWTDII. If the build configurations are described outside > the source tree then you can have as many of them as you need. Well, all the free CIs like Travis and Appveyor do it this way. You don't have to *use* it just because the .yml files are in the source tree. But you have to have the .yml files in the source tree in order to use these CIs. It'd be nice to be able to point somewhere else for them, but whatever, that's not something we get much choice in at this time. The .yml files are unobstrusive anyways, and it's handy to have them in-tree anyways. It also makes it easier to do things like: - get build passing/failing buttons on wiki / build status pages - make sure that the .yml files stay up to date as the source tree gets changed It also makes it somewhat easier to get hooked on github and such, but a bit of discipline will make that a non-issue. Nico --
On 06/11/2018 01:13 PM, Nico Williams wrote: > > Well, all the free CIs like Travis and Appveyor do it this way. You > don't have to *use* it just because the .yml files are in the source > tree. But you have to have the .yml files in the source tree in order > to use these CIs. It'd be nice to be able to point somewhere else for > them, but whatever, that's not something we get much choice in at this > time. > That's not true, at least for Appveyor (can't speak about travis - I have no first hand experience). For appveyor, you can supply a custom appveyor.yml file, which can be a complete URL. In fact, if you use a plain git source as opposed to one of the managed git services it supports, you have to do it that way - it ignores an appveyor.yml in your repo. I found this out the very hard way over the last few days, and they very kindly don't warn you at all about this. cheers andrew -- Andrew Dunstan https://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Hi, Nico was kind enough to provide me with some code review. This should those concerns (clarify short-read behavior and fixing error checking on GSS functions). Thanks, --Robbie From 20b9f7d5cd9a35e7210ccc309bada789411b6cdb Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Thu, 10 May 2018 16:12:03 -0400 Subject: [PATCH] libpq GSSAPI encryption support On both the frontend and backend, prepare for GSSAPI encryption support by moving common code for error handling into a separate file. Fix a TODO for handling multiple status messages in the process. Eliminate the OIDs, which have not been needed for some time. Add frontend and backend encryption support functions. Keep the context initiation for authentication-only separate on both the frontend and backend in order to avoid concerns about changing the requested flags to include encryption support. In postmaster, pull GSSAPI authorization checking into a shared function. Also share the initiator name between the encryption and non-encryption codepaths. Modify pqsecure_write() to take a non-const pointer. The pointer will not be modified, but this change (or a const-discarding cast, or a malloc()+memcpy()) is necessary for GSSAPI due to const/struct interactions in C. For HBA, add "hostgss" and "hostnogss" entries that behave similarly to their SSL counterparts. "hostgss" requires either "gss", "trust", or "reject" for its authentication. Simiarly, add a "gssmode" parameter to libpq. Supported values are "disable", "require", and "prefer". Notably, negotiation will only be attempted if credentials can be acquired. Move credential acquisition into its own function to support this behavior. Finally, add documentation for everything new, and update existing documentation on connection security. Thanks to Michael Paquier for the Windows fixes. --- doc/src/sgml/client-auth.sgml | 75 ++++-- doc/src/sgml/libpq.sgml | 57 +++- doc/src/sgml/runtime.sgml | 77 +++++- src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 103 +++---- src/backend/libpq/be-gssapi-common.c | 64 +++++ src/backend/libpq/be-gssapi-common.h | 26 ++ src/backend/libpq/be-secure-gssapi.c | 321 ++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 ++ src/backend/libpq/hba.c | 51 +++- src/backend/postmaster/pgstat.c | 3 + src/backend/postmaster/postmaster.c | 64 ++++- src/include/libpq/hba.h | 4 +- src/include/libpq/libpq-be.h | 11 +- src/include/libpq/libpq.h | 3 + src/include/libpq/pqcomm.h | 5 +- src/include/pgstat.h | 3 +- src/interfaces/libpq/Makefile | 4 + src/interfaces/libpq/fe-auth.c | 90 +------ src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++- src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++ src/interfaces/libpq/fe-gssapi-common.h | 23 ++ src/interfaces/libpq/fe-secure-gssapi.c | 345 ++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-fe.h | 3 +- src/interfaces/libpq/libpq-int.h | 30 ++- src/tools/msvc/Mkvcbuild.pm | 22 +- 27 files changed, 1578 insertions(+), 202 deletions(-) create mode 100644 src/backend/libpq/be-gssapi-common.c create mode 100644 src/backend/libpq/be-gssapi-common.h create mode 100644 src/backend/libpq/be-secure-gssapi.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.h 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 656d5f9417..38cf32e3be 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> </synopsis> The meaning of the fields is as follows: @@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> This record matches connection attempts made using TCP/IP. - <literal>host</literal> records match either + <literal>host</literal> records match <acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection - attempts. + attempts as well as <acronym>GSSAPI</acronym> or + non-<acronym>GSSAPI</acronym> connection attempts. </para> <note> <para> @@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><literal>hostgss</literal></term> + <listitem> + <para> + This record matches connection attempts made using TCP/IP, + but only when the connection is made with <acronym>GSSAPI</acronym> + encryption. + </para> + + <para> + To make use of this option the server must be built with + <acronym>GSSAPI</acronym> support. Otherwise, + the <literal>hostgss</literal> record is ignored except for logging a + warning that it cannot match any connections. + </para> + + <para> + Note that the only supported <xref linkend="auth-methods"/> for use + with <acronym>GSSAPI</acronym> encryption + are <literal>gss</literal>, <literal>reject</literal>, + and <literal>trust</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>hostnogss</literal></term> + <listitem> + <para> + This record type has the opposite behavior of <literal>hostgss</literal>; + it only matches connection attempts made over + TCP/IP that do not use <acronym>GSSAPI</acronym>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable>database</replaceable></term> <listitem> @@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> Use GSSAPI to authenticate the user. This is only - available for TCP/IP connections. See <xref - linkend="gssapi-auth"/> for details. + available for TCP/IP connections . See <xref + linkend="gssapi-auth"/> for details. It can be used in conjunction + with GSSAPI encryption. </para> </listitem> </varlistentry> @@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256 host all mike .example.com md5 host all all .example.com scram-sha-256 -# In the absence of preceding "host" lines, these two lines will +# In the absence of preceding "host" lines, these three lines will # reject all connections from 192.168.54.1 (since that entry will be -# matched first), but allow GSSAPI connections from anywhere else -# on the Internet. The zero mask causes no bits of the host IP +# matched first), but allow GSSAPI-encrypted connections from anywhere else +# on the Internet. Unencrypted GSSAPI connections are allowed from +# 192.168.12.10 only. The zero mask causes no bits of the host IP # address to be considered, so it matches any host. # # TYPE DATABASE USER ADDRESS METHOD host all all 192.168.54.1/32 reject -host all all 0.0.0.0/0 gss +hostgss all all 0.0.0.0/0 gss +host all all 192.168.12.10/32 gss # Allow users from 192.168.x.x hosts to connect to any database, if # they pass the ident check. If, for example, ident says the user is @@ -1051,13 +1093,16 @@ omicron bryanh guest1 <para> <productname>GSSAPI</productname> is an industry-standard protocol for secure authentication defined in RFC 2743. - <productname>PostgreSQL</productname> supports - <productname>GSSAPI</productname> with <productname>Kerberos</productname> - authentication according to RFC 1964. <productname>GSSAPI</productname> - 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. + + <productname>PostgreSQL</productname> + supports <productname>GSSAPI</productname> for use as either an encrypted, + authenticated layer, or as authentication + only. <productname>GSSAPI</productname> provides automatic authentication + (single sign-on) for systems that support it. The authentication itself is + secure. If <productname>GSSAPI</productname> encryption + (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are + used, the data sent along the database connection will be encrypted; + otherwise, it will not. </para> <para> diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 800e68a19e..73d233dc16 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1320,6 +1320,61 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode"> + <term><literal>gssmode</literal></term> + <listitem> + <para> + This option determines whether or with what priority a secure + <acronym>GSS</acronym> TCP/IP connection will be negotiated with the + server. There are three modes: + + <variablelist> + <varlistentry> + <term><literal>disable</literal></term> + <listitem> + <para> + only try a non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>prefer</literal> (default)</term> + <listitem> + <para> + if there are <acronym>GSSAPI</acronym> credentials present (i.e., + in a credentials cache), first try + a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or + there are no credentials, try a + non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>require</literal></term> + <listitem> + <para> + only try a <acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + <literal>gssmode</literal> is ignored for Unix domain socket + communication. If <productname>PostgreSQL</productname> is compiled + without GSSAPI support, using the <literal>require</literal> option + will cause an error, while <literal>prefer</literal> will be accepted + but <application>libpq</application> will not actually attempt + a <acronym>GSSAPI</acronym>-encrypted + connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with + libpq</secondary></indexterm> + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-sslmode" xreflabel="sslmode"> <term><literal>sslmode</literal></term> <listitem> @@ -7872,7 +7927,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*) </para> <para> - For a connection to be known secure, SSL usage must be configured + For a connection to be known SSL-secured, SSL usage must be configured on <emphasis>both the client and the server</emphasis> before the connection is made. If it is only configured on the server, the client may end up sending sensitive information (e.g. passwords) before diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 330e38a29e..06bf56153c 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 </para> <para> - To prevent spoofing on TCP connections, the best solution is to use - SSL certificates and make sure that clients check the server's certificate. - To do that, the server + To prevent spoofing on TCP connections, either use + SSL certificates and make sure that clients check the server's certificate, + or use GSSAPI encryption (or both, if they're on separate connections). + </para> + + <para> + To prevent spoofing with SSL, the server must be configured to accept only <literal>hostssl</literal> connections (<xref linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files (<xref linkend="ssl-tcp"/>). The TCP client must connect using @@ -2018,6 +2022,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 <literal>verify-full</literal> and have the appropriate root certificate file installed (<xref linkend="libq-ssl-certificates"/>). </para> + + <para> + To prevent spoofing with GSSAPI, the server must be configured to accept + only <literal>hostgss</literal> connections + (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal> + authentication with them. The TCP client must connect + using <literal>gssmode=require</literal>. + </para> </sect1> <sect1 id="encryption-options"> @@ -2114,8 +2126,24 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 which hosts can use non-encrypted connections (<literal>host</literal>) and which require SSL-encrypted connections (<literal>hostssl</literal>). Also, clients can specify that they - connect to servers only via SSL. <application>Stunnel</application> or - <application>SSH</application> can also be used to encrypt transmissions. + connect to servers only via SSL. + </para> + + <para> + GSSAPI-encrypted connections encrypt all data sent across the network, + including queries and data returned. (No password is sent across the + network.) The <filename>pg_hba.conf</filename> file allows + administrators to specify which hosts can use non-encrypted connections + (<literal>host</literal>) and which require GSSAPI-encrypted connections + (<literal>hostgss</literal>). Also, clients can specify that they + connect to servers only on GSSAPI-encrypted connections + (<literal>gssmode=require</literal>). + </para> + + <para> + <application>Stunnel</application> or + <application>SSH</application> can also be used to encrypt + transmissions. </para> </listitem> </varlistentry> @@ -2505,6 +2533,45 @@ openssl x509 -req -in server.csr -text -days 365 \ </sect1> + <sect1 id="gssapi-enc"> + <title>Secure TCP/IP Connections with GSSAPI encryption</title> + + <indexterm zone="gssapi-enc"> + <primary>gssapi</primary> + </indexterm> + + <para> + <productname>PostgreSQL</productname> also has native support for + using <acronym>GSSAPI</acronym> to encrypt client/server communications for + increased security. Support requires that a <acronym>GSSAPI</acronym> + implementation (such as MIT krb5) is installed on both client and server + systems, and that support in <productname>PostgreSQL</productname> is + enabled at build time (see <xref linkend="installation"/>). + </para> + + <sect2 id="gssapi-setup"> + <title>Basic Setup</title> + + <para> + The <productname>PostgreSQL</productname> server will listen for both + normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP + port, and will negotiate with any connecting client on whether to + use <acronym>GSSAPI</acronym> for encryption (and for authentication). By + default, this decision is up to the client (which means it can be + downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about + setting up the server to require the use of <acronym>GSSAPI</acronym> for + some or all conections. + </para> + + <para> + Other than configuration of the negotiation + behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond + that which necessary for GSSAPI authentication. (For more information on + configuring that, see <xref linkend="gssapi-auth"/>.) + </para> + </sect2> + </sect1> + <sect1 id="ssh-tunnels"> <title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title> diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 3dbec23e30..47efef0682 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 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 3014b17a7c..6e4cd66a90 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -173,12 +173,9 @@ bool pg_krb_caseins_users; *---------------------------------------------------------------- */ #ifdef ENABLE_GSS -#if defined(HAVE_GSSAPI_H) -#include <gssapi.h> -#else -#include <gssapi/gssapi.h> -#endif +#include "be-gssapi-common.h" +static int pg_GSS_checkauth(Port *port); static int pg_GSS_recvauth(Port *port); #endif /* ENABLE_GSS */ @@ -384,6 +381,17 @@ ClientAuthentication(Port *port) errmsg("connection requires a valid client certificate"))); } +#ifdef ENABLE_GSS + if (port->gss->enc && port->hba->auth_method != uaReject && + port->hba->auth_method != uaImplicitReject && + port->hba->auth_method != uaTrust && + port->hba->auth_method != uaGSS) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("GSSAPI encryption cannot be combined with non-GSSAPI authentication"))); + } +#endif + /* * Now proceed to do the actual authentication check */ @@ -524,8 +532,13 @@ ClientAuthentication(Port *port) case uaGSS: #ifdef ENABLE_GSS - sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); - status = pg_GSS_recvauth(port); + if (port->gss->enc) + status = pg_GSS_checkauth(port); + else + { + sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); + status = pg_GSS_recvauth(port); + } #else Assert(false); #endif @@ -1044,64 +1057,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) *---------------------------------------------------------------- */ #ifdef ENABLE_GSS - -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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, const 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) { @@ -1110,7 +1065,6 @@ pg_GSS_recvauth(Port *port) lmin_s, gflags; int mtype; - int ret; StringInfoData buf; gss_buffer_desc gbuf; @@ -1263,10 +1217,17 @@ pg_GSS_recvauth(Port *port) */ gss_release_cred(&min_stat, &port->gss->cred); } + return pg_GSS_checkauth(port); +} + +static int +pg_GSS_checkauth(Port *port) +{ + int ret; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc gbuf; /* - * GSS_S_COMPLETE indicates that authentication is now complete. - * * Get the name of the user that authenticated, and compare it to the pg * username that was specified for the connection. */ @@ -1308,7 +1269,7 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI realm (%s) and configured realm (%s) don't match", cp, port->hba->krb_realm); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } } @@ -1318,14 +1279,14 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI did not return realm but realm matching was requested"); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, pg_krb_caseins_users); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return ret; } diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c new file mode 100644 index 0000000000..78d9f5d325 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,64 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.c + * Common code for GSSAPI authentication and encryption + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/libpq/be-gssapi-common.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +static void +pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type) +{ + gss_buffer_desc gmsg; + size_t i = 0; + OM_uint32 lmin_s, msg_ctx = 0; + + gmsg.value = NULL; + gmsg.length = 0; + + do + { + gss_display_status(&lmin_s, stat, type, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(s + i, gmsg.value, len - i); + i += gmsg.length; + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < len); + + if (msg_ctx || i == len) + ereport(WARNING, + (errmsg_internal("incomplete GSS error report"))); +} + +void +pg_GSS_error(int severity, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat) +{ + char msg_major[128], msg_minor[128]; + + /* Fetch major status message */ + pg_GSS_error_int(msg_major, sizeof(msg_major), maj_stat, GSS_C_GSS_CODE); + + /* Fetch mechanism minor status message */ + pg_GSS_error_int(msg_minor, sizeof(msg_minor), min_stat, GSS_C_MECH_CODE); + + /* + * 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/backend/libpq/be-gssapi-common.h b/src/backend/libpq/be-gssapi-common.h new file mode 100644 index 0000000000..c020051d2e --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.h + * Definitions for GSSAPI authentication and encryption handling + * + * Portions Copyright (c) 1996-2018, 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, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat); + +#endif /* BE_GSSAPI_COMMON_H */ diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c new file mode 100644 index 0000000000..2b70db2232 --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,321 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gssapi.c + * GSSAPI encryption support + * + * Portions Copyright (c) 2018-2018, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +#include "libpq/auth.h" +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "pgstat.h" + +#include <unistd.h> + +static ssize_t +send_buffered_data(Port *port, size_t len) +{ + ssize_t ret = secure_raw_write( + port, + port->gss->writebuf.data + port->gss->writebuf.cursor, + port->gss->writebuf.len - port->gss->writebuf.cursor); + if (ret < 0) + return ret; + + /* update and possibly clear buffer state */ + port->gss->writebuf.cursor += ret; + + if (port->gss->writebuf.cursor == port->gss->writebuf.len) + { + /* entire request has now been written */ + resetStringInfo(&port->gss->writebuf); + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +be_gssapi_write(Port *port, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + pg_gssinfo *gss = port->gss; + + if (gss->writebuf.len != 0) + return send_buffered_data(port, len); + + /* encrypt the message */ + output.value = NULL; + output.length = 0; + input.value = ptr; + input.length = len; + + major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT, + &input, &conf, &output); + if (major != GSS_S_COMPLETE) + { + pg_GSS_error(ERROR, gettext_noop("GSSAPI wrap error"), major, minor); + goto cleanup; + } else if (conf == 0) + { + ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality"))); + goto cleanup; + } + + /* 4 network-order length bytes, then payload */ + netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, output.value, output.length); + + ret = send_buffered_data(port, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* load up any available data */ + if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len) + { + /* clamp length */ + if (len > gss->buf.len - gss->buf.cursor) + len = gss->buf.len - gss->buf.cursor; + + memcpy(ptr, gss->buf.data + gss->buf.cursor, len); + gss->buf.cursor += len; + ret = len; + } + + /* reset buffer if all data has been read */ + if (gss->buf.cursor == gss->buf.len) + resetStringInfo(&gss->buf); + + return ret; +} + +static ssize_t +load_packetlen(Port *port) +{ + pg_gssinfo *gss = port->gss; + ssize_t ret; + + if (gss->buf.len < 4) + { + enlargeStringInfo(&gss->buf, 4 - gss->buf.len); + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + 4 - gss->buf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len < 4) + { + /* partial read from secure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(Port *port, size_t len) +{ + ssize_t ret; + pg_gssinfo *gss = port->gss; + + enlargeStringInfo(&gss->buf, len - gss->buf.len + 4); + + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + len - gss->buf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len - 4 < len) + { + /* partial read from secure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + return 0; +} + +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; + pg_gssinfo *gss = port->gss; + + if (gss->buf.cursor > 0) + return read_from_buffer(gss, ptr, len); + + /* load length if not present */ + ret = load_packetlen(port); + if (ret != 0) + return ret; + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + return ret; + + /* decrypt the packet */ + output.value = NULL; + output.length = 0; + input.value = gss->buf.data + 4; + + major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf, NULL); + if (major != GSS_S_COMPLETE) + { + 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; + } + + /* put the decrypted packet in the buffer */ + resetStringInfo(&gss->buf); + enlargeStringInfo(&gss->buf, output.length); + + memcpy(gss->buf.data, output.value, output.length); + gss->buf.len = output.length; + gss->buf.data[gss->buf.len] = '\0'; + + ret = read_from_buffer(gss, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +ssize_t +secure_open_gssapi(Port *port) +{ + pg_gssinfo *gss = port->gss; + bool complete_next = false; + + /* + * Use the configured keytab, if there is one. Unfortunately, Heimdal + * doesn't support the cred store extensions, so use the env var. + */ + if (pg_krb_server_keyfile != NULL && strlen(pg_krb_server_keyfile) > 0) + setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1); + + while (true) + { + OM_uint32 major, minor; + size_t ret; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + + /* Handle any outgoing data */ + if (gss->writebuf.len != 0) + { + ret = send_buffered_data(port, 1); + if (ret != 1) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + } + + if (complete_next) + break; + + /* Load incoming data */ + ret = load_packetlen(port); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + input.value = gss->buf.data + 4; + + /* Process incoming data. (The client sends first.) */ + major = gss_accept_sec_context(&minor, &port->gss->ctx, + GSS_C_NO_CREDENTIAL, &input, + GSS_C_NO_CHANNEL_BINDINGS, + &port->gss->name, NULL, &output, NULL, + NULL, NULL); + resetStringInfo(&gss->buf); + if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, gettext_noop("GSSAPI context error"), + major, minor); + gss_release_buffer(&minor, &output); + return -1; + } + else if (!(major & GSS_S_CONTINUE_NEEDED)) + { + /* + * rfc2744 technically permits context negotiation to be complete + * both with and without a packet to be sent. + */ + complete_next = true; + } + + if (output.length != 0) + { + /* Queue packet for writing */ + uint32 netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, + output.value, output.length); + gss_release_buffer(&minor, &output); + continue; + } + + /* We're done - woohoo! */ + break; + } + port->gss->enc = true; + return 0; +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index edfe2c0751..7dd1cf7090 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -152,6 +152,14 @@ retry: n = be_tls_read(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_read(port, ptr, len); + waitfor = WL_SOCKET_READABLE; + } + else #endif { n = secure_raw_read(port, ptr, len); @@ -255,6 +263,14 @@ retry: n = be_tls_write(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_write(port, ptr, len); + waitfor = WL_SOCKET_WRITEABLE; + } + else #endif { n = secure_raw_write(port, ptr, len); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index acf625e4ec..9a1aa4e5cc 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -994,7 +994,9 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) } else if (strcmp(token->string, "host") == 0 || strcmp(token->string, "hostssl") == 0 || - strcmp(token->string, "hostnossl") == 0) + strcmp(token->string, "hostnossl") == 0 || + strcmp(token->string, "hostgss") == 0 || + strcmp(token->string, "hostnogss") == 0) { if (token->string[4] == 's') /* "hostssl" */ @@ -1022,10 +1024,23 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "hostssl record cannot match because SSL is not supported by this build"; #endif } - else if (token->string[4] == 'n') /* "hostnossl" */ + else if (token->string[4] == 'g') /* "hostgss" */ { - parsedline->conntype = ctHostNoSSL; + parsedline->conntype = ctHostGSS; +#ifndef ENABLE_GSS + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("hostgss record cannot match because GSSAPI is not supported by this build"), + errhint("Compile with --with-gssapi to use GSSAPI connections."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "hostgss record cannot match because GSSAPI is not supported by this build"; +#endif } + else if (token->string[4] == 'n' && token->string[6] == 's') + parsedline->conntype = ctHostNoSSL; + else if (token->string[4] == 'n' && token->string[6] == 'g') + parsedline->conntype = ctHostNoGSS; else { /* "host" */ @@ -1404,6 +1419,19 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "gssapi authentication is not supported on local sockets"; return NULL; } + if (parsedline->conntype == ctHostGSS && + parsedline->auth_method != uaGSS && + parsedline->auth_method != uaReject && + parsedline->auth_method != uaTrust) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("GSSAPI encryption only supports gss, trust, or reject authentication"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "GSSAPI encryption only supports gss, trust, or reject authenticaion"; + return NULL; + } if (parsedline->conntype != ctLocal && parsedline->auth_method == uaPeer) @@ -2060,6 +2088,17 @@ check_hba(hbaPort *port) continue; } + /* Check GSSAPI state */ +#ifdef ENABLE_GSS + if (port->gss->enc && hba->conntype == ctHostNoGSS) + continue; + else if (!port->gss->enc && hba->conntype == ctHostGSS) + continue; +#else + if (hba->conntype == ctHostGSS) + continue; +#endif + /* Check IP address */ switch (hba->ip_cmp_method) { @@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, case ctHostNoSSL: typestr = "hostnossl"; break; + case ctHostGSS: + typestr = "hostgss"; + break; + case ctHostNoGSS: + typestr = "hostnogss"; + break; } if (typestr) values[index++] = CStringGetTextDatum(typestr); diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 084573e77c..72ce147308 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -3562,6 +3562,9 @@ pgstat_get_wait_client(WaitEventClient w) case WAIT_EVENT_WAL_SENDER_WRITE_DATA: event_name = "WalSenderWriteData"; break; + case WAIT_EVENT_GSS_OPEN_SERVER: + event_name = "GSSOpenServer"; + break; /* no default case, so that compiler will warn */ } diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a4b53b33cd..87dbb33a88 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -1894,7 +1894,7 @@ initMasks(fd_set *rmask) * if we detect a communications failure.) */ static int -ProcessStartupPacket(Port *port, bool SSLdone) +ProcessStartupPacket(Port *port, bool secure_done) { int32 len; void *buf; @@ -1905,11 +1905,11 @@ ProcessStartupPacket(Port *port, bool SSLdone) if (pq_getbytes((char *) &len, 4) == EOF) { /* - * EOF after SSLdone probably means the client didn't like our + * EOF after secure_done probably means the client didn't like our * response to NEGOTIATE_SSL_CODE. That's not an error condition, so * don't clutter the log with a complaint. */ - if (!SSLdone) + if (!secure_done) ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("incomplete startup packet"))); @@ -1961,7 +1961,7 @@ ProcessStartupPacket(Port *port, bool SSLdone) return STATUS_ERROR; } - if (proto == NEGOTIATE_SSL_CODE && !SSLdone) + if (proto == NEGOTIATE_SSL_CODE && !secure_done) { char SSLok; @@ -1994,6 +1994,32 @@ retry1: /* but not another SSL negotiation request */ return ProcessStartupPacket(port, true); } + else if (proto == NEGOTIATE_GSS_CODE && !secure_done) + { + char GSSok = 'N'; +#ifdef ENABLE_GSS + /* No GSSAPI encryption when on Unix socket */ + if (!IS_AF_UNIX(port->laddr.addr.ss_family)) + GSSok = 'G'; +#endif + + while (send(port->sock, &GSSok, 1, 0) != 1) + { + if (errno == EINTR) + continue; + ereport(COMMERROR, + (errcode_for_socket_access(), + errmsg("failed to send GSSAPI negotiation response: %m)"))); + return STATUS_ERROR; /* close the connection */ + } + +#ifdef ENABLE_GSS + if (GSSok == 'G' && secure_open_gssapi(port) == -1) + return STATUS_ERROR; +#endif + /* Won't ever see more than one negotiation request */ + return ProcessStartupPacket(port, true); + } /* Could add additional special packet types here */ @@ -2413,6 +2439,17 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif #endif return port; @@ -2429,7 +2466,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); } @@ -4761,6 +4806,17 @@ SubPostmasterMain(int argc, char *argv[]) (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif /* * If appropriate, physically re-attach to shared memory segment. We want diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 5f68f4c666..830ddaa25a 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -55,7 +55,9 @@ typedef enum ConnType ctLocal, ctHost, ctHostSSL, - ctHostNoSSL + ctHostNoSSL, + ctHostGSS, + ctHostNoGSS, } ConnType; typedef struct HbaLine diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 7698cd1f88..3e46ac437a 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -66,7 +66,7 @@ typedef struct #include "datatype/timestamp.h" #include "libpq/hba.h" #include "libpq/pqcomm.h" - +#include "lib/stringinfo.h" typedef enum CAC_state { @@ -86,6 +86,9 @@ 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 */ + bool enc; /* GSSAPI encryption in use */ + StringInfoData buf; /* GSSAPI encryption data buffering */ + StringInfoData writebuf; /* GSSAPI nonblocking write buffering */ #endif } pg_gssinfo; #endif @@ -278,6 +281,12 @@ extern char *be_tls_get_peer_finished(Port *port, size_t *len); extern char *be_tls_get_certificate_hash(Port *port, size_t *len); #endif +#ifdef ENABLE_GSS +/* Read and write to a GSSAPI-encrypted connection. */ +extern ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); +extern 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 7bf06c65e9..e34f552ef7 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -92,6 +92,9 @@ extern ssize_t secure_read(Port *port, void *ptr, size_t len); extern ssize_t secure_write(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_read(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_write(Port *port, const void *ptr, size_t len); +#ifdef ENABLE_GSS +extern ssize_t secure_open_gssapi(Port *port); +#endif extern bool ssl_loaded_verify_locations; diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index cc0e0b32c7..ade1190096 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -199,9 +199,10 @@ typedef struct CancelRequestPacket /* - * A client can also start by sending a SSL negotiation request, to get a - * secure channel. + * A client can also start by sending a SSL or GSSAPI negotiation request to + * get a secure channel. */ #define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679) +#define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680) #endif /* PQCOMM_H */ diff --git a/src/include/pgstat.h b/src/include/pgstat.h index be2f59239b..4f06f7a2bc 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -787,7 +787,8 @@ typedef enum WAIT_EVENT_SSL_OPEN_SERVER, WAIT_EVENT_WAL_RECEIVER_WAIT_START, WAIT_EVENT_WAL_SENDER_WAIT_WAL, - WAIT_EVENT_WAL_SENDER_WRITE_DATA + WAIT_EVENT_WAL_SENDER_WRITE_DATA, + WAIT_EVENT_GSS_OPEN_SERVER, } WaitEventClient; /* ---------- diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index abe0a50e98..c814e5e35a 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -57,6 +57,10 @@ else OBJS += sha2.o endif +ifeq ($(with_gssapi),yes) +OBJS += fe-gssapi-common.o fe-secure-gssapi.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 3b2073a47f..af2ef78d76 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -49,52 +49,7 @@ * GSSAPI authentication system. */ -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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. @@ -195,18 +150,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen) static int pg_GSS_startup(PGconn *conn, int payloadlen) { - OM_uint32 maj_stat, - min_stat; - int maxlen; - gss_buffer_desc temp_gbuf; - char *host = PQhost(conn); - - if (!(host && host[0] != '\0')) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("host name must be specified\n")); - return STATUS_ERROR; - } + int ret; if (conn->gctx) { @@ -215,33 +159,9 @@ pg_GSS_startup(PGconn *conn, int payloadlen) return STATUS_ERROR; } - /* - * Import service principal name so the proper ticket can be acquired by - * the GSSAPI system. - */ - maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; - temp_gbuf.value = (char *) malloc(maxlen); - if (!temp_gbuf.value) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory\n")); - return STATUS_ERROR; - } - snprintf(temp_gbuf.value, maxlen, "%s@%s", - conn->krbsrvname, host); - temp_gbuf.length = strlen(temp_gbuf.value); - - maj_stat = gss_import_name(&min_stat, &temp_gbuf, - GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); - free(temp_gbuf.value); - - if (maj_stat != GSS_S_COMPLETE) - { - pg_GSS_error(libpq_gettext("GSSAPI name import error"), - conn, - maj_stat, min_stat); - return STATUS_ERROR; - } + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return ret; /* * Initial packet is the same as a continuation packet with no initial diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index a7e969d7c1..2bd1be1b97 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, #else #define DefaultSSLMode "disable" #endif +#ifdef ENABLE_GSS +#include "fe-gssapi-common.h" +#define DefaultGSSMode "prefer" +#else +#define DefaultGSSMode "disable" +#endif /* ---------- * Definition of the conninfo parameters and their fallback resources. @@ -303,6 +309,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Require-Peer", "", 10, offsetof(struct pg_conn, requirepeer)}, + /* + * Expose gssmode similarly to sslmode - we can stil handle "disable" and + * "prefer". + */ + {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL, + "GSS-Mode", "", 7, /* sizeof("disable") == 7 */ + offsetof(struct pg_conn, gssmode)}, + #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* Kerberos and GSSAPI authentication support specifying the service name */ {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL, @@ -1166,6 +1180,39 @@ connectOptions2(PGconn *conn) goto oom_error; } + /* + * validate gssmode option + */ + if (conn->gssmode) + { + if (strcmp(conn->gssmode, "disable") != 0 && + strcmp(conn->gssmode, "prefer") != 0 && + strcmp(conn->gssmode, "require") != 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid gssmode value: \"%s\"\n"), + conn->gssmode); + return false; + } +#ifndef ENABLE_GSS + if (strcmp(conn->gssmode, "require") == 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer( + &conn->errorMessage, + libpq_gettext("no GSSAPI support; cannot require GSSAPI\n")); + return false; + } +#endif + } + else + { + conn->gssmode = strdup(DefaultGSSMode); + if (!conn->gssmode) + goto oom_error; + } + /* * Resolve special "auto" client_encoding from the locale */ @@ -1804,6 +1851,11 @@ connectDBStart(PGconn *conn) conn->wait_ssl_try = true; #endif +#ifdef ENABLE_GSS + if (conn->gssmode[0] == 'd') /* "disable" */ + conn->try_gss = false; +#endif + /* * Set up to try to connect, with protocol 3.0 as the first attempt. */ @@ -2051,6 +2103,7 @@ PQconnectPoll(PGconn *conn) case CONNECTION_NEEDED: case CONNECTION_CHECK_WRITABLE: case CONNECTION_CONSUME: + case CONNECTION_GSS_STARTUP: break; default: @@ -2426,17 +2479,54 @@ keep_going: /* We will come back to here until there is } #endif /* HAVE_UNIX_SOCKETS */ + if (IS_AF_UNIX(conn->raddr.addr.ss_family)) + { + /* Don't request SSL or GSSAPI over Unix sockets */ #ifdef USE_SSL + conn->allow_ssl_try = false; +#endif +#ifdef ENABLE_GSS + conn->try_gss = false; +#endif + } +#ifdef ENABLE_GSS + /* + * If GSSAPI is enabled and we have a ccache, try to set it up + * before sending startup messages. If it's already + * operating, don't try SSL and instead just build the startup + * packet. + */ + if (conn->try_gss && !conn->gctx) + conn->try_gss = pg_GSS_have_ccache(&conn->gcred); + if (conn->try_gss && !conn->gctx) + { + ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE); + if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not send GSSAPI negotiation packet: %s\n"), + SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + goto error_return; + } + + /* Ok, wait for response */ + conn->status = CONNECTION_GSS_STARTUP; + return PGRES_POLLING_READING; + } + else if (!conn->gctx && conn->gssmode[0] == 'r') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache,no server support, or using a local socket)\n")); + goto error_return; + } +#endif + +#ifdef USE_SSL /* * If SSL is enabled and we haven't already got it running, * request it instead of sending the startup message. */ - if (IS_AF_UNIX(conn->raddr.addr.ss_family)) - { - /* Don't bother requesting SSL over a Unix socket */ - conn->allow_ssl_try = false; - } if (conn->allow_ssl_try && !conn->wait_ssl_try && !conn->ssl_in_use) { @@ -2629,6 +2719,97 @@ keep_going: /* We will come back to here until there is #endif /* USE_SSL */ } + case CONNECTION_GSS_STARTUP: + { +#ifdef ENABLE_GSS + PostgresPollingStatusType pollres; + + /* + * If we haven't yet, get the postmaster's response to our + * negotiation packet + */ + if (conn->try_gss && !conn->gctx) + { + char gss_ok; + int rdresult = pqReadData(conn); + if (rdresult < 0) + /* pqReadData fills in error message */ + goto error_return; + else if (rdresult == 0) + /* caller failed to wait for data */ + return PGRES_POLLING_READING; + if (pqGetc(&gss_ok, conn) < 0) + /* shouldn't happen... */ + return PGRES_POLLING_READING; + + if (gss_ok == 'E') + { + /* + * Server failure of some sort. Assume it's a + * protocol version support failure, and let's see if + * we can't recover (if it's not, we'll get a better + * error message on retry). Server gets fussy if we + * don't hang up the socket, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + + /* mark byte consumed */ + conn->inStart = conn->inCursor; + + if (gss_ok == 'N') + { + /* Server doesn't want GSSAPI; fall back if we can */ + if (conn->gssmode[0] == 'r') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n")); + goto error_return; + } + + conn->try_gss = false; + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (gss_ok != 'G') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"), + gss_ok); + goto error_return; + } + } + + /* Begin or continue GSSAPI negotiation */ + pollres = pqsecure_open_gss(conn); + if (pollres == PGRES_POLLING_OK) + { + /* All set for startup packet */ + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (pollres == PGRES_POLLING_FAILED && + conn->gssmode[0] == 'p') + { + /* + * We failed, but we can retry on "prefer". Have to drop + * the current connection to do so, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + return pollres; +#else /* !ENABLE_GSS */ + /* unreachable */ + goto error_return; +#endif /* ENABLE_GSS */ + } + /* * Handle authentication exchange: wait for postmaster messages * and respond as necessary. @@ -2781,6 +2962,24 @@ keep_going: /* We will come back to here until there is /* OK, we read the message; mark data consumed */ conn->inStart = conn->inCursor; +#ifdef ENABLE_GSS + /* + * If gssmode is "prefer" and we're using GSSAPI, retry + * without it. + */ + if (conn->gssenc && conn->gssmode[0] == 'p') + { + OM_uint32 minor; + /* postmaster expects us to drop the connection */ + conn->try_gss = false; + conn->gssenc = false; + gss_delete_sec_context(&minor, &conn->gctx, NULL); + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } +#endif + #ifdef USE_SSL /* @@ -3361,6 +3560,11 @@ makeEmptyPGconn(void) conn->wait_ssl_try = false; conn->ssl_in_use = false; #endif +#ifdef ENABLE_GSS + conn->try_gss = true; + initPQExpBuffer(&conn->gbuf); + initPQExpBuffer(&conn->gwritebuf); +#endif /* * We try to send at least 8K at a time, which is the usual size of pipe @@ -3492,10 +3696,28 @@ freePGconn(PGconn *conn) free(conn->sslcompression); if (conn->requirepeer) free(conn->requirepeer); + if (conn->gssmode) + free(conn->gssmode); #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) if (conn->krbsrvname) free(conn->krbsrvname); #endif +#ifdef ENABLE_GSS + if (conn->gcred != GSS_C_NO_CREDENTIAL) + { + OM_uint32 minor; + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + } + if (conn->gctx) + { + OM_uint32 minor; + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = NULL; + } + termPQExpBuffer(&conn->gbuf); + termPQExpBuffer(&conn->gwritebuf); +#endif #if defined(ENABLE_GSS) && defined(ENABLE_SSPI) if (conn->gsslib) free(conn->gsslib); diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c new file mode 100644 index 0000000000..ce71b11aea --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.c @@ -0,0 +1,128 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.c + * The front-end (client) GSSAPI common code + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-gssapi-common.c + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "fe-gssapi-common.h" + +#include "libpq-int.h" +#include "pqexpbuffer.h" + +/* + * 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); +} + +/* + * Check if we can acquire credentials at all. + */ +bool +pg_GSS_have_ccache(gss_cred_id_t *cred_out) +{ + OM_uint32 major, minor; + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + + major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &cred, NULL, NULL); + if (major != GSS_S_COMPLETE) + { + *cred_out = NULL; + return false; + } + *cred_out = cred; + return true; +} + +/* + * Try to load service name for a connection + */ +int +pg_GSS_load_servicename(PGconn *conn) +{ + OM_uint32 maj_stat, min_stat; + int maxlen; + gss_buffer_desc temp_gbuf; + char *host; + + if (conn->gtarg_nam != NULL) + /* Already taken care of - move along */ + return STATUS_OK; + + host = PQhost(conn); + if (!(host && host[0] != '\0')) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("host name must be specified\n")); + return STATUS_ERROR; + } + + /* + * Import service principal name so the proper ticket can be acquired by + * the GSSAPI system. + */ + maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; + temp_gbuf.value = (char *) malloc(maxlen); + if (!temp_gbuf.value) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return STATUS_ERROR; + } + snprintf(temp_gbuf.value, maxlen, "%s@%s", + conn->krbsrvname, host); + temp_gbuf.length = strlen(temp_gbuf.value); + + maj_stat = gss_import_name(&min_stat, &temp_gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); + free(temp_gbuf.value); + + if (maj_stat != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI name import error"), + conn, + maj_stat, min_stat); + return STATUS_ERROR; + } + return STATUS_OK; +} diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h new file mode 100644 index 0000000000..58811df9f1 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.h + * + * Definitions for GSSAPI common routines + * + * Portions Copyright (c) 1996-2018, 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 + +#include "libpq-fe.h" +#include "libpq-int.h" + +void pg_GSS_error(const char *mprefix, PGconn *conn, + OM_uint32 maj_stat, OM_uint32 min_stat); +bool pg_GSS_have_ccache(gss_cred_id_t *cred_out); +int pg_GSS_load_servicename(PGconn *conn); +#endif /* FE_GSSAPI_COMMON_H */ diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c new file mode 100644 index 0000000000..427651ec33 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,345 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-gssapi.c + * The front-end (client) encryption support for GSSAPI + * + * Portions Copyright (c) 2016-2018, 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" + +/* + * Require encryption support, as well as mutual authentication and + * tamperproofing measures. + */ +#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \ + GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG + +static ssize_t +send_buffered_data(PGconn *conn, size_t len) +{ + ssize_t ret = pqsecure_raw_write(conn, + conn->gwritebuf.data + conn->gwritecurs, + conn->gwritebuf.len - conn->gwritecurs); + if (ret < 0) + return ret; + + conn->gwritecurs += ret; + + if (conn->gwritecurs == conn->gwritebuf.len) + { + /* entire request has now been written */ + resetPQExpBuffer(&conn->gwritebuf); + conn->gwritecurs = 0; + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +pg_GSS_write(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + + if (conn->gwritebuf.len != 0) + return send_buffered_data(conn, len); + + /* encrypt the message */ + input.value = ptr; + input.length = len; + + major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT, + &input, &conf, &output); + if (major != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor); + goto cleanup; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI did not provide confidentiality\n")); + goto cleanup; + } + + /* 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + + ret = send_buffered_data(conn, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* check for available data */ + if (conn->gcursor < conn->gbuf.len) + { + /* clamp length */ + 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; + } + + /* reset buffer if all data has been read */ + if (conn->gcursor == conn->gbuf.len) + { + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + } + + return ret; +} + +static ssize_t +load_packet_length(PGconn *conn) +{ + ssize_t ret; + if (conn->gbuf.len < 4) + { + ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len); + 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 + conn->gbuf.len, + 4 - conn->gbuf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + { + /* partial read from pqsecure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(PGconn *conn, size_t len) +{ + ssize_t ret; + + ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI encrypted packet length %ld too big\n"), + len); + return -1; + } + + /* load any missing parts of the packet */ + if (conn->gbuf.len - 4 < len) + { + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + len - conn->gbuf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < len) + { + /* partial read from pqsecure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +ssize_t +pg_GSS_read(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = 0; + int conf = 0; + + /* handle any buffered data */ + if (conn->gcursor != 0) + return read_from_buffer(conn, ptr, len); + + /* load in the packet length, if not yet loaded */ + ret = load_packet_length(conn); + if (ret < 0) + return ret; + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0) + return ret; + + /* decrypt the packet */ + input.value = conn->gbuf.data + 4; + + major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL); + if (major != GSS_S_COMPLETE) + { + 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; + } + + /* load decrypted packet into our buffer */ + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + ret = enlargePQExpBuffer(&conn->gbuf, output.length); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI decrypted packet length %ld too big\n"), + output.length); + ret = -1; + goto cleanup; + } + + memcpy(conn->gbuf.data, output.value, output.length); + conn->gbuf.len = output.length; + conn->gbuf.data[conn->gbuf.len] = '\0'; + + ret = read_from_buffer(conn, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +PostgresPollingStatusType +pqsecure_open_gss(PGconn *conn) +{ + ssize_t ret; + OM_uint32 major, minor; + uint32 netlen; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + + /* Send out any data we might have */ + if (conn->gwritebuf.len != 0) + { + ret = send_buffered_data(conn, 1); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_WRITING; + else if (ret == 1) + /* sent all data */ + return PGRES_POLLING_READING; + return PGRES_POLLING_FAILED; + } + + /* Client sends first, and sending creates a context */ + if (conn->gctx) + { + /* Process any incoming data we might have */ + ret = load_packet_length(conn); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + if (conn->gbuf.data[0] == 'E') + { + /* + * We can taken an error here, and it's my least favorite thing. + * How long can error messages be? That's a good question, but + * backend's pg_gss_error() caps them at 256. Do a single read + * for that and call it a day. Cope with this here rather than in + * load_packet since expandPQExpBuffer will destroy any data in + * the buffer on failure. + */ + load_packet(conn, 256); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Server error: %s"), + conn->gbuf.data + 1); + return PGRES_POLLING_FAILED; + } + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + input.value = conn->gbuf.data + 4; + } + + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return PGRES_POLLING_FAILED; + + major = gss_init_sec_context(&minor, conn->gcred, &conn->gctx, + conn->gtarg_nam, GSS_C_NO_OID, + GSS_REQUIRED_FLAGS, 0, 0, &input, NULL, + &output, NULL, NULL); + resetPQExpBuffer(&conn->gbuf); + if (GSS_ERROR(major)) + { + pg_GSS_error(libpq_gettext("GSSAPI context establishment error"), + conn, major, minor); + return PGRES_POLLING_FAILED; + } + else if (output.length == 0) + { + /* + * We're done - hooray! Kind of gross, but we need to disable SSL + * here so that we don't accidentally tunnel one over the other. + */ +#ifdef USE_SSL + conn->allow_ssl_try = false; +#endif + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + conn->gssenc = true; + return PGRES_POLLING_OK; + } + + /* Queue the token for writing */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + gss_release_buffer(&minor, &output); + return PGRES_POLLING_WRITING; +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index f7dc249bf0..18155e4b5e 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) n = pgtls_read(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_read(conn, ptr, len); + } + else #endif { n = pqsecure_raw_read(conn, ptr, len); @@ -287,7 +294,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; @@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) n = pgtls_write(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_write(conn, ptr, len); + } + else #endif { n = pqsecure_raw_write(conn, ptr, len); diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index ed9c806861..bdd5a10cd8 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -65,8 +65,9 @@ typedef enum CONNECTION_NEEDED, /* Internal state: connect() needed */ CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable * connection. */ - CONNECTION_CONSUME /* Wait for any pending message and consume + CONNECTION_CONSUME, /* Wait for any pending message and consume * them. */ + CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */ } ConnStatusType; typedef enum diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 9a586ff25a..6cf3459a8c 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -22,6 +22,7 @@ /* We assume libpq-fe.h has already been included. */ #include "libpq-events.h" +#include "lib/stringinfo.h" #include <time.h> #ifndef WIN32 @@ -473,9 +474,19 @@ struct pg_conn #endif /* USE_OPENSSL */ #endif /* USE_SSL */ + char *gssmode; /* GSS mode (require,prefer,disable) */ #ifdef ENABLE_GSS gss_ctx_id_t gctx; /* GSS context */ gss_name_t gtarg_nam; /* GSS target name */ + + /* The following are encryption-only */ + 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 try_gss; /* GSS attempting permitted */ + bool gssenc; /* GSS encryption is usable */ + gss_cred_id_t gcred; /* GSS credential temp storage. */ #endif #ifdef ENABLE_SSPI @@ -651,7 +662,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); @@ -745,6 +756,23 @@ extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, int *names_examined, char **first_name); +/* === GSSAPI === */ + +#ifdef ENABLE_GSS + +/* + * Establish a GSSAPI-encrypted connection. + */ +extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn); + +/* + * Read and write functions for GSSAPI-encrypted connections, with internal + * buffering to handle nonblocking sockets. + */ +extern ssize_t pg_GSS_write(PGconn *conn, void *ptr, size_t len); +extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len); +#endif + /* === miscellaneous macros === */ /* diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 593732fd95..e152d7056b 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -183,13 +183,19 @@ sub mkvcbuild $postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); $postgres->FullExportDLL('postgres.lib'); - # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c - # if building without OpenSSL + # The OBJS scraper doesn't know about ifdefs, so remove + # be-secure-openssl.c if building without OpenSSL, and + # be-gssapi-common.c and be-secure-gssapi.c when building with GSSAPI. if (!$solution->{options}->{openssl}) { $postgres->RemoveFile('src/backend/libpq/be-secure-common.c'); $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + $postgres->RemoveFile('src/backend/libpq/be-secure-gssapi.c'); + } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', 'src/backend/snowball'); @@ -243,9 +249,10 @@ sub mkvcbuild 'src/interfaces/libpq/libpq.rc'); $libpq->AddReference($libpgport); - # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c - # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if - # building with OpenSSL. + # The OBJS scraper doesn't know about ifdefs, so remove + # fe-secure-openssl.c and sha2_openssl.c if building without OpenSSL, and + # remove sha2.c if building with OpenSSL. Also remove + # fe-gssapi-common.c and fe-secure-gssapi.c when building with GSSAPI. if (!$solution->{options}->{openssl}) { $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c'); @@ -256,6 +263,11 @@ sub mkvcbuild { $libpq->RemoveFile('src/common/sha2.c'); } + if (!$solution->{options}->{gss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c'); + } my $libpqwalreceiver = $solution->AddProject('libpqwalreceiver', 'dll', '', -- 2.17.1
Attachment
On Mon, Jun 11, 2018 at 01:31:12PM -0400, Andrew Dunstan wrote: > On 06/11/2018 01:13 PM, Nico Williams wrote: > >Well, all the free CIs like Travis and Appveyor do it this way. You > >don't have to *use* it just because the .yml files are in the source > >tree. But you have to have the .yml files in the source tree in order > >to use these CIs. It'd be nice to be able to point somewhere else for > >them, but whatever, that's not something we get much choice in at this > >time. > > That's not true, at least for Appveyor (can't speak about travis - I have no > first hand experience). For appveyor, you can supply a custom appveyor.yml > file, which can be a complete URL. In fact, if you use a plain git source as > opposed to one of the managed git services it supports, you have to do it > that way - it ignores an appveyor.yml in your repo. I found this out the > very hard way over the last few days, and they very kindly don't warn you at > all about this. OK, that's.. nice, maybe, I guess, but I'd still want version control for these yml files -- why not have them in-tree? I'd rather have them in-tree unless there's a good reason not to have them there. In other projects I definitely find it better to have these files in-tree. Nico --
On Mon, Jun 11, 2018 at 04:11:10PM -0400, Robbie Harwood wrote: > Nico was kind enough to provide me with some code review. This should > those concerns (clarify short-read behavior and fixing error checking on > GSS functions). Besides the bug you fixed and which I told you about off-list (on IRC, specifically), I only have some commentary that does not need any action: - support for non-Kerberos/default GSS mechanisms This might require new values for gssmode: prefer-<mechanism-name> and require-<mechanism-name>. One could always use SPNEGO if there are multiple mechanisms to choose from. And indeed, you could just use SPNEGO if the user has credentials for multiple mechanism. (Because GSS has no standard mechanism _names_, this means making some up. This is one obnoxious shortcoming of the GSS-API...) - when the SCRAM channel binding work is done, it might be good to add an option for TLS + GSS w/ channel binding to TLS and no gss wrap tokens Nico --
Nico Williams <nico@cryptonector.com> writes: > On Mon, Jun 11, 2018 at 04:11:10PM -0400, Robbie Harwood wrote: >> Nico was kind enough to provide me with some code review. This should >> those concerns (clarify short-read behavior and fixing error checking on >> GSS functions). > > Besides the bug you fixed and which I told you about off-list (on IRC, > specifically), I only have some commentary that does not need any > action: > > - support for non-Kerberos/default GSS mechanisms > > This might require new values for gssmode: prefer-<mechanism-name> > and require-<mechanism-name>. One could always use SPNEGO if there > are multiple mechanisms to choose from. And indeed, you could just > use SPNEGO if the user has credentials for multiple mechanism. > > (Because GSS has no standard mechanism _names_, this means making > some up. This is one obnoxious shortcoming of the GSS-API...) As long as it's better than passing raw OIDs on the CLI... > - when the SCRAM channel binding work is done, it might be good to add > an option for TLS + GSS w/ channel binding to TLS and no gss wrap > tokens I think both of these are neat ideas if they'll be used. Getting GSSAPI encryption in shouldn't preclude either in its present form (should make it easier, I hope), but I'm glad to hear of possible future work as well! Thanks, --Robbie
Attachment
On Tue, Jun 12, 2018 at 12:36:01PM -0400, Robbie Harwood wrote: > Nico Williams <nico@cryptonector.com> writes: > > On Mon, Jun 11, 2018 at 04:11:10PM -0400, Robbie Harwood wrote: > >> Nico was kind enough to provide me with some code review. This should > >> those concerns (clarify short-read behavior and fixing error checking on > >> GSS functions). > > > > Besides the bug you fixed and which I told you about off-list (on IRC, > > specifically), I only have some commentary that does not need any > > action: > > > > - support for non-Kerberos/default GSS mechanisms > > > > This might require new values for gssmode: prefer-<mechanism-name> > > and require-<mechanism-name>. One could always use SPNEGO if there > > are multiple mechanisms to choose from. And indeed, you could just > > use SPNEGO if the user has credentials for multiple mechanism. > > > > (Because GSS has no standard mechanism _names_, this means making > > some up. This is one obnoxious shortcoming of the GSS-API...) > > As long as it's better than passing raw OIDs on the CLI... Rite? I think this can be a follow-on patch, though trying SPNEGO if the user has credentials for multiple mechanisms (and SPNEGO is indicated) seems simple enough to do now (no interface changes). > > - when the SCRAM channel binding work is done, it might be good to add > > an option for TLS + GSS w/ channel binding to TLS and no gss wrap > > tokens > > I think both of these are neat ideas if they'll be used. Getting GSSAPI > encryption in shouldn't preclude either in its present form (should make > it easier, I hope), but I'm glad to hear of possible future work as > well! This one can (must) wait. It has some security benefits. You get to use GSS/Kerberos for authentication, but you get an forward security you'd get from TLS (if the GSS mechanism doesn't provide it, which Kerberos today does not). Nico --
Sorry if this sounds facetious, but: What is the point of this patch? What's the advantage of GSSAPI encryption over SSL? I was hoping to find the answer by reading the documentation changes, but all I can see is "how" to set it up, and nothing about "why". - Heikki
Heikki, * Heikki Linnakangas (hlinnaka@iki.fi) wrote: > Sorry if this sounds facetious, but: > > What is the point of this patch? What's the advantage of GSSAPI encryption > over SSL? I was hoping to find the answer by reading the documentation > changes, but all I can see is "how" to set it up, and nothing about "why". If you've already got an existing Kerberos environment, then it's a lot nicer to leverage that rather than having to also implement a full PKI to support and use SSL-based encryption. There's also something to be said for having alternatives to OpenSSL. Thanks! Stephen
Attachment
On Mon, Aug 06, 2018 at 10:36:34AM -0400, Stephen Frost wrote: > * Heikki Linnakangas (hlinnaka@iki.fi) wrote: > > Sorry if this sounds facetious, but: > > > > What is the point of this patch? What's the advantage of GSSAPI encryption > > over SSL? I was hoping to find the answer by reading the documentation > > changes, but all I can see is "how" to set it up, and nothing about "why". > > If you've already got an existing Kerberos environment, then it's a lot > nicer to leverage that rather than having to also implement a full PKI > to support and use SSL-based encryption. > > There's also something to be said for having alternatives to OpenSSL. Those two reasons would be my motivation if I were implementing this, and they are some of the reasons I did a code review. Nico --
Stephen Frost <sfrost@snowman.net> writes: > * Heikki Linnakangas (hlinnaka@iki.fi) wrote: > >> What is the point of this patch? What's the advantage of GSSAPI >> encryption over SSL? I was hoping to find the answer by reading the >> documentation changes, but all I can see is "how" to set it up, and >> nothing about "why". > > If you've already got an existing Kerberos environment, then it's a > lot nicer to leverage that rather than having to also implement a full > PKI to support and use SSL-based encryption. > > There's also something to be said for having alternatives to OpenSSL. This exactly. If you're in a position where you're using Kerberos (or most other things from the GSSAPI) for authentication, the encryption comes at little to no additional setup cost. And then you get all the security benefits outlined in the docs changes. Thanks, --Robbie
Attachment
On Mon, Aug 06, 2018 at 05:23:28PM -0400, Robbie Harwood wrote: > If you're in a position where you're using Kerberos (or most other > things from the GSSAPI) for authentication, the encryption comes at > little to no additional setup cost. And then you get all the security > benefits outlined in the docs changes. Please note that the last patch set does not apply anymore, so I have moved it to CF 2018-11 and marked it as waiting on author. -- Michael
Attachment
Michael Paquier <michael@paquier.xyz> writes: > On Mon, Aug 06, 2018 at 05:23:28PM -0400, Robbie Harwood wrote: >> If you're in a position where you're using Kerberos (or most other >> things from the GSSAPI) for authentication, the encryption comes at >> little to no additional setup cost. And then you get all the security >> benefits outlined in the docs changes. > > Please note that the last patch set does not apply anymore, so I have > moved it to CF 2018-11 and marked it as waiting on author. Indeed. Please find v19 attached. It's just a rebase; no architecture changes. I'll set commitfest status back to "Needs Review" once I've sent this mail. Thanks, --Robbie From 4a91571db6873da46becf2f7ad0cd90222255df2 Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Thu, 10 May 2018 16:12:03 -0400 Subject: [PATCH] libpq GSSAPI encryption support On both the frontend and backend, prepare for GSSAPI encryption support by moving common code for error handling into a separate file. Fix a TODO for handling multiple status messages in the process. Eliminate the OIDs, which have not been needed for some time. Add frontend and backend encryption support functions. Keep the context initiation for authentication-only separate on both the frontend and backend in order to avoid concerns about changing the requested flags to include encryption support. In postmaster, pull GSSAPI authorization checking into a shared function. Also share the initiator name between the encryption and non-encryption codepaths. Modify pqsecure_write() to take a non-const pointer. The pointer will not be modified, but this change (or a const-discarding cast, or a malloc()+memcpy()) is necessary for GSSAPI due to const/struct interactions in C. For HBA, add "hostgss" and "hostnogss" entries that behave similarly to their SSL counterparts. "hostgss" requires either "gss", "trust", or "reject" for its authentication. Simiarly, add a "gssmode" parameter to libpq. Supported values are "disable", "require", and "prefer". Notably, negotiation will only be attempted if credentials can be acquired. Move credential acquisition into its own function to support this behavior. Finally, add documentation for everything new, and update existing documentation on connection security. Thanks to Michael Paquier for the Windows fixes. --- doc/src/sgml/client-auth.sgml | 75 ++++-- doc/src/sgml/libpq.sgml | 57 +++- doc/src/sgml/runtime.sgml | 77 +++++- src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 107 +++----- src/backend/libpq/be-gssapi-common.c | 64 +++++ src/backend/libpq/be-gssapi-common.h | 26 ++ src/backend/libpq/be-secure-gssapi.c | 321 ++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 ++ src/backend/libpq/hba.c | 51 +++- src/backend/postmaster/pgstat.c | 3 + src/backend/postmaster/postmaster.c | 64 ++++- src/include/libpq/hba.h | 4 +- src/include/libpq/libpq-be.h | 11 +- src/include/libpq/libpq.h | 3 + src/include/libpq/pqcomm.h | 5 +- src/include/pgstat.h | 3 +- src/interfaces/libpq/Makefile | 4 + src/interfaces/libpq/fe-auth.c | 82 +----- src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++- src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++ src/interfaces/libpq/fe-gssapi-common.h | 23 ++ src/interfaces/libpq/fe-secure-gssapi.c | 345 ++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-fe.h | 3 +- src/interfaces/libpq/libpq-int.h | 30 ++- src/tools/msvc/Mkvcbuild.pm | 10 + 27 files changed, 1571 insertions(+), 193 deletions(-) create mode 100644 src/backend/libpq/be-gssapi-common.c create mode 100644 src/backend/libpq/be-gssapi-common.h create mode 100644 src/backend/libpq/be-secure-gssapi.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.h 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 c2114021c3..6f9f2b7560 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> </synopsis> The meaning of the fields is as follows: @@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> This record matches connection attempts made using TCP/IP. - <literal>host</literal> records match either + <literal>host</literal> records match <acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection - attempts. + attempts as well as <acronym>GSSAPI</acronym> or + non-<acronym>GSSAPI</acronym> connection attempts. </para> <note> <para> @@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><literal>hostgss</literal></term> + <listitem> + <para> + This record matches connection attempts made using TCP/IP, + but only when the connection is made with <acronym>GSSAPI</acronym> + encryption. + </para> + + <para> + To make use of this option the server must be built with + <acronym>GSSAPI</acronym> support. Otherwise, + the <literal>hostgss</literal> record is ignored except for logging a + warning that it cannot match any connections. + </para> + + <para> + Note that the only supported <xref linkend="auth-methods"/> for use + with <acronym>GSSAPI</acronym> encryption + are <literal>gss</literal>, <literal>reject</literal>, + and <literal>trust</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>hostnogss</literal></term> + <listitem> + <para> + This record type has the opposite behavior of <literal>hostgss</literal>; + it only matches connection attempts made over + TCP/IP that do not use <acronym>GSSAPI</acronym>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable>database</replaceable></term> <listitem> @@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> Use GSSAPI to authenticate the user. This is only - available for TCP/IP connections. See <xref - linkend="gssapi-auth"/> for details. + available for TCP/IP connections . See <xref + linkend="gssapi-auth"/> for details. It can be used in conjunction + with GSSAPI encryption. </para> </listitem> </varlistentry> @@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256 host all mike .example.com md5 host all all .example.com scram-sha-256 -# In the absence of preceding "host" lines, these two lines will +# In the absence of preceding "host" lines, these three lines will # reject all connections from 192.168.54.1 (since that entry will be -# matched first), but allow GSSAPI connections from anywhere else -# on the Internet. The zero mask causes no bits of the host IP +# matched first), but allow GSSAPI-encrypted connections from anywhere else +# on the Internet. Unencrypted GSSAPI connections are allowed from +# 192.168.12.10 only. The zero mask causes no bits of the host IP # address to be considered, so it matches any host. # # TYPE DATABASE USER ADDRESS METHOD host all all 192.168.54.1/32 reject -host all all 0.0.0.0/0 gss +hostgss all all 0.0.0.0/0 gss +host all all 192.168.12.10/32 gss # Allow users from 192.168.x.x hosts to connect to any database, if # they pass the ident check. If, for example, ident says the user is @@ -1051,13 +1093,16 @@ omicron bryanh guest1 <para> <productname>GSSAPI</productname> is an industry-standard protocol for secure authentication defined in RFC 2743. - <productname>PostgreSQL</productname> supports - <productname>GSSAPI</productname> with <productname>Kerberos</productname> - authentication according to RFC 1964. <productname>GSSAPI</productname> - 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. + + <productname>PostgreSQL</productname> + supports <productname>GSSAPI</productname> for use as either an encrypted, + authenticated layer, or as authentication + only. <productname>GSSAPI</productname> provides automatic authentication + (single sign-on) for systems that support it. The authentication itself is + secure. If <productname>GSSAPI</productname> encryption + (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are + used, the data sent along the database connection will be encrypted; + otherwise, it will not. </para> <para> diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 06d909e804..27c74056bb 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1316,6 +1316,61 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode"> + <term><literal>gssmode</literal></term> + <listitem> + <para> + This option determines whether or with what priority a secure + <acronym>GSS</acronym> TCP/IP connection will be negotiated with the + server. There are three modes: + + <variablelist> + <varlistentry> + <term><literal>disable</literal></term> + <listitem> + <para> + only try a non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>prefer</literal> (default)</term> + <listitem> + <para> + if there are <acronym>GSSAPI</acronym> credentials present (i.e., + in a credentials cache), first try + a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or + there are no credentials, try a + non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>require</literal></term> + <listitem> + <para> + only try a <acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + <literal>gssmode</literal> is ignored for Unix domain socket + communication. If <productname>PostgreSQL</productname> is compiled + without GSSAPI support, using the <literal>require</literal> option + will cause an error, while <literal>prefer</literal> will be accepted + but <application>libpq</application> will not actually attempt + a <acronym>GSSAPI</acronym>-encrypted + connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with + libpq</secondary></indexterm> + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-sslmode" xreflabel="sslmode"> <term><literal>sslmode</literal></term> <listitem> @@ -7918,7 +7973,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*) </para> <para> - For a connection to be known secure, SSL usage must be configured + For a connection to be known SSL-secured, SSL usage must be configured on <emphasis>both the client and the server</emphasis> before the connection is made. If it is only configured on the server, the client may end up sending sensitive information (e.g. passwords) before diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 8d9d40664b..ac9c882d44 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 </para> <para> - To prevent spoofing on TCP connections, the best solution is to use - SSL certificates and make sure that clients check the server's certificate. - To do that, the server + To prevent spoofing on TCP connections, either use + SSL certificates and make sure that clients check the server's certificate, + or use GSSAPI encryption (or both, if they're on separate connections). + </para> + + <para> + To prevent spoofing with SSL, the server must be configured to accept only <literal>hostssl</literal> connections (<xref linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files (<xref linkend="ssl-tcp"/>). The TCP client must connect using @@ -2018,6 +2022,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 <literal>verify-full</literal> and have the appropriate root certificate file installed (<xref linkend="libq-ssl-certificates"/>). </para> + + <para> + To prevent spoofing with GSSAPI, the server must be configured to accept + only <literal>hostgss</literal> connections + (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal> + authentication with them. The TCP client must connect + using <literal>gssmode=require</literal>. + </para> </sect1> <sect1 id="encryption-options"> @@ -2114,8 +2126,24 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 which hosts can use non-encrypted connections (<literal>host</literal>) and which require SSL-encrypted connections (<literal>hostssl</literal>). Also, clients can specify that they - connect to servers only via SSL. <application>Stunnel</application> or - <application>SSH</application> can also be used to encrypt transmissions. + connect to servers only via SSL. + </para> + + <para> + GSSAPI-encrypted connections encrypt all data sent across the network, + including queries and data returned. (No password is sent across the + network.) The <filename>pg_hba.conf</filename> file allows + administrators to specify which hosts can use non-encrypted connections + (<literal>host</literal>) and which require GSSAPI-encrypted connections + (<literal>hostgss</literal>). Also, clients can specify that they + connect to servers only on GSSAPI-encrypted connections + (<literal>gssmode=require</literal>). + </para> + + <para> + <application>Stunnel</application> or + <application>SSH</application> can also be used to encrypt + transmissions. </para> </listitem> </varlistentry> @@ -2504,6 +2532,45 @@ openssl x509 -req -in server.csr -text -days 365 \ </sect1> + <sect1 id="gssapi-enc"> + <title>Secure TCP/IP Connections with GSSAPI encryption</title> + + <indexterm zone="gssapi-enc"> + <primary>gssapi</primary> + </indexterm> + + <para> + <productname>PostgreSQL</productname> also has native support for + using <acronym>GSSAPI</acronym> to encrypt client/server communications for + increased security. Support requires that a <acronym>GSSAPI</acronym> + implementation (such as MIT krb5) is installed on both client and server + systems, and that support in <productname>PostgreSQL</productname> is + enabled at build time (see <xref linkend="installation"/>). + </para> + + <sect2 id="gssapi-setup"> + <title>Basic Setup</title> + + <para> + The <productname>PostgreSQL</productname> server will listen for both + normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP + port, and will negotiate with any connecting client on whether to + use <acronym>GSSAPI</acronym> for encryption (and for authentication). By + default, this decision is up to the client (which means it can be + downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about + setting up the server to require the use of <acronym>GSSAPI</acronym> for + some or all conections. + </para> + + <para> + Other than configuration of the negotiation + behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond + that which necessary for GSSAPI authentication. (For more information on + configuring that, see <xref linkend="gssapi-auth"/>.) + </para> + </sect2> + </sect1> + <sect1 id="ssh-tunnels"> <title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title> diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 3dbec23e30..47efef0682 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 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 8517565535..eed1e11ef1 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -173,12 +173,9 @@ bool pg_krb_caseins_users; *---------------------------------------------------------------- */ #ifdef ENABLE_GSS -#if defined(HAVE_GSSAPI_H) -#include <gssapi.h> -#else -#include <gssapi/gssapi.h> -#endif +#include "be-gssapi-common.h" +static int pg_GSS_checkauth(Port *port); static int pg_GSS_recvauth(Port *port); #endif /* ENABLE_GSS */ @@ -384,6 +381,17 @@ ClientAuthentication(Port *port) errmsg("connection requires a valid client certificate"))); } +#ifdef ENABLE_GSS + if (port->gss->enc && port->hba->auth_method != uaReject && + port->hba->auth_method != uaImplicitReject && + port->hba->auth_method != uaTrust && + port->hba->auth_method != uaGSS) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("GSSAPI encryption cannot be combined with non-GSSAPI authentication"))); + } +#endif + /* * Now proceed to do the actual authentication check */ @@ -524,8 +532,13 @@ ClientAuthentication(Port *port) case uaGSS: #ifdef ENABLE_GSS - sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); - status = pg_GSS_recvauth(port); + if (port->gss->enc) + status = pg_GSS_checkauth(port); + else + { + sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); + status = pg_GSS_recvauth(port); + } #else Assert(false); #endif @@ -1024,68 +1037,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) *---------------------------------------------------------------- */ #ifdef ENABLE_GSS - -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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 - - -/* - * Generate an error for GSSAPI authentication. The caller should apply - * _() to errmsg to make it translatable. - */ -static void -pg_GSS_error(int severity, const 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) { @@ -1094,7 +1045,6 @@ pg_GSS_recvauth(Port *port) lmin_s, gflags; int mtype; - int ret; StringInfoData buf; gss_buffer_desc gbuf; @@ -1247,10 +1197,17 @@ pg_GSS_recvauth(Port *port) */ gss_release_cred(&min_stat, &port->gss->cred); } + return pg_GSS_checkauth(port); +} + +static int +pg_GSS_checkauth(Port *port) +{ + int ret; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc gbuf; /* - * GSS_S_COMPLETE indicates that authentication is now complete. - * * Get the name of the user that authenticated, and compare it to the pg * username that was specified for the connection. */ @@ -1292,7 +1249,7 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI realm (%s) and configured realm (%s) don't match", cp, port->hba->krb_realm); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } } @@ -1302,14 +1259,14 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI did not return realm but realm matching was requested"); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, pg_krb_caseins_users); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return ret; } diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c new file mode 100644 index 0000000000..78d9f5d325 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,64 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.c + * Common code for GSSAPI authentication and encryption + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/libpq/be-gssapi-common.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +static void +pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type) +{ + gss_buffer_desc gmsg; + size_t i = 0; + OM_uint32 lmin_s, msg_ctx = 0; + + gmsg.value = NULL; + gmsg.length = 0; + + do + { + gss_display_status(&lmin_s, stat, type, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(s + i, gmsg.value, len - i); + i += gmsg.length; + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < len); + + if (msg_ctx || i == len) + ereport(WARNING, + (errmsg_internal("incomplete GSS error report"))); +} + +void +pg_GSS_error(int severity, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat) +{ + char msg_major[128], msg_minor[128]; + + /* Fetch major status message */ + pg_GSS_error_int(msg_major, sizeof(msg_major), maj_stat, GSS_C_GSS_CODE); + + /* Fetch mechanism minor status message */ + pg_GSS_error_int(msg_minor, sizeof(msg_minor), min_stat, GSS_C_MECH_CODE); + + /* + * 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/backend/libpq/be-gssapi-common.h b/src/backend/libpq/be-gssapi-common.h new file mode 100644 index 0000000000..c020051d2e --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.h + * Definitions for GSSAPI authentication and encryption handling + * + * Portions Copyright (c) 1996-2018, 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, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat); + +#endif /* BE_GSSAPI_COMMON_H */ diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c new file mode 100644 index 0000000000..2b70db2232 --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,321 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gssapi.c + * GSSAPI encryption support + * + * Portions Copyright (c) 2018-2018, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +#include "libpq/auth.h" +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "pgstat.h" + +#include <unistd.h> + +static ssize_t +send_buffered_data(Port *port, size_t len) +{ + ssize_t ret = secure_raw_write( + port, + port->gss->writebuf.data + port->gss->writebuf.cursor, + port->gss->writebuf.len - port->gss->writebuf.cursor); + if (ret < 0) + return ret; + + /* update and possibly clear buffer state */ + port->gss->writebuf.cursor += ret; + + if (port->gss->writebuf.cursor == port->gss->writebuf.len) + { + /* entire request has now been written */ + resetStringInfo(&port->gss->writebuf); + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +be_gssapi_write(Port *port, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + pg_gssinfo *gss = port->gss; + + if (gss->writebuf.len != 0) + return send_buffered_data(port, len); + + /* encrypt the message */ + output.value = NULL; + output.length = 0; + input.value = ptr; + input.length = len; + + major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT, + &input, &conf, &output); + if (major != GSS_S_COMPLETE) + { + pg_GSS_error(ERROR, gettext_noop("GSSAPI wrap error"), major, minor); + goto cleanup; + } else if (conf == 0) + { + ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality"))); + goto cleanup; + } + + /* 4 network-order length bytes, then payload */ + netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, output.value, output.length); + + ret = send_buffered_data(port, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* load up any available data */ + if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len) + { + /* clamp length */ + if (len > gss->buf.len - gss->buf.cursor) + len = gss->buf.len - gss->buf.cursor; + + memcpy(ptr, gss->buf.data + gss->buf.cursor, len); + gss->buf.cursor += len; + ret = len; + } + + /* reset buffer if all data has been read */ + if (gss->buf.cursor == gss->buf.len) + resetStringInfo(&gss->buf); + + return ret; +} + +static ssize_t +load_packetlen(Port *port) +{ + pg_gssinfo *gss = port->gss; + ssize_t ret; + + if (gss->buf.len < 4) + { + enlargeStringInfo(&gss->buf, 4 - gss->buf.len); + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + 4 - gss->buf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len < 4) + { + /* partial read from secure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(Port *port, size_t len) +{ + ssize_t ret; + pg_gssinfo *gss = port->gss; + + enlargeStringInfo(&gss->buf, len - gss->buf.len + 4); + + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + len - gss->buf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len - 4 < len) + { + /* partial read from secure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + return 0; +} + +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; + pg_gssinfo *gss = port->gss; + + if (gss->buf.cursor > 0) + return read_from_buffer(gss, ptr, len); + + /* load length if not present */ + ret = load_packetlen(port); + if (ret != 0) + return ret; + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + return ret; + + /* decrypt the packet */ + output.value = NULL; + output.length = 0; + input.value = gss->buf.data + 4; + + major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf, NULL); + if (major != GSS_S_COMPLETE) + { + 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; + } + + /* put the decrypted packet in the buffer */ + resetStringInfo(&gss->buf); + enlargeStringInfo(&gss->buf, output.length); + + memcpy(gss->buf.data, output.value, output.length); + gss->buf.len = output.length; + gss->buf.data[gss->buf.len] = '\0'; + + ret = read_from_buffer(gss, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +ssize_t +secure_open_gssapi(Port *port) +{ + pg_gssinfo *gss = port->gss; + bool complete_next = false; + + /* + * Use the configured keytab, if there is one. Unfortunately, Heimdal + * doesn't support the cred store extensions, so use the env var. + */ + if (pg_krb_server_keyfile != NULL && strlen(pg_krb_server_keyfile) > 0) + setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1); + + while (true) + { + OM_uint32 major, minor; + size_t ret; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + + /* Handle any outgoing data */ + if (gss->writebuf.len != 0) + { + ret = send_buffered_data(port, 1); + if (ret != 1) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + } + + if (complete_next) + break; + + /* Load incoming data */ + ret = load_packetlen(port); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + + input.length = ntohl(*(uint32*)gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0, + WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + input.value = gss->buf.data + 4; + + /* Process incoming data. (The client sends first.) */ + major = gss_accept_sec_context(&minor, &port->gss->ctx, + GSS_C_NO_CREDENTIAL, &input, + GSS_C_NO_CHANNEL_BINDINGS, + &port->gss->name, NULL, &output, NULL, + NULL, NULL); + resetStringInfo(&gss->buf); + if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, gettext_noop("GSSAPI context error"), + major, minor); + gss_release_buffer(&minor, &output); + return -1; + } + else if (!(major & GSS_S_CONTINUE_NEEDED)) + { + /* + * rfc2744 technically permits context negotiation to be complete + * both with and without a packet to be sent. + */ + complete_next = true; + } + + if (output.length != 0) + { + /* Queue packet for writing */ + uint32 netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4); + appendBinaryStringInfo(&gss->writebuf, + output.value, output.length); + gss_release_buffer(&minor, &output); + continue; + } + + /* We're done - woohoo! */ + break; + } + port->gss->enc = true; + return 0; +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index d349d7c2c7..9d48fd5dc6 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -153,6 +153,14 @@ retry: n = be_tls_read(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_read(port, ptr, len); + waitfor = WL_SOCKET_READABLE; + } + else #endif { n = secure_raw_read(port, ptr, len); @@ -256,6 +264,14 @@ retry: n = be_tls_write(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_write(port, ptr, len); + waitfor = WL_SOCKET_WRITEABLE; + } + else #endif { n = secure_raw_write(port, ptr, len); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 1a65ec87bd..457ca810d1 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -994,7 +994,9 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) } else if (strcmp(token->string, "host") == 0 || strcmp(token->string, "hostssl") == 0 || - strcmp(token->string, "hostnossl") == 0) + strcmp(token->string, "hostnossl") == 0 || + strcmp(token->string, "hostgss") == 0 || + strcmp(token->string, "hostnogss") == 0) { if (token->string[4] == 's') /* "hostssl" */ @@ -1022,10 +1024,23 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "hostssl record cannot match because SSL is not supported by this build"; #endif } - else if (token->string[4] == 'n') /* "hostnossl" */ + else if (token->string[4] == 'g') /* "hostgss" */ { - parsedline->conntype = ctHostNoSSL; + parsedline->conntype = ctHostGSS; +#ifndef ENABLE_GSS + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("hostgss record cannot match because GSSAPI is not supported by this build"), + errhint("Compile with --with-gssapi to use GSSAPI connections."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "hostgss record cannot match because GSSAPI is not supported by this build"; +#endif } + else if (token->string[4] == 'n' && token->string[6] == 's') + parsedline->conntype = ctHostNoSSL; + else if (token->string[4] == 'n' && token->string[6] == 'g') + parsedline->conntype = ctHostNoGSS; else { /* "host" */ @@ -1404,6 +1419,19 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "gssapi authentication is not supported on local sockets"; return NULL; } + if (parsedline->conntype == ctHostGSS && + parsedline->auth_method != uaGSS && + parsedline->auth_method != uaReject && + parsedline->auth_method != uaTrust) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("GSSAPI encryption only supports gss, trust, or reject authentication"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "GSSAPI encryption only supports gss, trust, or reject authenticaion"; + return NULL; + } if (parsedline->conntype != ctLocal && parsedline->auth_method == uaPeer) @@ -2060,6 +2088,17 @@ check_hba(hbaPort *port) continue; } + /* Check GSSAPI state */ +#ifdef ENABLE_GSS + if (port->gss->enc && hba->conntype == ctHostNoGSS) + continue; + else if (!port->gss->enc && hba->conntype == ctHostGSS) + continue; +#else + if (hba->conntype == ctHostGSS) + continue; +#endif + /* Check IP address */ switch (hba->ip_cmp_method) { @@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, case ctHostNoSSL: typestr = "hostnossl"; break; + case ctHostGSS: + typestr = "hostgss"; + break; + case ctHostNoGSS: + typestr = "hostnogss"; + break; } if (typestr) values[index++] = CStringGetTextDatum(typestr); diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 8a5b2b3b42..f980e4e167 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -3562,6 +3562,9 @@ pgstat_get_wait_client(WaitEventClient w) case WAIT_EVENT_WAL_SENDER_WRITE_DATA: event_name = "WalSenderWriteData"; break; + case WAIT_EVENT_GSS_OPEN_SERVER: + event_name = "GSSOpenServer"; + break; /* no default case, so that compiler will warn */ } diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 41de140ae0..d10c6dae1a 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -1897,7 +1897,7 @@ initMasks(fd_set *rmask) * if we detect a communications failure.) */ static int -ProcessStartupPacket(Port *port, bool SSLdone) +ProcessStartupPacket(Port *port, bool secure_done) { int32 len; void *buf; @@ -1908,11 +1908,11 @@ ProcessStartupPacket(Port *port, bool SSLdone) if (pq_getbytes((char *) &len, 4) == EOF) { /* - * EOF after SSLdone probably means the client didn't like our + * EOF after secure_done probably means the client didn't like our * response to NEGOTIATE_SSL_CODE. That's not an error condition, so * don't clutter the log with a complaint. */ - if (!SSLdone) + if (!secure_done) ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("incomplete startup packet"))); @@ -1964,7 +1964,7 @@ ProcessStartupPacket(Port *port, bool SSLdone) return STATUS_ERROR; } - if (proto == NEGOTIATE_SSL_CODE && !SSLdone) + if (proto == NEGOTIATE_SSL_CODE && !secure_done) { char SSLok; @@ -1997,6 +1997,32 @@ retry1: /* but not another SSL negotiation request */ return ProcessStartupPacket(port, true); } + else if (proto == NEGOTIATE_GSS_CODE && !secure_done) + { + char GSSok = 'N'; +#ifdef ENABLE_GSS + /* No GSSAPI encryption when on Unix socket */ + if (!IS_AF_UNIX(port->laddr.addr.ss_family)) + GSSok = 'G'; +#endif + + while (send(port->sock, &GSSok, 1, 0) != 1) + { + if (errno == EINTR) + continue; + ereport(COMMERROR, + (errcode_for_socket_access(), + errmsg("failed to send GSSAPI negotiation response: %m)"))); + return STATUS_ERROR; /* close the connection */ + } + +#ifdef ENABLE_GSS + if (GSSok == 'G' && secure_open_gssapi(port) == -1) + return STATUS_ERROR; +#endif + /* Won't ever see more than one negotiation request */ + return ProcessStartupPacket(port, true); + } /* Could add additional special packet types here */ @@ -2431,6 +2457,17 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif #endif return port; @@ -2447,7 +2484,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); } @@ -4779,6 +4824,17 @@ SubPostmasterMain(int argc, char *argv[]) (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif /* * If appropriate, physically re-attach to shared memory segment. We want diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 5f68f4c666..830ddaa25a 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -55,7 +55,9 @@ typedef enum ConnType ctLocal, ctHost, ctHostSSL, - ctHostNoSSL + ctHostNoSSL, + ctHostGSS, + ctHostNoGSS, } ConnType; typedef struct HbaLine diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index eb8bba4ed8..fae8db4c01 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -66,7 +66,7 @@ typedef struct #include "datatype/timestamp.h" #include "libpq/hba.h" #include "libpq/pqcomm.h" - +#include "lib/stringinfo.h" typedef enum CAC_state { @@ -86,6 +86,9 @@ 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 */ + bool enc; /* GSSAPI encryption in use */ + StringInfoData buf; /* GSSAPI encryption data buffering */ + StringInfoData writebuf; /* GSSAPI nonblocking write buffering */ #endif } pg_gssinfo; #endif @@ -284,6 +287,12 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len); #endif /* USE_SSL */ +#ifdef ENABLE_GSS +/* Read and write to a GSSAPI-encrypted connection. */ +extern ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); +extern ssize_t be_gssapi_write(Port *port, void *ptr, size_t len); +#endif /* ENABLE_GSS */ + 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 36baf6b919..0f06261099 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -93,6 +93,9 @@ extern ssize_t secure_read(Port *port, void *ptr, size_t len); extern ssize_t secure_write(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_read(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_write(Port *port, const void *ptr, size_t len); +#ifdef ENABLE_GSS +extern ssize_t secure_open_gssapi(Port *port); +#endif extern bool ssl_loaded_verify_locations; diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index cc0e0b32c7..ade1190096 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -199,9 +199,10 @@ typedef struct CancelRequestPacket /* - * A client can also start by sending a SSL negotiation request, to get a - * secure channel. + * A client can also start by sending a SSL or GSSAPI negotiation request to + * get a secure channel. */ #define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679) +#define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680) #endif /* PQCOMM_H */ diff --git a/src/include/pgstat.h b/src/include/pgstat.h index d59c24ae23..d5ce48e81e 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -787,7 +787,8 @@ typedef enum WAIT_EVENT_SSL_OPEN_SERVER, WAIT_EVENT_WAL_RECEIVER_WAIT_START, WAIT_EVENT_WAL_SENDER_WAIT_WAL, - WAIT_EVENT_WAL_SENDER_WRITE_DATA + WAIT_EVENT_WAL_SENDER_WRITE_DATA, + WAIT_EVENT_GSS_OPEN_SERVER, } WaitEventClient; /* ---------- diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index c2171d0856..238f208364 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -38,6 +38,10 @@ ifeq ($(with_openssl),yes) OBJS += fe-secure-openssl.o fe-secure-common.o endif +ifeq ($(with_gssapi),yes) +OBJS += fe-gssapi-common.o fe-secure-gssapi.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 92641fe5e9..6875ad7f79 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -49,52 +49,7 @@ * GSSAPI authentication system. */ -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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. @@ -195,10 +150,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen) static int pg_GSS_startup(PGconn *conn, int payloadlen) { - OM_uint32 maj_stat, - min_stat; - int maxlen; - gss_buffer_desc temp_gbuf; + int ret; char *host = conn->connhost[conn->whichhost].host; if (!(host && host[0] != '\0')) @@ -215,33 +167,9 @@ pg_GSS_startup(PGconn *conn, int payloadlen) return STATUS_ERROR; } - /* - * Import service principal name so the proper ticket can be acquired by - * the GSSAPI system. - */ - maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; - temp_gbuf.value = (char *) malloc(maxlen); - if (!temp_gbuf.value) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory\n")); - return STATUS_ERROR; - } - snprintf(temp_gbuf.value, maxlen, "%s@%s", - conn->krbsrvname, host); - temp_gbuf.length = strlen(temp_gbuf.value); - - maj_stat = gss_import_name(&min_stat, &temp_gbuf, - GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); - free(temp_gbuf.value); - - if (maj_stat != GSS_S_COMPLETE) - { - pg_GSS_error(libpq_gettext("GSSAPI name import error"), - conn, - maj_stat, min_stat); - return STATUS_ERROR; - } + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return ret; /* * Initial packet is the same as a continuation packet with no initial diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index d001bc513d..06f37c8a29 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, #else #define DefaultSSLMode "disable" #endif +#ifdef ENABLE_GSS +#include "fe-gssapi-common.h" +#define DefaultGSSMode "prefer" +#else +#define DefaultGSSMode "disable" +#endif /* ---------- * Definition of the conninfo parameters and their fallback resources. @@ -298,6 +304,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Require-Peer", "", 10, offsetof(struct pg_conn, requirepeer)}, + /* + * Expose gssmode similarly to sslmode - we can stil handle "disable" and + * "prefer". + */ + {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL, + "GSS-Mode", "", 7, /* sizeof("disable") == 7 */ + offsetof(struct pg_conn, gssmode)}, + #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* Kerberos and GSSAPI authentication support specifying the service name */ {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL, @@ -1222,6 +1236,39 @@ connectOptions2(PGconn *conn) goto oom_error; } + /* + * validate gssmode option + */ + if (conn->gssmode) + { + if (strcmp(conn->gssmode, "disable") != 0 && + strcmp(conn->gssmode, "prefer") != 0 && + strcmp(conn->gssmode, "require") != 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid gssmode value: \"%s\"\n"), + conn->gssmode); + return false; + } +#ifndef ENABLE_GSS + if (strcmp(conn->gssmode, "require") == 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer( + &conn->errorMessage, + libpq_gettext("no GSSAPI support; cannot require GSSAPI\n")); + return false; + } +#endif + } + else + { + conn->gssmode = strdup(DefaultGSSMode); + if (!conn->gssmode) + goto oom_error; + } + /* * Resolve special "auto" client_encoding from the locale */ @@ -1812,6 +1859,11 @@ connectDBStart(PGconn *conn) */ resetPQExpBuffer(&conn->errorMessage); +#ifdef ENABLE_GSS + if (conn->gssmode[0] == 'd') /* "disable" */ + conn->try_gss = false; +#endif + /* * Set up to try to connect to the first host. (Setting whichhost = -1 is * a bit of a cheat, but PQconnectPoll will advance it to 0 before @@ -2084,6 +2136,7 @@ PQconnectPoll(PGconn *conn) case CONNECTION_NEEDED: case CONNECTION_CHECK_WRITABLE: case CONNECTION_CONSUME: + case CONNECTION_GSS_STARTUP: break; default: @@ -2609,17 +2662,54 @@ keep_going: /* We will come back to here until there is } #endif /* HAVE_UNIX_SOCKETS */ + if (IS_AF_UNIX(conn->raddr.addr.ss_family)) + { + /* Don't request SSL or GSSAPI over Unix sockets */ #ifdef USE_SSL + conn->allow_ssl_try = false; +#endif +#ifdef ENABLE_GSS + conn->try_gss = false; +#endif + } +#ifdef ENABLE_GSS + /* + * If GSSAPI is enabled and we have a ccache, try to set it up + * before sending startup messages. If it's already + * operating, don't try SSL and instead just build the startup + * packet. + */ + if (conn->try_gss && !conn->gctx) + conn->try_gss = pg_GSS_have_ccache(&conn->gcred); + if (conn->try_gss && !conn->gctx) + { + ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE); + if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not send GSSAPI negotiation packet: %s\n"), + SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + goto error_return; + } + + /* Ok, wait for response */ + conn->status = CONNECTION_GSS_STARTUP; + return PGRES_POLLING_READING; + } + else if (!conn->gctx && conn->gssmode[0] == 'r') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache,no server support, or using a local socket)\n")); + goto error_return; + } +#endif + +#ifdef USE_SSL /* * If SSL is enabled and we haven't already got it running, * request it instead of sending the startup message. */ - if (IS_AF_UNIX(conn->raddr.addr.ss_family)) - { - /* Don't bother requesting SSL over a Unix socket */ - conn->allow_ssl_try = false; - } if (conn->allow_ssl_try && !conn->wait_ssl_try && !conn->ssl_in_use) { @@ -2813,6 +2903,97 @@ keep_going: /* We will come back to here until there is #endif /* USE_SSL */ } + case CONNECTION_GSS_STARTUP: + { +#ifdef ENABLE_GSS + PostgresPollingStatusType pollres; + + /* + * If we haven't yet, get the postmaster's response to our + * negotiation packet + */ + if (conn->try_gss && !conn->gctx) + { + char gss_ok; + int rdresult = pqReadData(conn); + if (rdresult < 0) + /* pqReadData fills in error message */ + goto error_return; + else if (rdresult == 0) + /* caller failed to wait for data */ + return PGRES_POLLING_READING; + if (pqGetc(&gss_ok, conn) < 0) + /* shouldn't happen... */ + return PGRES_POLLING_READING; + + if (gss_ok == 'E') + { + /* + * Server failure of some sort. Assume it's a + * protocol version support failure, and let's see if + * we can't recover (if it's not, we'll get a better + * error message on retry). Server gets fussy if we + * don't hang up the socket, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + + /* mark byte consumed */ + conn->inStart = conn->inCursor; + + if (gss_ok == 'N') + { + /* Server doesn't want GSSAPI; fall back if we can */ + if (conn->gssmode[0] == 'r') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n")); + goto error_return; + } + + conn->try_gss = false; + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (gss_ok != 'G') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"), + gss_ok); + goto error_return; + } + } + + /* Begin or continue GSSAPI negotiation */ + pollres = pqsecure_open_gss(conn); + if (pollres == PGRES_POLLING_OK) + { + /* All set for startup packet */ + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (pollres == PGRES_POLLING_FAILED && + conn->gssmode[0] == 'p') + { + /* + * We failed, but we can retry on "prefer". Have to drop + * the current connection to do so, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + return pollres; +#else /* !ENABLE_GSS */ + /* unreachable */ + goto error_return; +#endif /* ENABLE_GSS */ + } + /* * Handle authentication exchange: wait for postmaster messages * and respond as necessary. @@ -2966,6 +3147,24 @@ keep_going: /* We will come back to here until there is /* Check to see if we should mention pgpassfile */ pgpassfileWarning(conn); +#ifdef ENABLE_GSS + /* + * If gssmode is "prefer" and we're using GSSAPI, retry + * without it. + */ + if (conn->gssenc && conn->gssmode[0] == 'p') + { + OM_uint32 minor; + /* postmaster expects us to drop the connection */ + conn->try_gss = false; + conn->gssenc = false; + gss_delete_sec_context(&minor, &conn->gctx, NULL); + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } +#endif + #ifdef USE_SSL /* @@ -3536,6 +3735,11 @@ makeEmptyPGconn(void) conn->verbosity = PQERRORS_DEFAULT; conn->show_context = PQSHOW_CONTEXT_ERRORS; conn->sock = PGINVALID_SOCKET; +#ifdef ENABLE_GSS + conn->try_gss = true; + initPQExpBuffer(&conn->gbuf); + initPQExpBuffer(&conn->gwritebuf); +#endif /* * We try to send at least 8K at a time, which is the usual size of pipe @@ -3665,10 +3869,28 @@ freePGconn(PGconn *conn) free(conn->sslcompression); if (conn->requirepeer) free(conn->requirepeer); + if (conn->gssmode) + free(conn->gssmode); #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) if (conn->krbsrvname) free(conn->krbsrvname); #endif +#ifdef ENABLE_GSS + if (conn->gcred != GSS_C_NO_CREDENTIAL) + { + OM_uint32 minor; + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + } + if (conn->gctx) + { + OM_uint32 minor; + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = NULL; + } + termPQExpBuffer(&conn->gbuf); + termPQExpBuffer(&conn->gwritebuf); +#endif #if defined(ENABLE_GSS) && defined(ENABLE_SSPI) if (conn->gsslib) free(conn->gsslib); diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c new file mode 100644 index 0000000000..ce71b11aea --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.c @@ -0,0 +1,128 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.c + * The front-end (client) GSSAPI common code + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-gssapi-common.c + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "fe-gssapi-common.h" + +#include "libpq-int.h" +#include "pqexpbuffer.h" + +/* + * 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); +} + +/* + * Check if we can acquire credentials at all. + */ +bool +pg_GSS_have_ccache(gss_cred_id_t *cred_out) +{ + OM_uint32 major, minor; + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + + major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &cred, NULL, NULL); + if (major != GSS_S_COMPLETE) + { + *cred_out = NULL; + return false; + } + *cred_out = cred; + return true; +} + +/* + * Try to load service name for a connection + */ +int +pg_GSS_load_servicename(PGconn *conn) +{ + OM_uint32 maj_stat, min_stat; + int maxlen; + gss_buffer_desc temp_gbuf; + char *host; + + if (conn->gtarg_nam != NULL) + /* Already taken care of - move along */ + return STATUS_OK; + + host = PQhost(conn); + if (!(host && host[0] != '\0')) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("host name must be specified\n")); + return STATUS_ERROR; + } + + /* + * Import service principal name so the proper ticket can be acquired by + * the GSSAPI system. + */ + maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; + temp_gbuf.value = (char *) malloc(maxlen); + if (!temp_gbuf.value) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return STATUS_ERROR; + } + snprintf(temp_gbuf.value, maxlen, "%s@%s", + conn->krbsrvname, host); + temp_gbuf.length = strlen(temp_gbuf.value); + + maj_stat = gss_import_name(&min_stat, &temp_gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); + free(temp_gbuf.value); + + if (maj_stat != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI name import error"), + conn, + maj_stat, min_stat); + return STATUS_ERROR; + } + return STATUS_OK; +} diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h new file mode 100644 index 0000000000..58811df9f1 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.h + * + * Definitions for GSSAPI common routines + * + * Portions Copyright (c) 1996-2018, 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 + +#include "libpq-fe.h" +#include "libpq-int.h" + +void pg_GSS_error(const char *mprefix, PGconn *conn, + OM_uint32 maj_stat, OM_uint32 min_stat); +bool pg_GSS_have_ccache(gss_cred_id_t *cred_out); +int pg_GSS_load_servicename(PGconn *conn); +#endif /* FE_GSSAPI_COMMON_H */ diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c new file mode 100644 index 0000000000..427651ec33 --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,345 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-gssapi.c + * The front-end (client) encryption support for GSSAPI + * + * Portions Copyright (c) 2016-2018, 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" + +/* + * Require encryption support, as well as mutual authentication and + * tamperproofing measures. + */ +#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \ + GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG + +static ssize_t +send_buffered_data(PGconn *conn, size_t len) +{ + ssize_t ret = pqsecure_raw_write(conn, + conn->gwritebuf.data + conn->gwritecurs, + conn->gwritebuf.len - conn->gwritecurs); + if (ret < 0) + return ret; + + conn->gwritecurs += ret; + + if (conn->gwritecurs == conn->gwritebuf.len) + { + /* entire request has now been written */ + resetPQExpBuffer(&conn->gwritebuf); + conn->gwritecurs = 0; + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +ssize_t +pg_GSS_write(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + + if (conn->gwritebuf.len != 0) + return send_buffered_data(conn, len); + + /* encrypt the message */ + input.value = ptr; + input.length = len; + + major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT, + &input, &conf, &output); + if (major != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor); + goto cleanup; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI did not provide confidentiality\n")); + goto cleanup; + } + + /* 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + + ret = send_buffered_data(conn, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +static ssize_t +read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* check for available data */ + if (conn->gcursor < conn->gbuf.len) + { + /* clamp length */ + 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; + } + + /* reset buffer if all data has been read */ + if (conn->gcursor == conn->gbuf.len) + { + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + } + + return ret; +} + +static ssize_t +load_packet_length(PGconn *conn) +{ + ssize_t ret; + if (conn->gbuf.len < 4) + { + ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len); + 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 + conn->gbuf.len, + 4 - conn->gbuf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + { + /* partial read from pqsecure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +static ssize_t +load_packet(PGconn *conn, size_t len) +{ + ssize_t ret; + + ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI encrypted packet length %ld too big\n"), + len); + return -1; + } + + /* load any missing parts of the packet */ + if (conn->gbuf.len - 4 < len) + { + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + len - conn->gbuf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < len) + { + /* partial read from pqsecure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +ssize_t +pg_GSS_read(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, minor; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + ssize_t ret = 0; + int conf = 0; + + /* handle any buffered data */ + if (conn->gcursor != 0) + return read_from_buffer(conn, ptr, len); + + /* load in the packet length, if not yet loaded */ + ret = load_packet_length(conn); + if (ret < 0) + return ret; + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0) + return ret; + + /* decrypt the packet */ + input.value = conn->gbuf.data + 4; + + major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL); + if (major != GSS_S_COMPLETE) + { + 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; + } + + /* load decrypted packet into our buffer */ + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + ret = enlargePQExpBuffer(&conn->gbuf, output.length); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI decrypted packet length %ld too big\n"), + output.length); + ret = -1; + goto cleanup; + } + + memcpy(conn->gbuf.data, output.value, output.length); + conn->gbuf.len = output.length; + conn->gbuf.data[conn->gbuf.len] = '\0'; + + ret = read_from_buffer(conn, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +PostgresPollingStatusType +pqsecure_open_gss(PGconn *conn) +{ + ssize_t ret; + OM_uint32 major, minor; + uint32 netlen; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER; + + /* Send out any data we might have */ + if (conn->gwritebuf.len != 0) + { + ret = send_buffered_data(conn, 1); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_WRITING; + else if (ret == 1) + /* sent all data */ + return PGRES_POLLING_READING; + return PGRES_POLLING_FAILED; + } + + /* Client sends first, and sending creates a context */ + if (conn->gctx) + { + /* Process any incoming data we might have */ + ret = load_packet_length(conn); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + if (conn->gbuf.data[0] == 'E') + { + /* + * We can taken an error here, and it's my least favorite thing. + * How long can error messages be? That's a good question, but + * backend's pg_gss_error() caps them at 256. Do a single read + * for that and call it a day. Cope with this here rather than in + * load_packet since expandPQExpBuffer will destroy any data in + * the buffer on failure. + */ + load_packet(conn, 256); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Server error: %s"), + conn->gbuf.data + 1); + return PGRES_POLLING_FAILED; + } + + input.length = ntohl(*(uint32 *)conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + input.value = conn->gbuf.data + 4; + } + + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return PGRES_POLLING_FAILED; + + major = gss_init_sec_context(&minor, conn->gcred, &conn->gctx, + conn->gtarg_nam, GSS_C_NO_OID, + GSS_REQUIRED_FLAGS, 0, 0, &input, NULL, + &output, NULL, NULL); + resetPQExpBuffer(&conn->gbuf); + if (GSS_ERROR(major)) + { + pg_GSS_error(libpq_gettext("GSSAPI context establishment error"), + conn, major, minor); + return PGRES_POLLING_FAILED; + } + else if (output.length == 0) + { + /* + * We're done - hooray! Kind of gross, but we need to disable SSL + * here so that we don't accidentally tunnel one over the other. + */ +#ifdef USE_SSL + conn->allow_ssl_try = false; +#endif + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + conn->gssenc = true; + return PGRES_POLLING_OK; + } + + /* Queue the token for writing */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + gss_release_buffer(&minor, &output); + return PGRES_POLLING_WRITING; +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index a06fc7dc82..f4f196e3b4 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) n = pgtls_read(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_read(conn, ptr, len); + } + else #endif { n = pqsecure_raw_read(conn, ptr, len); @@ -287,7 +294,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; @@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) n = pgtls_write(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_write(conn, ptr, len); + } + else #endif { n = pqsecure_raw_write(conn, ptr, len); diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 52bd5d2cd8..158e761d0a 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -65,8 +65,9 @@ typedef enum CONNECTION_NEEDED, /* Internal state: connect() needed */ CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable * connection. */ - CONNECTION_CONSUME /* Wait for any pending message and consume + CONNECTION_CONSUME, /* Wait for any pending message and consume * them. */ + CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */ } ConnStatusType; typedef enum diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 975ab33d02..bdcb6e2523 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -22,6 +22,7 @@ /* We assume libpq-fe.h has already been included. */ #include "libpq-events.h" +#include "lib/stringinfo.h" #include <time.h> #ifndef WIN32 @@ -477,9 +478,19 @@ struct pg_conn #endif /* USE_OPENSSL */ #endif /* USE_SSL */ + char *gssmode; /* GSS mode (require,prefer,disable) */ #ifdef ENABLE_GSS gss_ctx_id_t gctx; /* GSS context */ gss_name_t gtarg_nam; /* GSS target name */ + + /* The following are encryption-only */ + 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 try_gss; /* GSS attempting permitted */ + bool gssenc; /* GSS encryption is usable */ + gss_cred_id_t gcred; /* GSS credential temp storage. */ #endif #ifdef ENABLE_SSPI @@ -655,7 +666,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); @@ -747,6 +758,23 @@ extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, int *names_examined, char **first_name); +/* === GSSAPI === */ + +#ifdef ENABLE_GSS + +/* + * Establish a GSSAPI-encrypted connection. + */ +extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn); + +/* + * Read and write functions for GSSAPI-encrypted connections, with internal + * buffering to handle nonblocking sockets. + */ +extern ssize_t pg_GSS_write(PGconn *conn, void *ptr, size_t len); +extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len); +#endif + /* === miscellaneous macros === */ /* diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 59bed3b8a8..16c13ca3b3 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -189,6 +189,11 @@ sub mkvcbuild $postgres->RemoveFile('src/backend/libpq/be-secure-common.c'); $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + $postgres->RemoveFile('src/backend/libpq/be-secure-gssapi.c'); + } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', 'src/backend/snowball'); @@ -249,6 +254,11 @@ sub mkvcbuild $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c'); $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c'); + } my $libpqwalreceiver = $solution->AddProject('libpqwalreceiver', 'dll', '', -- 2.19.0
Attachment
> On Tue, Oct 2, 2018 at 11:12 PM Robbie Harwood <rharwood@redhat.com> wrote: > > Michael Paquier <michael@paquier.xyz> writes: > > > On Mon, Aug 06, 2018 at 05:23:28PM -0400, Robbie Harwood wrote: > >> If you're in a position where you're using Kerberos (or most other > >> things from the GSSAPI) for authentication, the encryption comes at > >> little to no additional setup cost. And then you get all the security > >> benefits outlined in the docs changes. > > > > Please note that the last patch set does not apply anymore, so I have > > moved it to CF 2018-11 and marked it as waiting on author. > > Indeed. Please find v19 attached. It's just a rebase; no architecture > changes. Unfortunately, patch needs another rebase, could you do this? In the meantime I'll move it to the next CF.
Greetings Robbie, * Dmitry Dolgov (9erthalion6@gmail.com) wrote: > > On Tue, Oct 2, 2018 at 11:12 PM Robbie Harwood <rharwood@redhat.com> wrote: > > > > Michael Paquier <michael@paquier.xyz> writes: > > > > > On Mon, Aug 06, 2018 at 05:23:28PM -0400, Robbie Harwood wrote: > > >> If you're in a position where you're using Kerberos (or most other > > >> things from the GSSAPI) for authentication, the encryption comes at > > >> little to no additional setup cost. And then you get all the security > > >> benefits outlined in the docs changes. > > > > > > Please note that the last patch set does not apply anymore, so I have > > > moved it to CF 2018-11 and marked it as waiting on author. > > > > Indeed. Please find v19 attached. It's just a rebase; no architecture > > changes. > > Unfortunately, patch needs another rebase, could you do this? In the meantime > I'll move it to the next CF. This patch needs a few minor changes to get it back to working, but I'm happy to say that with those changes, it seems to be working rather well for me. I'm working on a more comprehensive review but I wanted to go ahead and get these first minor items out of the way: Needs the same treatment done in ab69ea9, for all the WaitLatchOrSocket calls in be-secure-gssapi.c: - WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0, + WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE | WL_EXIT_ON_PM_DEATH, port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER); (and I have to wonder- if we want nearly all callers of WaitLatch/WaitLatchOrSocket to use WL_EXIT_ON_PM_DEATH, maybe we should make that the default and allow it to be overridden..? Also, does ModifyWaitEvent() need to also do some checking here..? Should be_tls_read()'s waitfor settings also include WL_EXIT_ON_PM_DEATH?) Needed a minor adjustment in src/interfaces/libpq/fe-connect.c due to conflict with 6e5f8d4. Otherwise, it's building and running with all flavors of client and server-side GSS-related options (require/disable/prefer and host/hostgss/hostnogss). Not surprisingly, trying to connect from a newer psql w/ PGGSSMODE=require (explicitly requesting encryption from the server) with an older server blows up, but there's not much we can do for that. Using prefer works, with an extra roundtrip to discover the server doesn't understand GSSAPI encryption and then falling back. Also, when connecting from an older psql to a newer server, if pg_hba.conf has 'host' or 'hostnogss', everything works great (though without GSSAPI encryption, of course), while an entry of 'hostgss' returns the typical 'no pg_hba.conf entry found', as you'd expect. Just in general, it seems like the code could use a lot more comments. :) Also, it needs some pgindent run over it. That's about all I have time to cover for today, but maybe you could try to spruce up the comments (I'm at least a fan of function-level comments, in particular, explaining how they're to be used, et al..), see if you can get pgindent run over the changes and make the above-mentioned fixes and then perhaps we can get cfbot to do its thing, ask the other folks on the thread who were having issues before to retry with this patch, if possible, and I'll start doing a more thorough code review later this week. Thanks! Stephen
Attachment
On Tue, Dec 4, 2018 at 10:20 AM Stephen Frost <sfrost@snowman.net> wrote: > (and I have to wonder- if we want nearly all callers of > WaitLatch/WaitLatchOrSocket to use WL_EXIT_ON_PM_DEATH, maybe we should > make that the default and allow it to be overridden..? ... That is what I proposed. It was Heikki who talked me into the opt-in solution, but using an assertion to make sure you handle it one way or the other: https://www.postgresql.org/message-id/6417314e-93d5-ed2d-9012-8d6e9ed21778%40iki.fi Perhaps I should have sought more opinions. Please feel free to start a new thread on that if you don't like the way it was done. -- Thomas Munro http://www.enterprisedb.com
Stephen Frost <sfrost@snowman.net> writes: > Greetings Robbie, > > * Dmitry Dolgov (9erthalion6@gmail.com) wrote: >> > On Tue, Oct 2, 2018 at 11:12 PM Robbie Harwood <rharwood@redhat.com> wrote: >> > >> > Michael Paquier <michael@paquier.xyz> writes: >> > >> > > On Mon, Aug 06, 2018 at 05:23:28PM -0400, Robbie Harwood wrote: >> > >> If you're in a position where you're using Kerberos (or most other >> > >> things from the GSSAPI) for authentication, the encryption comes at >> > >> little to no additional setup cost. And then you get all the security >> > >> benefits outlined in the docs changes. >> > > >> > > Please note that the last patch set does not apply anymore, so I have >> > > moved it to CF 2018-11 and marked it as waiting on author. >> > >> > Indeed. Please find v19 attached. It's just a rebase; no architecture >> > changes. >> >> Unfortunately, patch needs another rebase, could you do this? In the meantime >> I'll move it to the next CF. > > This patch needs a few minor changes to get it back to working, but I'm > happy to say that with those changes, it seems to be working rather well > for me. Thanks Stephen! Dmitry, I will make an update (and address Stephen's feedback) hopefully soon. Thanks, --Robbie
Attachment
Hello friends, Attached please find version 20 of the GSSAPI encryption support. This has been rebased onto master (thanks Stephen for calling out ab69ea9). Other changes since v19 from Stephen's review: - About 100 lines of new comments - pgindent run over code (only the stuff I'm changing; it reports other problems on master, but if I understand correctly, that's not on me to fix here) Thanks for the feedback! And speaking of feedback, this patch is in the "Needs Review" state so if people have additional feedback, that would be appreciated. I've CC'd people who I can remember reviewing before; they should of course feel no obligation to review again if time commitments/interests do not allow. Thanks, --Robbie From 6915ae2507bf7910c5eecfbd0b84805531c16a07 Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Thu, 10 May 2018 16:12:03 -0400 Subject: [PATCH] libpq GSSAPI encryption support On both the frontend and backend, prepare for GSSAPI encryption support by moving common code for error handling into a separate file. Fix a TODO for handling multiple status messages in the process. Eliminate the OIDs, which have not been needed for some time. Add frontend and backend encryption support functions. Keep the context initiation for authentication-only separate on both the frontend and backend in order to avoid concerns about changing the requested flags to include encryption support. In postmaster, pull GSSAPI authorization checking into a shared function. Also share the initiator name between the encryption and non-encryption codepaths. Modify pqsecure_write() to take a non-const pointer. The pointer will not be modified, but this change (or a const-discarding cast, or a malloc()+memcpy()) is necessary for GSSAPI due to const/struct interactions in C. For HBA, add "hostgss" and "hostnogss" entries that behave similarly to their SSL counterparts. "hostgss" requires either "gss", "trust", or "reject" for its authentication. Simiarly, add a "gssmode" parameter to libpq. Supported values are "disable", "require", and "prefer". Notably, negotiation will only be attempted if credentials can be acquired. Move credential acquisition into its own function to support this behavior. Finally, add documentation for everything new, and update existing documentation on connection security. Thanks to Michael Paquier for the Windows fixes. --- doc/src/sgml/client-auth.sgml | 75 ++++- doc/src/sgml/libpq.sgml | 57 +++- doc/src/sgml/runtime.sgml | 77 ++++- src/backend/libpq/Makefile | 4 + src/backend/libpq/auth.c | 116 +++---- src/backend/libpq/be-gssapi-common.c | 74 +++++ src/backend/libpq/be-gssapi-common.h | 26 ++ src/backend/libpq/be-secure-gssapi.c | 374 ++++++++++++++++++++++ src/backend/libpq/be-secure.c | 16 + src/backend/libpq/hba.c | 51 ++- src/backend/postmaster/pgstat.c | 3 + src/backend/postmaster/postmaster.c | 69 ++++- src/include/libpq/hba.h | 4 +- src/include/libpq/libpq-be.h | 13 +- src/include/libpq/libpq.h | 3 + src/include/libpq/pqcomm.h | 5 +- src/include/pgstat.h | 3 +- src/interfaces/libpq/Makefile | 4 + src/interfaces/libpq/fe-auth.c | 82 +---- src/interfaces/libpq/fe-connect.c | 241 ++++++++++++++- src/interfaces/libpq/fe-gssapi-common.c | 130 ++++++++ src/interfaces/libpq/fe-gssapi-common.h | 23 ++ src/interfaces/libpq/fe-secure-gssapi.c | 394 ++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 16 +- src/interfaces/libpq/libpq-fe.h | 3 +- src/interfaces/libpq/libpq-int.h | 30 +- src/tools/msvc/Mkvcbuild.pm | 10 + 27 files changed, 1707 insertions(+), 196 deletions(-) create mode 100644 src/backend/libpq/be-gssapi-common.c create mode 100644 src/backend/libpq/be-gssapi-common.h create mode 100644 src/backend/libpq/be-secure-gssapi.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.c create mode 100644 src/interfaces/libpq/fe-gssapi-common.h 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 c2114021c3..6f9f2b7560 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> +hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> </synopsis> The meaning of the fields is as follows: @@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> This record matches connection attempts made using TCP/IP. - <literal>host</literal> records match either + <literal>host</literal> records match <acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection - attempts. + attempts as well as <acronym>GSSAPI</acronym> or + non-<acronym>GSSAPI</acronym> connection attempts. </para> <note> <para> @@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><literal>hostgss</literal></term> + <listitem> + <para> + This record matches connection attempts made using TCP/IP, + but only when the connection is made with <acronym>GSSAPI</acronym> + encryption. + </para> + + <para> + To make use of this option the server must be built with + <acronym>GSSAPI</acronym> support. Otherwise, + the <literal>hostgss</literal> record is ignored except for logging a + warning that it cannot match any connections. + </para> + + <para> + Note that the only supported <xref linkend="auth-methods"/> for use + with <acronym>GSSAPI</acronym> encryption + are <literal>gss</literal>, <literal>reject</literal>, + and <literal>trust</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>hostnogss</literal></term> + <listitem> + <para> + This record type has the opposite behavior of <literal>hostgss</literal>; + it only matches connection attempts made over + TCP/IP that do not use <acronym>GSSAPI</acronym>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable>database</replaceable></term> <listitem> @@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <listitem> <para> Use GSSAPI to authenticate the user. This is only - available for TCP/IP connections. See <xref - linkend="gssapi-auth"/> for details. + available for TCP/IP connections . See <xref + linkend="gssapi-auth"/> for details. It can be used in conjunction + with GSSAPI encryption. </para> </listitem> </varlistentry> @@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256 host all mike .example.com md5 host all all .example.com scram-sha-256 -# In the absence of preceding "host" lines, these two lines will +# In the absence of preceding "host" lines, these three lines will # reject all connections from 192.168.54.1 (since that entry will be -# matched first), but allow GSSAPI connections from anywhere else -# on the Internet. The zero mask causes no bits of the host IP +# matched first), but allow GSSAPI-encrypted connections from anywhere else +# on the Internet. Unencrypted GSSAPI connections are allowed from +# 192.168.12.10 only. The zero mask causes no bits of the host IP # address to be considered, so it matches any host. # # TYPE DATABASE USER ADDRESS METHOD host all all 192.168.54.1/32 reject -host all all 0.0.0.0/0 gss +hostgss all all 0.0.0.0/0 gss +host all all 192.168.12.10/32 gss # Allow users from 192.168.x.x hosts to connect to any database, if # they pass the ident check. If, for example, ident says the user is @@ -1051,13 +1093,16 @@ omicron bryanh guest1 <para> <productname>GSSAPI</productname> is an industry-standard protocol for secure authentication defined in RFC 2743. - <productname>PostgreSQL</productname> supports - <productname>GSSAPI</productname> with <productname>Kerberos</productname> - authentication according to RFC 1964. <productname>GSSAPI</productname> - 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. + + <productname>PostgreSQL</productname> + supports <productname>GSSAPI</productname> for use as either an encrypted, + authenticated layer, or as authentication + only. <productname>GSSAPI</productname> provides automatic authentication + (single sign-on) for systems that support it. The authentication itself is + secure. If <productname>GSSAPI</productname> encryption + (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are + used, the data sent along the database connection will be encrypted; + otherwise, it will not. </para> <para> diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index d2e5b08541..27896a2f35 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1316,6 +1316,61 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode"> + <term><literal>gssmode</literal></term> + <listitem> + <para> + This option determines whether or with what priority a secure + <acronym>GSS</acronym> TCP/IP connection will be negotiated with the + server. There are three modes: + + <variablelist> + <varlistentry> + <term><literal>disable</literal></term> + <listitem> + <para> + only try a non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>prefer</literal> (default)</term> + <listitem> + <para> + if there are <acronym>GSSAPI</acronym> credentials present (i.e., + in a credentials cache), first try + a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or + there are no credentials, try a + non-<acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>require</literal></term> + <listitem> + <para> + only try a <acronym>GSSAPI</acronym>-encrypted connection + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + <literal>gssmode</literal> is ignored for Unix domain socket + communication. If <productname>PostgreSQL</productname> is compiled + without GSSAPI support, using the <literal>require</literal> option + will cause an error, while <literal>prefer</literal> will be accepted + but <application>libpq</application> will not actually attempt + a <acronym>GSSAPI</acronym>-encrypted + connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with + libpq</secondary></indexterm> + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-sslmode" xreflabel="sslmode"> <term><literal>sslmode</literal></term> <listitem> @@ -7948,7 +8003,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*) </para> <para> - For a connection to be known secure, SSL usage must be configured + For a connection to be known SSL-secured, SSL usage must be configured on <emphasis>both the client and the server</emphasis> before the connection is made. If it is only configured on the server, the client may end up sending sensitive information (e.g. passwords) before diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 8d9d40664b..ac9c882d44 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 </para> <para> - To prevent spoofing on TCP connections, the best solution is to use - SSL certificates and make sure that clients check the server's certificate. - To do that, the server + To prevent spoofing on TCP connections, either use + SSL certificates and make sure that clients check the server's certificate, + or use GSSAPI encryption (or both, if they're on separate connections). + </para> + + <para> + To prevent spoofing with SSL, the server must be configured to accept only <literal>hostssl</literal> connections (<xref linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files (<xref linkend="ssl-tcp"/>). The TCP client must connect using @@ -2018,6 +2022,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 <literal>verify-full</literal> and have the appropriate root certificate file installed (<xref linkend="libq-ssl-certificates"/>). </para> + + <para> + To prevent spoofing with GSSAPI, the server must be configured to accept + only <literal>hostgss</literal> connections + (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal> + authentication with them. The TCP client must connect + using <literal>gssmode=require</literal>. + </para> </sect1> <sect1 id="encryption-options"> @@ -2114,8 +2126,24 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 which hosts can use non-encrypted connections (<literal>host</literal>) and which require SSL-encrypted connections (<literal>hostssl</literal>). Also, clients can specify that they - connect to servers only via SSL. <application>Stunnel</application> or - <application>SSH</application> can also be used to encrypt transmissions. + connect to servers only via SSL. + </para> + + <para> + GSSAPI-encrypted connections encrypt all data sent across the network, + including queries and data returned. (No password is sent across the + network.) The <filename>pg_hba.conf</filename> file allows + administrators to specify which hosts can use non-encrypted connections + (<literal>host</literal>) and which require GSSAPI-encrypted connections + (<literal>hostgss</literal>). Also, clients can specify that they + connect to servers only on GSSAPI-encrypted connections + (<literal>gssmode=require</literal>). + </para> + + <para> + <application>Stunnel</application> or + <application>SSH</application> can also be used to encrypt + transmissions. </para> </listitem> </varlistentry> @@ -2504,6 +2532,45 @@ openssl x509 -req -in server.csr -text -days 365 \ </sect1> + <sect1 id="gssapi-enc"> + <title>Secure TCP/IP Connections with GSSAPI encryption</title> + + <indexterm zone="gssapi-enc"> + <primary>gssapi</primary> + </indexterm> + + <para> + <productname>PostgreSQL</productname> also has native support for + using <acronym>GSSAPI</acronym> to encrypt client/server communications for + increased security. Support requires that a <acronym>GSSAPI</acronym> + implementation (such as MIT krb5) is installed on both client and server + systems, and that support in <productname>PostgreSQL</productname> is + enabled at build time (see <xref linkend="installation"/>). + </para> + + <sect2 id="gssapi-setup"> + <title>Basic Setup</title> + + <para> + The <productname>PostgreSQL</productname> server will listen for both + normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP + port, and will negotiate with any connecting client on whether to + use <acronym>GSSAPI</acronym> for encryption (and for authentication). By + default, this decision is up to the client (which means it can be + downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about + setting up the server to require the use of <acronym>GSSAPI</acronym> for + some or all conections. + </para> + + <para> + Other than configuration of the negotiation + behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond + that which necessary for GSSAPI authentication. (For more information on + configuring that, see <xref linkend="gssapi-auth"/>.) + </para> + </sect2> + </sect1> + <sect1 id="ssh-tunnels"> <title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title> diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 3dbec23e30..47efef0682 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 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 ff0832dba8..1027b0dae7 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -173,12 +173,9 @@ bool pg_krb_caseins_users; *---------------------------------------------------------------- */ #ifdef ENABLE_GSS -#if defined(HAVE_GSSAPI_H) -#include <gssapi.h> -#else -#include <gssapi/gssapi.h> -#endif +#include "be-gssapi-common.h" +static int pg_GSS_checkauth(Port *port); static int pg_GSS_recvauth(Port *port); #endif /* ENABLE_GSS */ @@ -384,6 +381,17 @@ ClientAuthentication(Port *port) errmsg("connection requires a valid client certificate"))); } +#ifdef ENABLE_GSS + if (port->gss->enc && port->hba->auth_method != uaReject && + port->hba->auth_method != uaImplicitReject && + port->hba->auth_method != uaTrust && + port->hba->auth_method != uaGSS) + { + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("GSSAPI encryption cannot be combined with non-GSSAPI authentication"))); + } +#endif + /* * Now proceed to do the actual authentication check */ @@ -524,8 +532,13 @@ ClientAuthentication(Port *port) case uaGSS: #ifdef ENABLE_GSS - sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); - status = pg_GSS_recvauth(port); + if (port->gss->enc) + status = pg_GSS_checkauth(port); + else + { + sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0); + status = pg_GSS_recvauth(port); + } #else Assert(false); #endif @@ -1024,68 +1037,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) *---------------------------------------------------------------- */ #ifdef ENABLE_GSS - -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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 - - -/* - * Generate an error for GSSAPI authentication. The caller should apply - * _() to errmsg to make it translatable. - */ -static void -pg_GSS_error(int severity, const 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) { @@ -1094,7 +1045,6 @@ pg_GSS_recvauth(Port *port) lmin_s, gflags; int mtype; - int ret; StringInfoData buf; gss_buffer_desc gbuf; @@ -1247,10 +1197,22 @@ pg_GSS_recvauth(Port *port) */ gss_release_cred(&min_stat, &port->gss->cred); } + return pg_GSS_checkauth(port); +} + +/* + * Check whether the GSSAPI-authenticated user is allowed to connect as the + * claimed username. + */ +static int +pg_GSS_checkauth(Port *port) +{ + int ret; + OM_uint32 maj_stat, + min_stat; + gss_buffer_desc gbuf; /* - * GSS_S_COMPLETE indicates that authentication is now complete. - * * Get the name of the user that authenticated, and compare it to the pg * username that was specified for the connection. */ @@ -1292,7 +1254,7 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI realm (%s) and configured realm (%s) don't match", cp, port->hba->krb_realm); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } } @@ -1302,14 +1264,14 @@ pg_GSS_recvauth(Port *port) elog(DEBUG2, "GSSAPI did not return realm but realm matching was requested"); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return STATUS_ERROR; } ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, pg_krb_caseins_users); - gss_release_buffer(&lmin_s, &gbuf); + gss_release_buffer(&min_stat, &gbuf); return ret; } @@ -2366,8 +2328,8 @@ InitializeLDAPConnection(Port *port, LDAP **ldap) char *uris = NULL; /* - * We have a space-separated list of hostnames. Convert it - * to a space-separated list of URIs. + * We have a space-separated list of hostnames. Convert it to a + * space-separated list of URIs. */ do { diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c new file mode 100644 index 0000000000..40ada14bdd --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.c @@ -0,0 +1,74 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.c + * Common code for GSSAPI authentication and encryption + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/libpq/be-gssapi-common.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +/* + * Helper function for getting all strings of a GSSAPI error (of specified + * stat). Call once for GSS_CODE and once for MECH_CODE. + */ +static void +pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type) +{ + gss_buffer_desc gmsg; + size_t i = 0; + OM_uint32 lmin_s, + msg_ctx = 0; + + gmsg.value = NULL; + gmsg.length = 0; + + do + { + gss_display_status(&lmin_s, stat, type, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(s + i, gmsg.value, len - i); + i += gmsg.length; + gss_release_buffer(&lmin_s, &gmsg); + } + while (msg_ctx && i < len); + + if (msg_ctx || i == len) + ereport(WARNING, + (errmsg_internal("incomplete GSS error report"))); +} + +/* + * Fetch and report all error messages from GSSAPI. To avoid allocation, + * total error size is capped (at 128 bytes for each of major and minor). No + * known mechanisms will produce error messages beyond this cap. + */ +void +pg_GSS_error(int severity, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat) +{ + char msg_major[128], + msg_minor[128]; + + /* Fetch major status message */ + pg_GSS_error_int(msg_major, sizeof(msg_major), maj_stat, GSS_C_GSS_CODE); + + /* Fetch mechanism minor status message */ + pg_GSS_error_int(msg_minor, sizeof(msg_minor), min_stat, GSS_C_MECH_CODE); + + /* + * 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/backend/libpq/be-gssapi-common.h b/src/backend/libpq/be-gssapi-common.h new file mode 100644 index 0000000000..f6e90ea3a7 --- /dev/null +++ b/src/backend/libpq/be-gssapi-common.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * be-gssapi-common.h + * Definitions for GSSAPI authentication and encryption handling + * + * Portions Copyright (c) 1996-2018, 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, const char *errmsg, + OM_uint32 maj_stat, OM_uint32 min_stat); + +#endif /* BE_GSSAPI_COMMON_H */ diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c new file mode 100644 index 0000000000..e58efc37c3 --- /dev/null +++ b/src/backend/libpq/be-secure-gssapi.c @@ -0,0 +1,374 @@ +/*------------------------------------------------------------------------- + * + * be-secure-gssapi.c + * GSSAPI encryption support + * + * Portions Copyright (c) 2018-2018, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-gssapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "be-gssapi-common.h" + +#include "libpq/auth.h" +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "libpq/pqformat.h" +#include "miscadmin.h" +#include "pgstat.h" + +#include <unistd.h> + +/* + * Helper for sending data. Assumes data is present to send. Returns len + * when the buffer is empty; otherwise, it yields the underlying problem and + * sets errno. + */ +static ssize_t +send_buffered_data(Port *port, size_t len) +{ + ssize_t ret = secure_raw_write( + port, + port->gss->writebuf.data + port->gss->writebuf.cursor, + port->gss->writebuf.len - port->gss->writebuf.cursor); + + if (ret < 0) + return ret; + + /* update and possibly clear buffer state */ + port->gss->writebuf.cursor += ret; + + if (port->gss->writebuf.cursor == port->gss->writebuf.len) + { + /* entire request has now been written */ + resetStringInfo(&port->gss->writebuf); + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +/* + * Write len bytes of data from ptr along a GSSAPI-encrypted connection. + * Connection must be fully established (including authentication step) before + * calling. Returns len once complete. Data is internally buffered; in the + * case of an incomplete write, a negative value will be returned and errno + * set appropriately. To continue writing in the case of EWOULDBLOCK and + * similar, call this function again with matching ptr and len parameters. + */ +ssize_t +be_gssapi_write(Port *port, void *ptr, size_t len) +{ + OM_uint32 major, + minor; + gss_buffer_desc input, + output; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + pg_gssinfo *gss = port->gss; + + if (gss->writebuf.len != 0) + return send_buffered_data(port, len); + + /* encrypt the message */ + output.value = NULL; + output.length = 0; + input.value = ptr; + input.length = len; + + major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT, + &input, &conf, &output); + if (major != GSS_S_COMPLETE) + { + pg_GSS_error(ERROR, gettext_noop("GSSAPI wrap error"), major, minor); + goto cleanup; + } + else if (conf == 0) + { + ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality"))); + goto cleanup; + } + + /* 4 network-order length bytes, then payload */ + netlen = htonl(output.length); + appendBinaryStringInfo(&gss->writebuf, (char *) &netlen, 4); + appendBinaryStringInfo(&gss->writebuf, output.value, output.length); + + ret = send_buffered_data(port, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +/* + * Helper function to copy up to len bytes of data from the read buffer into + * ptr. Returns number of bytes copied this way. + */ +static ssize_t +read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* load up any available data */ + if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len) + { + /* clamp length */ + if (len > gss->buf.len - gss->buf.cursor) + len = gss->buf.len - gss->buf.cursor; + + memcpy(ptr, gss->buf.data + gss->buf.cursor, len); + gss->buf.cursor += len; + ret = len; + } + + /* reset buffer if all data has been read */ + if (gss->buf.cursor == gss->buf.len) + resetStringInfo(&gss->buf); + + return ret; +} + +/* + * Helper function to ensure the packet length (i.e., first four bytes of + * packet) have been read into the read buffer. Returns 0 on success, or -1 + * with errno set otherwise. Read buffer is transparently resized if needed. + */ +static ssize_t +load_packetlen(Port *port) +{ + pg_gssinfo *gss = port->gss; + ssize_t ret; + + if (gss->buf.len < 4) + { + enlargeStringInfo(&gss->buf, 4 - gss->buf.len); + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + 4 - gss->buf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len < 4) + { + /* partial read from secure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +/* + * Helper function to read the entire packet (len+4 bytes) into the read + * buffer. Returns 0 on success, or -1 with errno set otherwise. Read buffer + * is transparently resized if needed; resizing accounts for problematically + * large size requests. + */ +static ssize_t +load_packet(Port *port, size_t len) +{ + ssize_t ret; + pg_gssinfo *gss = port->gss; + + enlargeStringInfo(&gss->buf, len - gss->buf.len + 4); + + ret = secure_raw_read(port, gss->buf.data + gss->buf.len, + len - gss->buf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + gss->buf.len += ret; + gss->buf.data[gss->buf.len] = '\0'; + if (gss->buf.len - 4 < len) + { + /* partial read from secure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + return 0; +} + +/* + * Read up to len bytes from a GSSAPI-encrypted connection into ptr. Call + * only after the connection has been fully established (i.e., GSSAPI + * authentication is complete). On success, returns the number of bytes + * written into ptr; otherwise, return -1 and set errno appropriately. + */ +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; + pg_gssinfo *gss = port->gss; + + if (gss->buf.cursor > 0) + return read_from_buffer(gss, ptr, len); + + /* load length if not present */ + ret = load_packetlen(port); + if (ret != 0) + return ret; + + input.length = ntohl(*(uint32 *) gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + return ret; + + /* decrypt the packet */ + output.value = NULL; + output.length = 0; + input.value = gss->buf.data + 4; + + major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf, NULL); + if (major != GSS_S_COMPLETE) + { + 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; + } + + /* put the decrypted packet in the buffer */ + resetStringInfo(&gss->buf); + enlargeStringInfo(&gss->buf, output.length); + + memcpy(gss->buf.data, output.value, output.length); + gss->buf.len = output.length; + gss->buf.data[gss->buf.len] = '\0'; + + ret = read_from_buffer(gss, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +/* + * Start up a GSSAPI-encrypted connection. This performs GSSAPI + * authentication; after this function completes, it is safe to call + * be_gssapi_read and be_gssapi_write. Returns -1 and logs on failure; + * otherwise, returns 0 and marks the connection as ready for GSSAPI + * encryption. + */ +ssize_t +secure_open_gssapi(Port *port) +{ + pg_gssinfo *gss = port->gss; + bool complete_next = false; + + /* + * Use the configured keytab, if there is one. Unfortunately, Heimdal + * doesn't support the cred store extensions, so use the env var. + */ + if (pg_krb_server_keyfile != NULL && strlen(pg_krb_server_keyfile) > 0) + setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1); + + while (true) + { + OM_uint32 major, + minor; + size_t ret; + gss_buffer_desc input, + output = GSS_C_EMPTY_BUFFER; + + /* Handle any outgoing data */ + if (gss->writebuf.len != 0) + { + ret = send_buffered_data(port, 1); + if (ret != 1) + { + WaitLatchOrSocket(MyLatch, + WL_SOCKET_WRITEABLE | WL_EXIT_ON_PM_DEATH, + port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + } + + if (complete_next) + break; + + /* Load incoming data */ + ret = load_packetlen(port); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, + WL_SOCKET_READABLE | WL_EXIT_ON_PM_DEATH, + port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + + input.length = ntohl(*(uint32 *) gss->buf.data); + ret = load_packet(port, input.length); + if (ret != 0) + { + WaitLatchOrSocket(MyLatch, + WL_SOCKET_READABLE | WL_EXIT_ON_PM_DEATH, + port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER); + continue; + } + input.value = gss->buf.data + 4; + + /* Process incoming data. (The client sends first.) */ + major = gss_accept_sec_context(&minor, &port->gss->ctx, + GSS_C_NO_CREDENTIAL, &input, + GSS_C_NO_CHANNEL_BINDINGS, + &port->gss->name, NULL, &output, NULL, + NULL, NULL); + resetStringInfo(&gss->buf); + if (GSS_ERROR(major)) + { + pg_GSS_error(ERROR, gettext_noop("GSSAPI context error"), + major, minor); + gss_release_buffer(&minor, &output); + return -1; + } + else if (!(major & GSS_S_CONTINUE_NEEDED)) + { + /* + * rfc2744 technically permits context negotiation to be complete + * both with and without a packet to be sent. + */ + complete_next = true; + } + + if (output.length != 0) + { + /* Queue packet for writing */ + uint32 netlen = htonl(output.length); + + appendBinaryStringInfo(&gss->writebuf, (char *) &netlen, 4); + appendBinaryStringInfo(&gss->writebuf, + output.value, output.length); + gss_release_buffer(&minor, &output); + continue; + } + + /* We're done - woohoo! */ + break; + } + port->gss->enc = true; + return 0; +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 3620b8ce8c..300fd0f071 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -159,6 +159,14 @@ retry: n = be_tls_read(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_read(port, ptr, len); + waitfor = WL_SOCKET_READABLE; + } + else #endif { n = secure_raw_read(port, ptr, len); @@ -264,6 +272,14 @@ retry: n = be_tls_write(port, ptr, len, &waitfor); } else +#endif +#ifdef ENABLE_GSS + if (port->gss->enc) + { + n = be_gssapi_write(port, ptr, len); + waitfor = WL_SOCKET_WRITEABLE; + } + else #endif { n = secure_raw_write(port, ptr, len); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 0129dd24d0..943680dc5e 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -994,7 +994,9 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) } else if (strcmp(token->string, "host") == 0 || strcmp(token->string, "hostssl") == 0 || - strcmp(token->string, "hostnossl") == 0) + strcmp(token->string, "hostnossl") == 0 || + strcmp(token->string, "hostgss") == 0 || + strcmp(token->string, "hostnogss") == 0) { if (token->string[4] == 's') /* "hostssl" */ @@ -1022,10 +1024,23 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "hostssl record cannot match because SSL is not supported by this build"; #endif } - else if (token->string[4] == 'n') /* "hostnossl" */ + else if (token->string[4] == 'g') /* "hostgss" */ { - parsedline->conntype = ctHostNoSSL; + parsedline->conntype = ctHostGSS; +#ifndef ENABLE_GSS + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("hostgss record cannot match because GSSAPI is not supported by this build"), + errhint("Compile with --with-gssapi to use GSSAPI connections."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "hostgss record cannot match because GSSAPI is not supported by this build"; +#endif } + else if (token->string[4] == 'n' && token->string[6] == 's') + parsedline->conntype = ctHostNoSSL; + else if (token->string[4] == 'n' && token->string[6] == 'g') + parsedline->conntype = ctHostNoGSS; else { /* "host" */ @@ -1404,6 +1419,19 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) *err_msg = "gssapi authentication is not supported on local sockets"; return NULL; } + if (parsedline->conntype == ctHostGSS && + parsedline->auth_method != uaGSS && + parsedline->auth_method != uaReject && + parsedline->auth_method != uaTrust) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("GSSAPI encryption only supports gss, trust, or reject authentication"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "GSSAPI encryption only supports gss, trust, or reject authenticaion"; + return NULL; + } if (parsedline->conntype != ctLocal && parsedline->auth_method == uaPeer) @@ -2060,6 +2088,17 @@ check_hba(hbaPort *port) continue; } + /* Check GSSAPI state */ +#ifdef ENABLE_GSS + if (port->gss->enc && hba->conntype == ctHostNoGSS) + continue; + else if (!port->gss->enc && hba->conntype == ctHostGSS) + continue; +#else + if (hba->conntype == ctHostGSS) + continue; +#endif + /* Check IP address */ switch (hba->ip_cmp_method) { @@ -2396,6 +2435,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, case ctHostNoSSL: typestr = "hostnossl"; break; + case ctHostGSS: + typestr = "hostgss"; + break; + case ctHostNoGSS: + typestr = "hostnogss"; + break; } if (typestr) values[index++] = CStringGetTextDatum(typestr); diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 8676088e57..86514f7ed2 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -3558,6 +3558,9 @@ pgstat_get_wait_client(WaitEventClient w) case WAIT_EVENT_WAL_SENDER_WRITE_DATA: event_name = "WalSenderWriteData"; break; + case WAIT_EVENT_GSS_OPEN_SERVER: + event_name = "GSSOpenServer"; + break; /* no default case, so that compiler will warn */ } diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index eedc617db4..ad223bc681 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -1899,9 +1899,12 @@ initMasks(fd_set *rmask) * if that's what you want. Return STATUS_ERROR if you don't want to * send anything to the client, which would typically be appropriate * if we detect a communications failure.) + * + * Set secure_done when negotiation of an encrypted layer (currently, TLS or + * GSSAPI) is already completed. */ static int -ProcessStartupPacket(Port *port, bool SSLdone) +ProcessStartupPacket(Port *port, bool secure_done) { int32 len; void *buf; @@ -1912,11 +1915,11 @@ ProcessStartupPacket(Port *port, bool SSLdone) if (pq_getbytes((char *) &len, 4) == EOF) { /* - * EOF after SSLdone probably means the client didn't like our + * EOF after secure_done probably means the client didn't like our * response to NEGOTIATE_SSL_CODE. That's not an error condition, so * don't clutter the log with a complaint. */ - if (!SSLdone) + if (!secure_done) ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("incomplete startup packet"))); @@ -1968,7 +1971,7 @@ ProcessStartupPacket(Port *port, bool SSLdone) return STATUS_ERROR; } - if (proto == NEGOTIATE_SSL_CODE && !SSLdone) + if (proto == NEGOTIATE_SSL_CODE && !secure_done) { char SSLok; @@ -2001,6 +2004,32 @@ retry1: /* but not another SSL negotiation request */ return ProcessStartupPacket(port, true); } + else if (proto == NEGOTIATE_GSS_CODE && !secure_done) + { + char GSSok = 'N'; +#ifdef ENABLE_GSS + /* No GSSAPI encryption when on Unix socket */ + if (!IS_AF_UNIX(port->laddr.addr.ss_family)) + GSSok = 'G'; +#endif + + while (send(port->sock, &GSSok, 1, 0) != 1) + { + if (errno == EINTR) + continue; + ereport(COMMERROR, + (errcode_for_socket_access(), + errmsg("failed to send GSSAPI negotiation response: %m)"))); + return STATUS_ERROR; /* close the connection */ + } + +#ifdef ENABLE_GSS + if (GSSok == 'G' && secure_open_gssapi(port) == -1) + return STATUS_ERROR; +#endif + /* Won't ever see more than one negotiation request */ + return ProcessStartupPacket(port, true); + } /* Could add additional special packet types here */ @@ -2435,6 +2464,18 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif #endif return port; @@ -2451,7 +2492,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); } @@ -4790,6 +4839,18 @@ SubPostmasterMain(int argc, char *argv[]) (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); #endif +#ifdef ENABLE_GSS + { + MemoryContext save = CurrentMemoryContext; + + CurrentMemoryContext = TopMemoryContext; + + initStringInfo(&port->gss->buf); + initStringInfo(&port->gss->writebuf); + + CurrentMemoryContext = save; + } +#endif /* * If appropriate, physically re-attach to shared memory segment. We want diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 5f68f4c666..830ddaa25a 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -55,7 +55,9 @@ typedef enum ConnType ctLocal, ctHost, ctHostSSL, - ctHostNoSSL + ctHostNoSSL, + ctHostGSS, + ctHostNoGSS, } ConnType; typedef struct HbaLine diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index b2c59df54e..5d4ffee1e5 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -66,7 +66,7 @@ typedef struct #include "datatype/timestamp.h" #include "libpq/hba.h" #include "libpq/pqcomm.h" - +#include "lib/stringinfo.h" typedef enum CAC_state { @@ -86,6 +86,9 @@ 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 */ + bool enc; /* GSSAPI encryption in use */ + StringInfoData buf; /* GSSAPI encryption data buffering */ + StringInfoData writebuf; /* GSSAPI nonblocking write buffering */ #endif } pg_gssinfo; #endif @@ -275,7 +278,13 @@ 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 */ + +#ifdef ENABLE_GSS +/* Read and write to a GSSAPI-encrypted connection. */ +extern ssize_t be_gssapi_read(Port *port, void *ptr, size_t len); +extern ssize_t be_gssapi_write(Port *port, void *ptr, size_t len); +#endif /* ENABLE_GSS */ extern ProtocolVersion FrontendProtocol; diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index e81752446e..7e5a8c7fac 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -93,6 +93,9 @@ extern ssize_t secure_read(Port *port, void *ptr, size_t len); extern ssize_t secure_write(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_read(Port *port, void *ptr, size_t len); extern ssize_t secure_raw_write(Port *port, const void *ptr, size_t len); +#ifdef ENABLE_GSS +extern ssize_t secure_open_gssapi(Port *port); +#endif extern bool ssl_loaded_verify_locations; diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index cc0e0b32c7..ade1190096 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -199,9 +199,10 @@ typedef struct CancelRequestPacket /* - * A client can also start by sending a SSL negotiation request, to get a - * secure channel. + * A client can also start by sending a SSL or GSSAPI negotiation request to + * get a secure channel. */ #define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679) +#define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680) #endif /* PQCOMM_H */ diff --git a/src/include/pgstat.h b/src/include/pgstat.h index f1c10d16b8..3771bc18df 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -787,7 +787,8 @@ typedef enum WAIT_EVENT_SSL_OPEN_SERVER, WAIT_EVENT_WAL_RECEIVER_WAIT_START, WAIT_EVENT_WAL_SENDER_WAIT_WAL, - WAIT_EVENT_WAL_SENDER_WRITE_DATA + WAIT_EVENT_WAL_SENDER_WRITE_DATA, + WAIT_EVENT_GSS_OPEN_SERVER, } WaitEventClient; /* ---------- diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index c2171d0856..238f208364 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -38,6 +38,10 @@ ifeq ($(with_openssl),yes) OBJS += fe-secure-openssl.o fe-secure-common.o endif +ifeq ($(with_gssapi),yes) +OBJS += fe-gssapi-common.o fe-secure-gssapi.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 92641fe5e9..6875ad7f79 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -49,52 +49,7 @@ * GSSAPI authentication system. */ -#if defined(WIN32) && !defined(_MSC_VER) -/* - * 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. @@ -195,10 +150,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen) static int pg_GSS_startup(PGconn *conn, int payloadlen) { - OM_uint32 maj_stat, - min_stat; - int maxlen; - gss_buffer_desc temp_gbuf; + int ret; char *host = conn->connhost[conn->whichhost].host; if (!(host && host[0] != '\0')) @@ -215,33 +167,9 @@ pg_GSS_startup(PGconn *conn, int payloadlen) return STATUS_ERROR; } - /* - * Import service principal name so the proper ticket can be acquired by - * the GSSAPI system. - */ - maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; - temp_gbuf.value = (char *) malloc(maxlen); - if (!temp_gbuf.value) - { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory\n")); - return STATUS_ERROR; - } - snprintf(temp_gbuf.value, maxlen, "%s@%s", - conn->krbsrvname, host); - temp_gbuf.length = strlen(temp_gbuf.value); - - maj_stat = gss_import_name(&min_stat, &temp_gbuf, - GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); - free(temp_gbuf.value); - - if (maj_stat != GSS_S_COMPLETE) - { - pg_GSS_error(libpq_gettext("GSSAPI name import error"), - conn, - maj_stat, min_stat); - return STATUS_ERROR; - } + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return ret; /* * Initial packet is the same as a continuation packet with no initial diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index bc456fec0c..419a067b7a 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, #else #define DefaultSSLMode "disable" #endif +#ifdef ENABLE_GSS +#include "fe-gssapi-common.h" +#define DefaultGSSMode "prefer" +#else +#define DefaultGSSMode "disable" +#endif /* ---------- * Definition of the conninfo parameters and their fallback resources. @@ -298,6 +304,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Require-Peer", "", 10, offsetof(struct pg_conn, requirepeer)}, + /* + * Expose gssmode similarly to sslmode - we can stil handle "disable" and + * "prefer". + */ + {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL, + "GSS-Mode", "", 7, /* sizeof("disable") == 7 */ + offsetof(struct pg_conn, gssmode)}, + #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* Kerberos and GSSAPI authentication support specifying the service name */ {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL, @@ -1222,6 +1236,39 @@ connectOptions2(PGconn *conn) goto oom_error; } + /* + * validate gssmode option + */ + if (conn->gssmode) + { + if (strcmp(conn->gssmode, "disable") != 0 && + strcmp(conn->gssmode, "prefer") != 0 && + strcmp(conn->gssmode, "require") != 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid gssmode value: \"%s\"\n"), + conn->gssmode); + return false; + } +#ifndef ENABLE_GSS + if (strcmp(conn->gssmode, "require") == 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer( + &conn->errorMessage, + libpq_gettext("no GSSAPI support; cannot require GSSAPI\n")); + return false; + } +#endif + } + else + { + conn->gssmode = strdup(DefaultGSSMode); + if (!conn->gssmode) + goto oom_error; + } + /* * Resolve special "auto" client_encoding from the locale */ @@ -1823,6 +1870,11 @@ connectDBStart(PGconn *conn) */ resetPQExpBuffer(&conn->errorMessage); +#ifdef ENABLE_GSS + if (conn->gssmode[0] == 'd') /* "disable" */ + conn->try_gss = false; +#endif + /* * Set up to try to connect to the first host. (Setting whichhost = -1 is * a bit of a cheat, but PQconnectPoll will advance it to 0 before @@ -2095,6 +2147,7 @@ PQconnectPoll(PGconn *conn) case CONNECTION_NEEDED: case CONNECTION_CHECK_WRITABLE: case CONNECTION_CONSUME: + case CONNECTION_GSS_STARTUP: break; default: @@ -2324,6 +2377,7 @@ keep_going: /* We will come back to here until there is getHostaddr(conn, host_addr, NI_MAXHOST); if (strlen(host_addr) > 0) conn->connip = strdup(host_addr); + /* * purposely ignore strdup failure; not a big problem if * it fails anyway. @@ -2636,17 +2690,57 @@ keep_going: /* We will come back to here until there is } #endif /* HAVE_UNIX_SOCKETS */ + if (IS_AF_UNIX(conn->raddr.addr.ss_family)) + { + /* Don't request SSL or GSSAPI over Unix sockets */ +#ifdef USE_SSL + conn->allow_ssl_try = false; +#endif +#ifdef ENABLE_GSS + conn->try_gss = false; +#endif + } + +#ifdef ENABLE_GSS + + /* + * If GSSAPI is enabled and we have a ccache, try to set it up + * before sending startup messages. If it's already + * operating, don't try SSL and instead just build the startup + * packet. + */ + if (conn->try_gss && !conn->gctx) + conn->try_gss = pg_GSS_have_ccache(&conn->gcred); + if (conn->try_gss && !conn->gctx) + { + ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE); + + if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not send GSSAPI negotiation packet: %s\n"), + SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); + goto error_return; + } + + /* Ok, wait for response */ + conn->status = CONNECTION_GSS_STARTUP; + return PGRES_POLLING_READING; + } + else if (!conn->gctx && conn->gssmode[0] == 'r') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache,no server support, or using a local socket)\n")); + goto error_return; + } +#endif + #ifdef USE_SSL /* * If SSL is enabled and we haven't already got it running, * request it instead of sending the startup message. */ - if (IS_AF_UNIX(conn->raddr.addr.ss_family)) - { - /* Don't bother requesting SSL over a Unix socket */ - conn->allow_ssl_try = false; - } if (conn->allow_ssl_try && !conn->wait_ssl_try && !conn->ssl_in_use) { @@ -2840,6 +2934,98 @@ keep_going: /* We will come back to here until there is #endif /* USE_SSL */ } + case CONNECTION_GSS_STARTUP: + { +#ifdef ENABLE_GSS + PostgresPollingStatusType pollres; + + /* + * If we haven't yet, get the postmaster's response to our + * negotiation packet + */ + if (conn->try_gss && !conn->gctx) + { + char gss_ok; + int rdresult = pqReadData(conn); + + if (rdresult < 0) + /* pqReadData fills in error message */ + goto error_return; + else if (rdresult == 0) + /* caller failed to wait for data */ + return PGRES_POLLING_READING; + if (pqGetc(&gss_ok, conn) < 0) + /* shouldn't happen... */ + return PGRES_POLLING_READING; + + if (gss_ok == 'E') + { + /* + * Server failure of some sort. Assume it's a + * protocol version support failure, and let's see if + * we can't recover (if it's not, we'll get a better + * error message on retry). Server gets fussy if we + * don't hang up the socket, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + + /* mark byte consumed */ + conn->inStart = conn->inCursor; + + if (gss_ok == 'N') + { + /* Server doesn't want GSSAPI; fall back if we can */ + if (conn->gssmode[0] == 'r') + { + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n")); + goto error_return; + } + + conn->try_gss = false; + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (gss_ok != 'G') + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"), + gss_ok); + goto error_return; + } + } + + /* Begin or continue GSSAPI negotiation */ + pollres = pqsecure_open_gss(conn); + if (pollres == PGRES_POLLING_OK) + { + /* All set for startup packet */ + conn->status = CONNECTION_MADE; + return PGRES_POLLING_WRITING; + } + else if (pollres == PGRES_POLLING_FAILED && + conn->gssmode[0] == 'p') + { + /* + * We failed, but we can retry on "prefer". Have to drop + * the current connection to do so, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + return pollres; +#else /* !ENABLE_GSS */ + /* unreachable */ + goto error_return; +#endif /* ENABLE_GSS */ + } + /* * Handle authentication exchange: wait for postmaster messages * and respond as necessary. @@ -2993,6 +3179,26 @@ keep_going: /* We will come back to here until there is /* Check to see if we should mention pgpassfile */ pgpassfileWarning(conn); +#ifdef ENABLE_GSS + + /* + * If gssmode is "prefer" and we're using GSSAPI, retry + * without it. + */ + if (conn->gssenc && conn->gssmode[0] == 'p') + { + OM_uint32 minor; + + /* postmaster expects us to drop the connection */ + conn->try_gss = false; + conn->gssenc = false; + gss_delete_sec_context(&minor, &conn->gctx, NULL); + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } +#endif + #ifdef USE_SSL /* @@ -3563,6 +3769,11 @@ makeEmptyPGconn(void) conn->verbosity = PQERRORS_DEFAULT; conn->show_context = PQSHOW_CONTEXT_ERRORS; conn->sock = PGINVALID_SOCKET; +#ifdef ENABLE_GSS + conn->try_gss = true; + initPQExpBuffer(&conn->gbuf); + initPQExpBuffer(&conn->gwritebuf); +#endif /* * We try to send at least 8K at a time, which is the usual size of pipe @@ -3694,10 +3905,30 @@ freePGconn(PGconn *conn) free(conn->requirepeer); if (conn->connip) free(conn->connip); + if (conn->gssmode) + free(conn->gssmode); #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) if (conn->krbsrvname) free(conn->krbsrvname); #endif +#ifdef ENABLE_GSS + if (conn->gcred != GSS_C_NO_CREDENTIAL) + { + OM_uint32 minor; + + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + } + if (conn->gctx) + { + OM_uint32 minor; + + gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER); + conn->gctx = NULL; + } + termPQExpBuffer(&conn->gbuf); + termPQExpBuffer(&conn->gwritebuf); +#endif #if defined(ENABLE_GSS) && defined(ENABLE_SSPI) if (conn->gsslib) free(conn->gsslib); diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c new file mode 100644 index 0000000000..3192f9190b --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.c @@ -0,0 +1,130 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.c + * The front-end (client) GSSAPI common code + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/interfaces/libpq/fe-gssapi-common.c + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "fe-gssapi-common.h" + +#include "libpq-int.h" +#include "pqexpbuffer.h" + +/* + * 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); +} + +/* + * Check if we can acquire credentials at all (and yield them if so). + */ +bool +pg_GSS_have_ccache(gss_cred_id_t *cred_out) +{ + OM_uint32 major, + minor; + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + + major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &cred, NULL, NULL); + if (major != GSS_S_COMPLETE) + { + *cred_out = NULL; + return false; + } + *cred_out = cred; + return true; +} + +/* + * Try to load service name for a connection + */ +int +pg_GSS_load_servicename(PGconn *conn) +{ + OM_uint32 maj_stat, + min_stat; + int maxlen; + gss_buffer_desc temp_gbuf; + char *host; + + if (conn->gtarg_nam != NULL) + /* Already taken care of - move along */ + return STATUS_OK; + + host = PQhost(conn); + if (!(host && host[0] != '\0')) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("host name must be specified\n")); + return STATUS_ERROR; + } + + /* + * Import service principal name so the proper ticket can be acquired by + * the GSSAPI system. + */ + maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; + temp_gbuf.value = (char *) malloc(maxlen); + if (!temp_gbuf.value) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return STATUS_ERROR; + } + snprintf(temp_gbuf.value, maxlen, "%s@%s", + conn->krbsrvname, host); + temp_gbuf.length = strlen(temp_gbuf.value); + + maj_stat = gss_import_name(&min_stat, &temp_gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); + free(temp_gbuf.value); + + if (maj_stat != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI name import error"), + conn, + maj_stat, min_stat); + return STATUS_ERROR; + } + return STATUS_OK; +} diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h new file mode 100644 index 0000000000..b429e79848 --- /dev/null +++ b/src/interfaces/libpq/fe-gssapi-common.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * fe-gssapi-common.h + * + * Definitions for GSSAPI common routines + * + * Portions Copyright (c) 1996-2018, 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 + +#include "libpq-fe.h" +#include "libpq-int.h" + +void pg_GSS_error(const char *mprefix, PGconn *conn, + OM_uint32 maj_stat, OM_uint32 min_stat); +bool pg_GSS_have_ccache(gss_cred_id_t *cred_out); +int pg_GSS_load_servicename(PGconn *conn); +#endif /* FE_GSSAPI_COMMON_H */ diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c new file mode 100644 index 0000000000..5d9c1f8f5b --- /dev/null +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -0,0 +1,394 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-gssapi.c + * The front-end (client) encryption support for GSSAPI + * + * Portions Copyright (c) 2016-2018, 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" + +/* + * Require encryption support, as well as mutual authentication and + * tamperproofing measures. + */ +#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \ + GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG + +/* + * Helper function to write any pending data. Returns len when it has all + * been written; otherwise, sets errno appropriately. Assumes there is data + * to be written. + */ +static ssize_t +send_buffered_data(PGconn *conn, size_t len) +{ + ssize_t ret = pqsecure_raw_write(conn, + conn->gwritebuf.data + conn->gwritecurs, + conn->gwritebuf.len - conn->gwritecurs); + + if (ret < 0) + return ret; + + conn->gwritecurs += ret; + + if (conn->gwritecurs == conn->gwritebuf.len) + { + /* entire request has now been written */ + resetPQExpBuffer(&conn->gwritebuf); + conn->gwritecurs = 0; + return len; + } + + /* need to be called again */ + errno = EWOULDBLOCK; + return -1; +} + +/* + * Write len bytes of data from ptr along a GSSAPI-encrypted connection. Note + * that the connection must be already set up for GSSAPI encryption (i.e., + * GSSAPI transport negotiation is complete). Returns len when all data has + * been written; retry when errno is EWOULDBLOCK or similar with the same + * values of ptr and len. On non-socket failures, will log an error message. + */ +ssize_t +pg_GSS_write(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, + minor; + gss_buffer_desc input, + output = GSS_C_EMPTY_BUFFER; + ssize_t ret = -1; + int conf = 0; + uint32 netlen; + + if (conn->gwritebuf.len != 0) + return send_buffered_data(conn, len); + + /* encrypt the message */ + input.value = ptr; + input.length = len; + + major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT, + &input, &conf, &output); + if (major != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor); + goto cleanup; + } + else if (conf == 0) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI did not provide confidentiality\n")); + goto cleanup; + } + + /* 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *) &netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + + ret = send_buffered_data(conn, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +/* + * Helper function which transfers up to len bytes from the read buffer into + * ptr. Returns number of bytes transfered, and clears the read buffer when + * done. + */ +static ssize_t +read_from_buffer(PGconn *conn, void *ptr, size_t len) +{ + ssize_t ret = 0; + + /* check for available data */ + if (conn->gcursor < conn->gbuf.len) + { + /* clamp length */ + 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; + } + + /* reset buffer if all data has been read */ + if (conn->gcursor == conn->gbuf.len) + { + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + } + + return ret; +} + +/* + * Ensure that the read buffer contains the packet length (4 bytes), reading + * more data and resizing the buffer where necessary. Returns 0 when the + * length is present; otherwise, returns -1. Retry if errno is EWOULDBLOCK or + * similar. + */ +static ssize_t +load_packet_length(PGconn *conn) +{ + ssize_t ret; + + if (conn->gbuf.len < 4) + { + ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len); + 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 + conn->gbuf.len, + 4 - conn->gbuf.len); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len < 4) + { + /* partial read from pqsecure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +/* + * Read the entire packet (len+4 bytes) into the read buffer. Returns 0 on + * success; otherwise, returns -1. Retry if errno is EWOULDBLOCK or similar. + * Will resize the read buffer if needed; this will fail any maliciously large + * packets. + */ +static ssize_t +load_packet(PGconn *conn, size_t len) +{ + ssize_t ret; + + ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI encrypted packet length %ld too big\n"), + len); + return -1; + } + + /* load any missing parts of the packet */ + if (conn->gbuf.len - 4 < len) + { + ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len, + len - conn->gbuf.len + 4); + if (ret < 0) + return ret; + + /* update buffer state */ + conn->gbuf.len += ret; + conn->gbuf.data[conn->gbuf.len] = '\0'; + if (conn->gbuf.len - 4 < len) + { + /* partial read from pqsecure_raw_read() */ + errno = EWOULDBLOCK; + return -1; + } + } + return 0; +} + +/* + * Read up to len bytes of data into ptr from a GSSAPI-encrypted connection. + * Note that GSSAPI transport must already have been negotiated. Returns the + * number of bytes read into ptr; otherwise, returns -1. Retry with the same + * ptr and len when errno is EWOULDBLOCK or similar. + */ +ssize_t +pg_GSS_read(PGconn *conn, void *ptr, size_t len) +{ + OM_uint32 major, + minor; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, + output = GSS_C_EMPTY_BUFFER; + ssize_t ret = 0; + int conf = 0; + + /* handle any buffered data */ + if (conn->gcursor != 0) + return read_from_buffer(conn, ptr, len); + + /* load in the packet length, if not yet loaded */ + ret = load_packet_length(conn); + if (ret < 0) + return ret; + + input.length = ntohl(*(uint32 *) conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0) + return ret; + + /* decrypt the packet */ + input.value = conn->gbuf.data + 4; + + major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL); + if (major != GSS_S_COMPLETE) + { + 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; + } + + /* load decrypted packet into our buffer */ + conn->gcursor = 0; + resetPQExpBuffer(&conn->gbuf); + ret = enlargePQExpBuffer(&conn->gbuf, output.length); + if (ret != 1) + { + printfPQExpBuffer(&conn->errorMessage, libpq_gettext( + "GSSAPI decrypted packet length %ld too big\n"), + output.length); + ret = -1; + goto cleanup; + } + + memcpy(conn->gbuf.data, output.value, output.length); + conn->gbuf.len = output.length; + conn->gbuf.data[conn->gbuf.len] = '\0'; + + ret = read_from_buffer(conn, ptr, len); +cleanup: + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +/* + * Negotiate GSSAPI transport for a connection. When complete, returns + * PGRES_POLLING_OK. Will return PGRES_POLLING_READING or + * PGRES_POLLING_WRITING as appropriate whenever it would block, and + * PGRES_POLLING_FAILED if transport could not be negotiated. + */ +PostgresPollingStatusType +pqsecure_open_gss(PGconn *conn) +{ + ssize_t ret; + OM_uint32 major, + minor; + uint32 netlen; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, + output = GSS_C_EMPTY_BUFFER; + + /* Send out any data we might have */ + if (conn->gwritebuf.len != 0) + { + ret = send_buffered_data(conn, 1); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_WRITING; + else if (ret == 1) + /* sent all data */ + return PGRES_POLLING_READING; + return PGRES_POLLING_FAILED; + } + + /* Client sends first, and sending creates a context */ + if (conn->gctx) + { + /* Process any incoming data we might have */ + ret = load_packet_length(conn); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + if (conn->gbuf.data[0] == 'E') + { + /* + * We can taken an error here, and it's my least favorite thing. + * How long can error messages be? That's a good question, but + * backend's pg_gss_error() caps them at 256. Do a single read + * for that and call it a day. Cope with this here rather than in + * load_packet since expandPQExpBuffer will destroy any data in + * the buffer on failure. + */ + load_packet(conn, 256); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Server error: %s"), + conn->gbuf.data + 1); + return PGRES_POLLING_FAILED; + } + + input.length = ntohl(*(uint32 *) conn->gbuf.data); + ret = load_packet(conn, input.length); + if (ret < 0 && errno == EWOULDBLOCK) + return PGRES_POLLING_READING; + else if (ret < 0) + return PGRES_POLLING_FAILED; + + input.value = conn->gbuf.data + 4; + } + + ret = pg_GSS_load_servicename(conn); + if (ret != STATUS_OK) + return PGRES_POLLING_FAILED; + + major = gss_init_sec_context(&minor, conn->gcred, &conn->gctx, + conn->gtarg_nam, GSS_C_NO_OID, + GSS_REQUIRED_FLAGS, 0, 0, &input, NULL, + &output, NULL, NULL); + resetPQExpBuffer(&conn->gbuf); + if (GSS_ERROR(major)) + { + pg_GSS_error(libpq_gettext("GSSAPI context establishment error"), + conn, major, minor); + return PGRES_POLLING_FAILED; + } + else if (output.length == 0) + { + /* + * We're done - hooray! Kind of gross, but we need to disable SSL + * here so that we don't accidentally tunnel one over the other. + */ +#ifdef USE_SSL + conn->allow_ssl_try = false; +#endif + gss_release_cred(&minor, &conn->gcred); + conn->gcred = GSS_C_NO_CREDENTIAL; + conn->gssenc = true; + return PGRES_POLLING_OK; + } + + /* Queue the token for writing */ + netlen = htonl(output.length); + appendBinaryPQExpBuffer(&conn->gwritebuf, (char *) &netlen, 4); + appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length); + gss_release_buffer(&minor, &output); + return PGRES_POLLING_WRITING; +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index a06fc7dc82..f4f196e3b4 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) n = pgtls_read(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_read(conn, ptr, len); + } + else #endif { n = pqsecure_raw_read(conn, ptr, len); @@ -287,7 +294,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; @@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) n = pgtls_write(conn, ptr, len); } else +#endif +#ifdef ENABLE_GSS + if (conn->gssenc) + { + n = pg_GSS_write(conn, ptr, len); + } + else #endif { n = pqsecure_raw_write(conn, ptr, len); diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 3f13ddf092..e8e0a9529a 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -65,8 +65,9 @@ typedef enum CONNECTION_NEEDED, /* Internal state: connect() needed */ CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable * connection. */ - CONNECTION_CONSUME /* Wait for any pending message and consume + CONNECTION_CONSUME, /* Wait for any pending message and consume * them. */ + CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */ } ConnStatusType; typedef enum diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 66fd317b94..3315fde1ff 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -22,6 +22,7 @@ /* We assume libpq-fe.h has already been included. */ #include "libpq-events.h" +#include "lib/stringinfo.h" #include <time.h> #ifndef WIN32 @@ -478,9 +479,19 @@ struct pg_conn #endif /* USE_OPENSSL */ #endif /* USE_SSL */ + char *gssmode; /* GSS mode (require,prefer,disable) */ #ifdef ENABLE_GSS gss_ctx_id_t gctx; /* GSS context */ gss_name_t gtarg_nam; /* GSS target name */ + + /* The following are encryption-only */ + 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 try_gss; /* GSS attempting permitted */ + bool gssenc; /* GSS encryption is usable */ + gss_cred_id_t gcred; /* GSS credential temp storage. */ #endif #ifdef ENABLE_SSPI @@ -656,7 +667,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); @@ -748,6 +759,23 @@ extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn, int *names_examined, char **first_name); +/* === GSSAPI === */ + +#ifdef ENABLE_GSS + +/* + * Establish a GSSAPI-encrypted connection. + */ +extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn); + +/* + * Read and write functions for GSSAPI-encrypted connections, with internal + * buffering to handle nonblocking sockets. + */ +extern ssize_t pg_GSS_write(PGconn *conn, void *ptr, size_t len); +extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len); +#endif + /* === miscellaneous macros === */ /* diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 2921d193a1..6ab6276cdb 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -190,6 +190,11 @@ sub mkvcbuild $postgres->RemoveFile('src/backend/libpq/be-secure-common.c'); $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + $postgres->RemoveFile('src/backend/libpq/be-secure-gssapi.c'); + } my $snowball = $solution->AddProject('dict_snowball', 'dll', '', 'src/backend/snowball'); @@ -250,6 +255,11 @@ sub mkvcbuild $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c'); $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); } + if (!$solution->{options}->{gss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c'); + } my $libpqwalreceiver = $solution->AddProject('libpqwalreceiver', 'dll', '', -- 2.19.2
Attachment
Greetings Robbie, * Robbie Harwood (rharwood@redhat.com) wrote: > Attached please find version 20 of the GSSAPI encryption support. This > has been rebased onto master (thanks Stephen for calling out ab69ea9). > Other changes since v19 from Stephen's review: > > - About 100 lines of new comments Thanks for that. > - pgindent run over code (only the stuff I'm changing; it reports other > problems on master, but if I understand correctly, that's not on me to > fix here) That's correct. > Thanks for the feedback! And speaking of feedback, this patch is in the > "Needs Review" state so if people have additional feedback, that would > be appreciated. I've CC'd people who I can remember reviewing before; > they should of course feel no obligation to review again if time > commitments/interests do not allow. I've looked over this again and have been playing with it off-and-on for a while now. One thing I was actually looking at implementing before we push to commit this was to have similar information to what SSL provides on connection, specifically what printSSLInfo() prints by way of cipher, bits, etc for the client-side and then something like pg_stat_ssl for the server side. I went ahead and added a printGSSENCInfo(), and then PQgetgssSASLSSF(), which calls down into gss_inquire_sec_context_by_oid() for GSS_C_SEC_CONTEXT_SASL_SSF to get the bits (which also required including gssapi_ext.h), and now have psql producing: --- psql (12devel) GSS Encrypted connection (bits: 256) Type "help" for help. --- I was curious though- is there a good way to get at the encryption type used..? I haven't had much luck in reading the RFPs and poking around, but perhaps I'm just not looking in the right place. Also, what information do you think would be particularly useful on the server side? I was thinking that the princ used to authenticate, the encryption type/cipher, and bits used, at least, would be meaningful. I'm also guessing that we may need to add something to make these functions not fail on older systems which don't provide the SASL SSF OID..? I haven't looked into that yet but we should. I don't think these things are absolutely required to push the patch forward, just to be clear, but it's helping my confidence level, at least, and would make it closer to on-par with our current SSL implementation. They also seem, well, reasonably straight-forward to add and quite useful. I've attached my WIP patch for adding the bits, patched over-top of your v20 (would be neat if there was a way to tell cfbot to ignore it...). Thoughts? Thanks! Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > * Robbie Harwood (rharwood@redhat.com) wrote: > >> Attached please find version 20 of the GSSAPI encryption support. >> This has been rebased onto master (thanks Stephen for calling out >> ab69ea9). > > I've looked over this again and have been playing with it off-and-on > for a while now. One thing I was actually looking at implementing > before we push to commit this was to have similar information to what > SSL provides on connection, specifically what printSSLInfo() prints by > way of cipher, bits, etc for the client-side and then something like > pg_stat_ssl for the server side. > > I went ahead and added a printGSSENCInfo(), and then > PQgetgssSASLSSF(), which calls down into > gss_inquire_sec_context_by_oid() for GSS_C_SEC_CONTEXT_SASL_SSF to get > the bits (which also required including gssapi_ext.h), and now have > psql producing: Neat! Two things here: First, because this is a SASL extension, it requires existing mechanism integration with SASL. For instance, NTLM (through gss-ntlmssp) doesn't implement it. PQgetgssSASLSSF() logs an error message here, which I don't think is quite right - probably you should only log an error message if you don't get back GSS_S_COMPLETE or GSS_S_UNAVAILABLE. (NTLM is an example here; I cannot in good conscience recommend its use, and neither does Microsoft.) Also, this is an MIT-specific extension: Heimdal doesn't support it. While I (a krb5 developer) am fine with a hard MIT dependency, the project doesn't currently have this. (And if we went that road, I'd like to use gss_acquire_cred_from() instead of the setenv() in be-secure-gssapi.c.) > --- > psql (12devel) > GSS Encrypted connection (bits: 256) > Type "help" for help. > --- > > I was curious though- is there a good way to get at the encryption > type used..? I haven't had much luck in reading the RFPs and poking > around, but perhaps I'm just not looking in the right place. It's something of a layering issue. GSSAPI might not be using Kerberos, and even if it is, Kerberos has algorithm agility. A call to gss_inquire_context() produces mech type, so you could print something like "GSS Encrypted connection (Kerberos 256 bits)" or "GSS encrypted connection (NTLM)". I think this'd be pretty reasonable. For Kerberos-specific introspection, MIT krb5 and Heimdal both support an interface called lucid contexts that allows this kind of introspection (and some other gross layering violations) for use with the kernel. Look at gssapi_krb5.h (it's called that in both). I don't think it's worth supporting rfc1964 at this point (it's 1DES-only). > Also, what information do you think would be particularly useful on > the server side? I was thinking that the princ used to authenticate, > the encryption type/cipher, and bits used, at least, would be > meaningful. I'm wary about indicating number of bits especially with the asymmetric/symmetric divide and the importance of which algorithm they're used with, but that may be a non-concern - especially if it attains parity with the TLS code. Principal used is definitely good (helps debugging the user mapping rules, if nothing else). Mechanism is also nice. I can't think of anything else right now that you haven't mentioned. I think there's value in auth indcatorsp http://web.mit.edu/kerberos/krb5-latest/doc/admin/auth_indicator.html but supporting them would be a separate follow-on patch. > I'm also guessing that we may need to add something to > make these functions not fail on older systems which don't provide the > SASL SSF OID..? I haven't looked into that yet but we should. MIT krb5 gained support in 2017, which corresponds to krb5-1.16; Heimdal has no support for it. There probably isn't a shortcut for a buildsystem check, unfortunately: Heimdal has gss_inquire_sec_context_by_oid() in another file (they don't have gssapi_ext.h), and it's the OID is MIT-only and not a #define-d constant. (With my downstream maintainer hat on, I'd further ask that any such check not just be a version check in order to allow backporting; in particular, the el7 krb5-1.15 branch does include support for this OID.) > I don't think these things are absolutely required to push the patch > forward, just to be clear, but it's helping my confidence level, at > least, and would make it closer to on-par with our current SSL > implementation. They also seem, well, reasonably straight-forward to > add and quite useful. Auditability is definitely reasonable. I think there are a couple additional wrinkles discussed above, but we can probably get them sorted. > I've attached my WIP patch for adding the bits, patched over-top of your > v20 (would be neat if there was a way to tell cfbot to ignore it...). I don't have additional concerns beyond the above. Thanks, --Robbie
Attachment
Greetings, * Robbie Harwood (rharwood@redhat.com) wrote: > Stephen Frost <sfrost@snowman.net> writes: > > * Robbie Harwood (rharwood@redhat.com) wrote: > >> Attached please find version 20 of the GSSAPI encryption support. > >> This has been rebased onto master (thanks Stephen for calling out > >> ab69ea9). > > > > I've looked over this again and have been playing with it off-and-on > > for a while now. One thing I was actually looking at implementing > > before we push to commit this was to have similar information to what > > SSL provides on connection, specifically what printSSLInfo() prints by > > way of cipher, bits, etc for the client-side and then something like > > pg_stat_ssl for the server side. > > > > I went ahead and added a printGSSENCInfo(), and then > > PQgetgssSASLSSF(), which calls down into > > gss_inquire_sec_context_by_oid() for GSS_C_SEC_CONTEXT_SASL_SSF to get > > the bits (which also required including gssapi_ext.h), and now have > > psql producing: > > Neat! Two things here: > > First, because this is a SASL extension, it requires existing mechanism > integration with SASL. For instance, NTLM (through gss-ntlmssp) doesn't > implement it. PQgetgssSASLSSF() logs an error message here, which I > don't think is quite right - probably you should only log an error > message if you don't get back GSS_S_COMPLETE or GSS_S_UNAVAILABLE. Ok, that's easy enough to fix. > (NTLM is an example here; I cannot in good conscience recommend its use, > and neither does Microsoft.) Agreed! > Also, this is an MIT-specific extension: Heimdal doesn't support it. Is that simply because they've not gotten around to it..? From what I saw, this approach is in an accepted RFC, so if they want to implement it, they could do so..? > While I (a krb5 developer) am fine with a hard MIT dependency, the > project doesn't currently have this. (And if we went that road, I'd > like to use gss_acquire_cred_from() instead of the setenv() in > be-secure-gssapi.c.) We certainly don't want a hard MIT dependency, but if it's following an RFC and we can gracefully fall-back if it isn't available then I think it's acceptable. If there's a better approach which would work with both MIT and Heimdal and ideally is defined through an RFC, that'd be better, but I'm guessing there isn't... > > --- > > psql (12devel) > > GSS Encrypted connection (bits: 256) > > Type "help" for help. > > --- > > > > I was curious though- is there a good way to get at the encryption > > type used..? I haven't had much luck in reading the RFPs and poking > > around, but perhaps I'm just not looking in the right place. > > It's something of a layering issue. GSSAPI might not be using Kerberos, > and even if it is, Kerberos has algorithm agility. Right, I'm aware that Kerberos could be using different encryption algorithms, similar to SSL and that GSSAPI itself would allow for different algorithms through different mechanisms- but the question is, how do we know what the encryption algorithm ultimately used, regardless of the rest, is? > A call to gss_inquire_context() produces mech type, so you could print > something like "GSS Encrypted connection (Kerberos 256 bits)" or "GSS > encrypted connection (NTLM)". I think this'd be pretty reasonable. I'm... not impressed with that approach, since "Kerberos" as an encryption algorithm doesn't mean squat- that could be DES or RC4 for all we know. > For Kerberos-specific introspection, MIT krb5 and Heimdal both support > an interface called lucid contexts that allows this kind of > introspection (and some other gross layering violations) for use with > the kernel. Look at gssapi_krb5.h (it's called that in both). I don't > think it's worth supporting rfc1964 at this point (it's 1DES-only). I agree that there's no sense in supporting 1DES-only. I also, frankly, don't agree with the characterization that wanting to know what the agreed-upon encryption algorithm is constitutes a gross layering violation, but that's really an independent discussion that we can have in a pub somewhere. ;) To get where I'd like us to be, however, it sounds like we need to: a) determine if we've got encryption (that's easy, assuming I've done it correctly so far) b) Figure out if the encryption is being provided by Kerberos (using gss_inquire_context() would do that, right?) c) If we're using Kerberos, use the lucid contexts to ask what the actual encryption algorithm used is That's a bit obnoxious but it at least seems doable. I don't suppose you know of any example code out there which provides this? Any idea if there's work underway on an RFC to make this, well, suck less? > > Also, what information do you think would be particularly useful on > > the server side? I was thinking that the princ used to authenticate, > > the encryption type/cipher, and bits used, at least, would be > > meaningful. > > I'm wary about indicating number of bits especially with the > asymmetric/symmetric divide and the importance of which algorithm > they're used with, but that may be a non-concern - especially if it > attains parity with the TLS code. This would be why I'd like to also provide the algorithm... :) > Principal used is definitely good (helps debugging the user mapping > rules, if nothing else). Mechanism is also nice. I can't think of > anything else right now that you haven't mentioned. Good idea to also include mechanism explicitly. > I think there's value in auth indcatorsp > http://web.mit.edu/kerberos/krb5-latest/doc/admin/auth_indicator.html > but supporting them would be a separate follow-on patch. Hmmm, that does look interesting, but I agree that it could be a follow-on patch. > > I'm also guessing that we may need to add something to > > make these functions not fail on older systems which don't provide the > > SASL SSF OID..? I haven't looked into that yet but we should. > > MIT krb5 gained support in 2017, which corresponds to krb5-1.16; Heimdal > has no support for it. There probably isn't a shortcut for a > buildsystem check, unfortunately: Heimdal has > gss_inquire_sec_context_by_oid() in another file (they don't have > gssapi_ext.h), and it's the OID is MIT-only and not a #define-d > constant. I had been presuming that it'd be a buildsystem check, though it would have been nice if it could have been something run-time. > (With my downstream maintainer hat on, I'd further ask that any such > check not just be a version check in order to allow backporting; in > particular, the el7 krb5-1.15 branch does include support for this OID.) I don't have any issue with that provided we can implement the check in a reasonable way (hint: I'm open to your suggestions on this :). > > I don't think these things are absolutely required to push the patch > > forward, just to be clear, but it's helping my confidence level, at > > least, and would make it closer to on-par with our current SSL > > implementation. They also seem, well, reasonably straight-forward to > > add and quite useful. > > Auditability is definitely reasonable. I think there are a couple > additional wrinkles discussed above, but we can probably get them > sorted. Fantastic. Do you think you'll have time to work through some of the above with me over the next couple of weeks? I was just starting to look into adding things into the beentry to hold information on the server side. Thanks! Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > * Robbie Harwood (rharwood@redhat.com) wrote: >> Stephen Frost <sfrost@snowman.net> writes: >>> * Robbie Harwood (rharwood@redhat.com) wrote: >>> >>>> Attached please find version 20 of the GSSAPI encryption support. >>>> This has been rebased onto master (thanks Stephen for calling out >>>> ab69ea9). >>> >>> I've looked over this again and have been playing with it off-and-on >>> for a while now. One thing I was actually looking at implementing >>> before we push to commit this was to have similar information to >>> what SSL provides on connection, specifically what printSSLInfo() >>> prints by way of cipher, bits, etc for the client-side and then >>> something like pg_stat_ssl for the server side. >>> >>> I went ahead and added a printGSSENCInfo(), and then >>> PQgetgssSASLSSF(), which calls down into >>> gss_inquire_sec_context_by_oid() for GSS_C_SEC_CONTEXT_SASL_SSF to >>> get the bits (which also required including gssapi_ext.h), and now >>> have psql producing: >> >> Also, this is an MIT-specific extension: Heimdal doesn't support it. > > Is that simply because they've not gotten around to it..? From what I > saw, this approach is in an accepted RFC, so if they want to implement > it, they could do so..? Yes. Heimdal is less active these days than MIT; they were dormant for a while, though are active again. They do have an issue open to track this feature: https://github.com/heimdal/heimdal/issues/400 >> While I (a krb5 developer) am fine with a hard MIT dependency, the >> project doesn't currently have this. (And if we went that road, I'd >> like to use gss_acquire_cred_from() instead of the setenv() in >> be-secure-gssapi.c.) > > We certainly don't want a hard MIT dependency, but if it's following an > RFC and we can gracefully fall-back if it isn't available then I think > it's acceptable. If there's a better approach which would work with > both MIT and Heimdal and ideally is defined through an RFC, that'd be > better, but I'm guessing there isn't... While I think the concept of SASL SSF is standardized, I don't think the semantics of this OID are - the OID itself is in the MIT gssapi extension space. As a historical note, the reason this interface exists in krb5 is of course for SASL. In particular, cyrus was reporting treating the SSF of all krb5 types as if they were DES. So while technically "assume DES" could be a fallback... we probably shouldn't do that. >>> --- >>> psql (12devel) >>> GSS Encrypted connection (bits: 256) >>> Type "help" for help. >>> --- >>> >>> I was curious though- is there a good way to get at the encryption >>> type used..? I haven't had much luck in reading the RFPs and poking >>> around, but perhaps I'm just not looking in the right place. >> >> A call to gss_inquire_context() produces mech type, so you could >> print something like "GSS Encrypted connection (Kerberos 256 bits)" >> or "GSS encrypted connection (NTLM)". I think this'd be pretty >> reasonable. > > I'm... not impressed with that approach, since "Kerberos" as an > encryption algorithm doesn't mean squat- that could be DES or RC4 for > all we know. This is a fair concern. >> For Kerberos-specific introspection, MIT krb5 and Heimdal both >> support an interface called lucid contexts that allows this kind of >> introspection (and some other gross layering violations) for use with >> the kernel. Look at gssapi_krb5.h (it's called that in both). I >> don't think it's worth supporting rfc1964 at this point (it's >> 1DES-only). > > I agree that there's no sense in supporting 1DES-only. > > I also, frankly, don't agree with the characterization that wanting to > know what the agreed-upon encryption algorithm is constitutes a gross > layering violation, but that's really an independent discussion that we > can have in a pub somewhere. ;) Yeah I won't defend it very hard :) > To get where I'd like us to be, however, it sounds like we need to: > > a) determine if we've got encryption (that's easy, assuming I've done it > correctly so far) Yes. > b) Figure out if the encryption is being provided by Kerberos (using > gss_inquire_context() would do that, right?) Right - check and log mech_out there, then proceed if it's gss_mech_krb5. Probably not worth handling _old and _wrong, especially since Heimdal doesn't have them and they shouldn't be in use anyway. > c) If we're using Kerberos, use the lucid contexts to ask what the > actual encryption algorithm used is Yes. This'll involve some introspection into krb5.h (for enctype names), but that's not too hard. > That's a bit obnoxious but it at least seems doable. I don't suppose > you know of any example code out there which provides this? Any idea > if there's work underway on an RFC to make this, well, suck less? MIT has some in our test suite (t_enctypes). nfs-utils (GPLv2) also uses this interface (NFS is the intended consumer). There isn't IETF effort in this regard that I'm aware of, but I'll add it to the kitten backlog. >> (With my downstream maintainer hat on, I'd further ask that any such >> check not just be a version check in order to allow backporting; in >> particular, the el7 krb5-1.15 branch does include support for this OID.) > > I don't have any issue with that provided we can implement the check in > a reasonable way (hint: I'm open to your suggestions on this :). > >>> I don't think these things are absolutely required to push the patch >>> forward, just to be clear, but it's helping my confidence level, at >>> least, and would make it closer to on-par with our current SSL >>> implementation. They also seem, well, reasonably straight-forward to >>> add and quite useful. >> >> Auditability is definitely reasonable. I think there are a couple >> additional wrinkles discussed above, but we can probably get them >> sorted. > > Fantastic. Do you think you'll have time to work through some of the > above with me over the next couple of weeks? I was just starting to > look into adding things into the beentry to hold information on the > server side. Sure! I'll go ahead and hack up the checks and lucid stuff and get back to you. Thanks, --Robbie
Attachment
Greetings, * Robbie Harwood (rharwood@redhat.com) wrote: > Stephen Frost <sfrost@snowman.net> writes: > > * Robbie Harwood (rharwood@redhat.com) wrote: > >> Stephen Frost <sfrost@snowman.net> writes: > >>> * Robbie Harwood (rharwood@redhat.com) wrote: > >>> > >>>> Attached please find version 20 of the GSSAPI encryption support. > >>>> This has been rebased onto master (thanks Stephen for calling out > >>>> ab69ea9). > >>> > >>> I've looked over this again and have been playing with it off-and-on > >>> for a while now. One thing I was actually looking at implementing > >>> before we push to commit this was to have similar information to > >>> what SSL provides on connection, specifically what printSSLInfo() > >>> prints by way of cipher, bits, etc for the client-side and then > >>> something like pg_stat_ssl for the server side. > >>> > >>> I went ahead and added a printGSSENCInfo(), and then > >>> PQgetgssSASLSSF(), which calls down into > >>> gss_inquire_sec_context_by_oid() for GSS_C_SEC_CONTEXT_SASL_SSF to > >>> get the bits (which also required including gssapi_ext.h), and now > >>> have psql producing: > >> > >> Also, this is an MIT-specific extension: Heimdal doesn't support it. > > > > Is that simply because they've not gotten around to it..? From what I > > saw, this approach is in an accepted RFC, so if they want to implement > > it, they could do so..? > > Yes. Heimdal is less active these days than MIT; they were dormant for > a while, though are active again. They do have an issue open to track > this feature: https://github.com/heimdal/heimdal/issues/400 Ok, good, when they add it then hopefully it'll be picked up more-or-less 'for free'. > >> While I (a krb5 developer) am fine with a hard MIT dependency, the > >> project doesn't currently have this. (And if we went that road, I'd > >> like to use gss_acquire_cred_from() instead of the setenv() in > >> be-secure-gssapi.c.) > > > > We certainly don't want a hard MIT dependency, but if it's following an > > RFC and we can gracefully fall-back if it isn't available then I think > > it's acceptable. If there's a better approach which would work with > > both MIT and Heimdal and ideally is defined through an RFC, that'd be > > better, but I'm guessing there isn't... > > While I think the concept of SASL SSF is standardized, I don't think the > semantics of this OID are - the OID itself is in the MIT gssapi > extension space. We can adjust for that pretty easily, presumably, if another OID ends up being assigned (though if one already exists, isn't it something that Heimdal, for example, could potentially decide to just support..?). > As a historical note, the reason this interface exists in krb5 is of > course for SASL. In particular, cyrus was reporting treating the SSF of > all krb5 types as if they were DES. So while technically "assume DES" > could be a fallback... we probably shouldn't do that. Agreed, we shouldn't do that. > >> A call to gss_inquire_context() produces mech type, so you could > >> print something like "GSS Encrypted connection (Kerberos 256 bits)" > >> or "GSS encrypted connection (NTLM)". I think this'd be pretty > >> reasonable. > > > > I'm... not impressed with that approach, since "Kerberos" as an > > encryption algorithm doesn't mean squat- that could be DES or RC4 for > > all we know. > > This is a fair concern. I will say that I'm alright with listing Kerberos in the message if we think it's useful- but we should also list the actual encryption algorithm and bits, if possible (which it sounds like it is, so that's definitely good). > > To get where I'd like us to be, however, it sounds like we need to: > > > > a) determine if we've got encryption (that's easy, assuming I've done it > > correctly so far) > > Yes. > > > b) Figure out if the encryption is being provided by Kerberos (using > > gss_inquire_context() would do that, right?) > > Right - check and log mech_out there, then proceed if it's > gss_mech_krb5. Probably not worth handling _old and _wrong, especially > since Heimdal doesn't have them and they shouldn't be in use anyway. > > > c) If we're using Kerberos, use the lucid contexts to ask what the > > actual encryption algorithm used is > > Yes. This'll involve some introspection into krb5.h (for enctype > names), but that's not too hard. Sounds good. > > That's a bit obnoxious but it at least seems doable. I don't suppose > > you know of any example code out there which provides this? Any idea > > if there's work underway on an RFC to make this, well, suck less? > > MIT has some in our test suite (t_enctypes). nfs-utils (GPLv2) also > uses this interface (NFS is the intended consumer). > > There isn't IETF effort in this regard that I'm aware of, but I'll add > it to the kitten backlog. Fantastic, thanks. > >> (With my downstream maintainer hat on, I'd further ask that any such > >> check not just be a version check in order to allow backporting; in > >> particular, the el7 krb5-1.15 branch does include support for this OID.) > > > > I don't have any issue with that provided we can implement the check in > > a reasonable way (hint: I'm open to your suggestions on this :). > > > >>> I don't think these things are absolutely required to push the patch > >>> forward, just to be clear, but it's helping my confidence level, at > >>> least, and would make it closer to on-par with our current SSL > >>> implementation. They also seem, well, reasonably straight-forward to > >>> add and quite useful. > >> > >> Auditability is definitely reasonable. I think there are a couple > >> additional wrinkles discussed above, but we can probably get them > >> sorted. > > > > Fantastic. Do you think you'll have time to work through some of the > > above with me over the next couple of weeks? I was just starting to > > look into adding things into the beentry to hold information on the > > server side. > > Sure! I'll go ahead and hack up the checks and lucid stuff and get back > to you. Great! I'll finish fleshing out the basics of how to have this work server-side and once the checks and lucid stuff are in on the psql side, it should be pretty straight-forward to copy that same information into beentry alongside the SSL info, and expose it through pg_stat_get_activity() into a pg_stat_gss view. Thanks! Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > * Robbie Harwood (rharwood@redhat.com) wrote: >> Stephen Frost <sfrost@snowman.net> writes: >>> * Robbie Harwood (rharwood@redhat.com) wrote: >>>> Stephen Frost <sfrost@snowman.net> writes: >>>>> * Robbie Harwood (rharwood@redhat.com) wrote: >>>>> >>>>>> Attached please find version 20 of the GSSAPI encryption support. >>>>>> This has been rebased onto master (thanks Stephen for calling out >>>>>> ab69ea9). >>>>> >>>>> I've looked over this again and have been playing with it >>>>> off-and-on for a while now. One thing I was actually looking at >>>>> implementing before we push to commit this was to have similar >>>>> information to what SSL provides on connection, specifically what >>>>> printSSLInfo() prints by way of cipher, bits, etc for the >>>>> client-side and then something like pg_stat_ssl for the server >>>>> side. >>>>> >>>>> I went ahead and added a printGSSENCInfo(), and then >>>>> PQgetgssSASLSSF(), which calls down into >>>>> gss_inquire_sec_context_by_oid() for GSS_C_SEC_CONTEXT_SASL_SSF to >>>>> get the bits (which also required including gssapi_ext.h), and now >>>>> have psql producing: >>>> >>>> While I (a krb5 developer) am fine with a hard MIT dependency, the >>>> project doesn't currently have this. (And if we went that road, >>>> I'd like to use gss_acquire_cred_from() instead of the setenv() in >>>> be-secure-gssapi.c.) >>> >>> We certainly don't want a hard MIT dependency, but if it's following >>> an RFC and we can gracefully fall-back if it isn't available then I >>> think it's acceptable. If there's a better approach which would >>> work with both MIT and Heimdal and ideally is defined through an >>> RFC, that'd be better, but I'm guessing there isn't... >> >> While I think the concept of SASL SSF is standardized, I don't think >> the semantics of this OID are - the OID itself is in the MIT gssapi >> extension space. > > We can adjust for that pretty easily, presumably, if another OID ends > up being assigned (though if one already exists, isn't it something > that Heimdal, for example, could potentially decide to just > support..?). Yes, exactly - Heimdal would probably just use the MIT OID with the existing semantics. Thanks, --Robbie
Attachment
Hi, On 2018-12-18 14:12:46 -0500, Robbie Harwood wrote: > From 6915ae2507bf7910c5eecfbd0b84805531c16a07 Mon Sep 17 00:00:00 2001 > From: Robbie Harwood <rharwood@redhat.com> > Date: Thu, 10 May 2018 16:12:03 -0400 > Subject: [PATCH] libpq GSSAPI encryption support > > On both the frontend and backend, prepare for GSSAPI encryption > support by moving common code for error handling into a separate file. > Fix a TODO for handling multiple status messages in the process. > Eliminate the OIDs, which have not been needed for some time. > Add frontend and backend encryption support functions. Keep the > context initiation for authentication-only separate on both the > frontend and backend in order to avoid concerns about changing the > requested flags to include encryption support. > > In postmaster, pull GSSAPI authorization checking into a shared > function. Also share the initiator name between the encryption and > non-encryption codepaths. > > Modify pqsecure_write() to take a non-const pointer. The pointer will > not be modified, but this change (or a const-discarding cast, or a > malloc()+memcpy()) is necessary for GSSAPI due to const/struct > interactions in C. > > For HBA, add "hostgss" and "hostnogss" entries that behave similarly > to their SSL counterparts. "hostgss" requires either "gss", "trust", > or "reject" for its authentication. > > Simiarly, add a "gssmode" parameter to libpq. Supported values are > "disable", "require", and "prefer". Notably, negotiation will only be > attempted if credentials can be acquired. Move credential acquisition > into its own function to support this behavior. > > Finally, add documentation for everything new, and update existing > documentation on connection security. > > Thanks to Michael Paquier for the Windows fixes. Could some of these be split into separate patches that could be more eagerly merged? This is a somewhat large patch... > diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c > index a06fc7dc82..f4f196e3b4 100644 > --- a/src/interfaces/libpq/fe-secure.c > +++ b/src/interfaces/libpq/fe-secure.c > @@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) > n = pgtls_read(conn, ptr, len); > } > else > +#endif > +#ifdef ENABLE_GSS > + if (conn->gssenc) > + { > + n = pg_GSS_read(conn, ptr, len); > + } > + else > #endif > { > n = pqsecure_raw_read(conn, ptr, len); > @@ -287,7 +294,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; > > @@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) > n = pgtls_write(conn, ptr, len); > } > else > +#endif > +#ifdef ENABLE_GSS > + if (conn->gssenc) > + { > + n = pg_GSS_write(conn, ptr, len); > + } > + else > #endif > { > n = pqsecure_raw_write(conn, ptr, len); Not a fan of this. Seems like we'll grow more and more such branches over time? Wouldn't the right thing be to have callbacks in PGconn (and similarly in the backend)? Seems like if that's done reasonably it'd also make integration of compression easier, because that could just layer itself between encryption and wire? Greetings, Andres Freund
Andres Freund <andres@anarazel.de> writes: > On 2018-12-18 14:12:46 -0500, Robbie Harwood wrote: > >> Subject: [PATCH] libpq GSSAPI encryption support > > Could some of these be split into separate patches that could be more > eagerly merged? This is a somewhat large patch... What splits do you propose? (It's been a single patch since v3 as per your request in id:20151003161810.GD30738@alap3.anarazel.de and Michael Paquier's request in id:CAB7nPqTJD-tTrM1Vu8P55_4kKVeDX8DFz9v1D_XsQQvR_xA5qQ@mail.gmail.com). >> diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c >> index a06fc7dc82..f4f196e3b4 100644 >> --- a/src/interfaces/libpq/fe-secure.c >> +++ b/src/interfaces/libpq/fe-secure.c >> @@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) >> n = pgtls_read(conn, ptr, len); >> } >> else >> +#endif >> +#ifdef ENABLE_GSS >> + if (conn->gssenc) >> + { >> + n = pg_GSS_read(conn, ptr, len); >> + } >> + else >> #endif >> { >> n = pqsecure_raw_read(conn, ptr, len); >> @@ -287,7 +294,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; >> >> @@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) >> n = pgtls_write(conn, ptr, len); >> } >> else >> +#endif >> +#ifdef ENABLE_GSS >> + if (conn->gssenc) >> + { >> + n = pg_GSS_write(conn, ptr, len); >> + } >> + else >> #endif >> { >> n = pqsecure_raw_write(conn, ptr, len); > > Not a fan of this. Seems like we'll grow more and more such branches > over time? Wouldn't the right thing be to have callbacks in PGconn > (and similarly in the backend)? Is that really a problem? Each branch is only seven lines, which is a lot less than adding callback support will be. And we'll only get a new branch when we want to support a new encryption protocol - unlike authentication, there aren't too many of those. > Seems like if that's done reasonably it'd also make integration of > compression easier, because that could just layer itself between > encryption and wire? The current interface would allow a compress/decompress call in a way that makes sense to me (here for write, ignoring ifdefs): ssize_t pqsecure_write(PGConn *conn, void *ptr, size_t len) { ssize_t n; if (conn->compress) { do_compression(conn, &ptr, &len); } if (conn->ssl_in_use) { n = pgtls_write(conn, ptr, len); } else if (conn->gssenc) { n = pg_GSS_write(conn, ptr, len); } else { n = pqsecure_raw_write(conn, ptr, len); } return n; } (pqsecure_read would look similarly, with decompression as the last step instead of the first.) Thanks, --Robbie
Attachment
Greetings, * Robbie Harwood (rharwood@redhat.com) wrote: > Andres Freund <andres@anarazel.de> writes: > > > On 2018-12-18 14:12:46 -0500, Robbie Harwood wrote: > > > >> Subject: [PATCH] libpq GSSAPI encryption support > > > > Could some of these be split into separate patches that could be more > > eagerly merged? This is a somewhat large patch... > > What splits do you propose? (It's been a single patch since v3 as per > your request in id:20151003161810.GD30738@alap3.anarazel.de and Michael > Paquier's request in > id:CAB7nPqTJD-tTrM1Vu8P55_4kKVeDX8DFz9v1D_XsQQvR_xA5qQ@mail.gmail.com). Yeah, I tend to agree with the earlier comments that this can be a single patch. There's a little bit that could maybe be split out (when it comes to the bits that are mostly just moving things around and removing things) but I'm not convinced it's necessary or, really, particularly worth it. > >> diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c > >> index a06fc7dc82..f4f196e3b4 100644 > >> --- a/src/interfaces/libpq/fe-secure.c > >> +++ b/src/interfaces/libpq/fe-secure.c > >> @@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len) > >> n = pgtls_read(conn, ptr, len); > >> } > >> else > >> +#endif > >> +#ifdef ENABLE_GSS > >> + if (conn->gssenc) > >> + { > >> + n = pg_GSS_read(conn, ptr, len); > >> + } > >> + else > >> #endif > >> { > >> n = pqsecure_raw_read(conn, ptr, len); > >> @@ -287,7 +294,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; > >> > >> @@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len) > >> n = pgtls_write(conn, ptr, len); > >> } > >> else > >> +#endif > >> +#ifdef ENABLE_GSS > >> + if (conn->gssenc) > >> + { > >> + n = pg_GSS_write(conn, ptr, len); > >> + } > >> + else > >> #endif > >> { > >> n = pqsecure_raw_write(conn, ptr, len); > > > > Not a fan of this. Seems like we'll grow more and more such branches > > over time? Wouldn't the right thing be to have callbacks in PGconn > > (and similarly in the backend)? > > Is that really a problem? Each branch is only seven lines, which is a > lot less than adding callback support will be. And we'll only get a new > branch when we want to support a new encryption protocol - unlike > authentication, there aren't too many of those. Considering this is only the second encryption protocol in the project's lifetime, I agree that using callbacks would be overkill here. What other encryption protocols are you thinking we would be adding here? I think most would be quite hard-pressed to name a second general-purpose one beyond TLS/SSL, and those who can almost certainly would say GSS, but a third? Certainly OpenSSH has its own, but it's not intended to be general purpose and I can't see us adding support for OpenSSH's encryption protocol to PG. Adding support for different libraries which implement TLS/SSL wouldn't be changing this part of the code, if that was perhaps what was being considered..? > > Seems like if that's done reasonably it'd also make integration of > > compression easier, because that could just layer itself between > > encryption and wire? Building a general-purpose filtering mechanism for streams is actually quite a bit of work (we did it for pgBackRest, feel free to check it out- and all that code is in C these days too) and I don't think it's really necessary when the options are "optionally compress and optionally encrypt using one of these two protocols". I don't see any reason why we'd need to, say, compress a stream multiple times, or encrypt it multiple times (like with TLS first and then GSS). > The current interface would allow a compress/decompress call in a way > that makes sense to me (here for write, ignoring ifdefs): [...] > (pqsecure_read would look similarly, with decompression as the last step > instead of the first.) That certainly seems reasonable to me. Thanks! Stephen
Attachment
On 2019-02-18 16:32, Stephen Frost wrote: > Considering this is only the second encryption protocol in the project's > lifetime, I agree that using callbacks would be overkill here. What > other encryption protocols are you thinking we would be adding here? I > think most would be quite hard-pressed to name a second general-purpose > one beyond TLS/SSL, and those who can almost certainly would say GSS, > but a third? Certainly OpenSSH has its own, but it's not intended to be > general purpose and I can't see us adding support for OpenSSH's > encryption protocol to PG. I did look into an SSH-based encryption layer at one point. It's certainly attractive in terms of simplicity over SSL. But your point stands nonetheless, for two or three plausible implementations, we don't necessarily need a generic plugin system. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Stephen Frost <sfrost@snowman.net> writes: > * Robbie Harwood (rharwood@redhat.com) wrote: >> Stephen Frost <sfrost@snowman.net> writes: >>> * Robbie Harwood (rharwood@redhat.com) wrote: >>>> Stephen Frost <sfrost@snowman.net> writes: >>>>> * Robbie Harwood (rharwood@redhat.com) wrote: >>>>> >>>>>> Attached please find version 20 of the GSSAPI encryption support. >>>>>> This has been rebased onto master (thanks Stephen for calling out >>>>>> ab69ea9). >>>>> >>>>> I've looked over this again and have been playing with it >>>>> off-and-on for a while now. One thing I was actually looking at >>>>> implementing before we push to commit this was to have similar >>>>> information to what SSL provides on connection, specifically what >>>>> printSSLInfo() prints by way of cipher, bits, etc for the >>>>> client-side and then something like pg_stat_ssl for the server >>>>> side. >>>>> >>>>> I went ahead and added a printGSSENCInfo(), and then >>>>> PQgetgssSASLSSF(), which calls down into >>>>> gss_inquire_sec_context_by_oid() for GSS_C_SEC_CONTEXT_SASL_SSF to >>>>> get the bits (which also required including gssapi_ext.h), and now >>>>> have psql producing: >>>>> >>>>> I don't think these things are absolutely required to push the >>>>> patch forward, just to be clear, but it's helping my confidence >>>>> level, at least, and would make it closer to on-par with our >>>>> current SSL implementation. They also seem, well, reasonably >>>>> straight-forward to add and quite useful. >>>> >>>> Auditability is definitely reasonable. I think there are a couple >>>> additional wrinkles discussed above, but we can probably get them >>>> sorted. >>> >>> Fantastic. Do you think you'll have time to work through some of >>> the above with me over the next couple of weeks? I was just >>> starting to look into adding things into the beentry to hold >>> information on the server side. >> >> Sure! I'll go ahead and hack up the checks and lucid stuff and get >> back to you. > > Great! I'll finish fleshing out the basics of how to have this work > server-side and once the checks and lucid stuff are in on the psql > side, it should be pretty straight-forward to copy that same > information into beentry alongside the SSL info, and expose it through > pg_stat_get_activity() into a pg_stat_gss view. When the mech is gss_mech_krb5 under MIT krb5: psql (12devel) GSSAPI encrypted connection (krb5 using aes256-cts-hmac-sha384-192) Type "help" for help. And the same under Heimdal: psql (12devel) GSSAPI encrypted connection (krb5 using aes256-cts-hmac-sha1-96) Type "help" for help If the mech weren't gss_krb5, or Lucid introspection failed, but we're a SASL-aware mech (and using MIT): psql (12devel) GSSAPI encrypted connection (~256 bits) Type "help" for help. The third case (no lucid, no SASL SSF): psql (12devel) GSSAPI encrypted connection (unknown mechanism) Type "help" for help. Feel free to tweak these strings as needed. I've also adjusted the layering somewhat and moved the actual printf() call down into fe-secure-gssapi.c I don't know whether this model makes more sense in the long run, but for PoC code it was marginally easier to reason about. Another thing I've been thinking about here is whether the SASL SSF logic is useful. It'll only get invoked when the mechanism isn't gss_mech_krb5, and only under MIT. SPNEGO (krb5), NTLM (gss-ntlmssp), IAKERB (krb5), and EAP (moonshot) all don't support GSS_C_SEC_CONTEXT_SASL_SSF; I actually couldn't come up with another mechanism that does. I defer to you on whether to remove that, though. I did some testing with Heimdal since we're using some extensions here, and found out that the current build machinery doesn't support Heimdal. (The configure.in detection logic only seems to work for MIT and Solaris.) However, it's possible to explicitly pass in CFLAGS/LDFLAGS and it worked prior to my changes, so I've preserved that behavior. Finally, as this patch touches configure.in, configure needs to be regenerated; I'm not sure what project policy is on when that happens (and it produces rather a lot of churn on my machine). Patch attached after the break; apply on top of -20. Thanks, --Robbie From 0f32efdb9b201a3b2d25d3fc41c9758790c84b93 Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Wed, 20 Feb 2019 17:23:06 -0500 Subject: [PATCH] Log encryption strength in libpq GSSAPI connections --- configure.in | 20 ++++ src/bin/psql/command.c | 2 + src/include/pg_config.h.in | 6 ++ src/interfaces/libpq/exports.txt | 1 + src/interfaces/libpq/fe-secure-gssapi.c | 127 ++++++++++++++++++++++++ src/interfaces/libpq/fe-secure.c | 11 ++ src/interfaces/libpq/libpq-fe.h | 3 + 7 files changed, 170 insertions(+) diff --git a/configure.in b/configure.in index 89a0fb2470..be011778f3 100644 --- a/configure.in +++ b/configure.in @@ -1191,6 +1191,26 @@ if test "$with_gssapi" = yes ; then if test "$PORTNAME" != "win32"; then AC_SEARCH_LIBS(gss_init_sec_context, [gssapi_krb5 gss 'gssapi -lkrb5 -lcrypto'], [], [AC_MSG_ERROR([could not find function 'gss_init_sec_context' required for GSSAPI])]) + + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [#include <krb5.h>], + [[(void)krb5_enctype_to_name]] + )], + [AC_DEFINE(HAVE_KRB5_ENCTYPE_TO_NAME, 1, + [Define to 1 if you have support for krb5_enctype_to_name], + [] + )]) + + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [#include <gssapi/gssapi_krb5.h>], + [[gss_OID g = GSS_C_SEC_CONTEXT_SASL_SSF]] + )], + [AC_DEFINE(HAVE_GSS_C_SEC_CONTEXT_SASL_SSF, 1, + [Define to 1 if you have support for GSS_C_SEC_CONTEXT_SASL_SSF], + [] + )]) else LIBS="$LIBS -lgssapi32" fi diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 55315fe43b..042e585c20 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -621,6 +621,7 @@ exec_command_conninfo(PsqlScanState scan_state, bool active_branch) db, PQuser(pset.db), host, PQport(pset.db)); } printSSLInfo(); + PQprintGSSENCInfo(pset.db); } } @@ -3184,6 +3185,7 @@ connection_warnings(bool in_startup) checkWin32Codepage(); #endif printSSLInfo(); + PQprintGSSENCInfo(pset.db); } } diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 76bd81e9bf..903e1aad56 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -290,6 +290,9 @@ /* Define to 1 if you have the <gssapi.h> header file. */ #undef HAVE_GSSAPI_H +/* Define to 1 if you have support for GSS_C_SEC_CONTEXT_SASL_SSF */ +#undef HAVE_GSS_C_SEC_CONTEXT_SASL_SSF + /* Define to 1 if you have the <history.h> header file. */ #undef HAVE_HISTORY_H @@ -332,6 +335,9 @@ /* Define to 1 if you have isinf(). */ #undef HAVE_ISINF +/* Define to 1 if you have support for krb5_enctype_to_name */ +#undef HAVE_KRB5_ENCTYPE_TO_NAME + /* Define to 1 if you have the <langinfo.h> header file. */ #undef HAVE_LANGINFO_H diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index cc9ee9ce6b..2075ebde0c 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -174,3 +174,4 @@ PQresultVerboseErrorMessage 171 PQencryptPasswordConn 172 PQresultMemorySize 173 PQhostaddr 174 +PQprintGSSENCInfo 175 diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c index 5d9c1f8f5b..821dd2ac9e 100644 --- a/src/interfaces/libpq/fe-secure-gssapi.c +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -17,6 +17,11 @@ #include "libpq-int.h" #include "fe-gssapi-common.h" +#include "port/pg_bswap.h" + +#include <gssapi/gssapi_krb5.h> +#include <krb5.h> + /* * Require encryption support, as well as mutual authentication and * tamperproofing measures. @@ -24,6 +29,11 @@ #define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \ GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG +static ssize_t send_buffered_data(PGconn *conn, size_t len); +static ssize_t read_from_buffer(PGconn *conn, void *ptr, size_t len); +static ssize_t load_packet_length(PGconn *conn); +static ssize_t load_packet(PGconn *conn, size_t len); + /* * Helper function to write any pending data. Returns len when it has all * been written; otherwise, sets errno appropriately. Assumes there is data @@ -392,3 +402,120 @@ pqsecure_open_gss(PGconn *conn) gss_release_buffer(&minor, &output); return PGRES_POLLING_WRITING; } + +#ifndef HAVE_KRB5_ENCTYPE_TO_NAME +/* Heimdal doesn't (yet) support krb5_enctype_to_name(), but its + * krb5_enctype_to_string() has similar behavior. (MIT's + * krb5_enctype_to_string() produces very verbose output that we don't want, + * and has different calling convention.) This wrapper gives us approximate + * parity between the two. + * + * Heimdal issue: https://github.com/heimdal/heimdal/issues/525 */ +static krb5_error_code +krb5_enctype_to_name(krb5_enctype enctype, krb5_boolean shortest, + char *buffer, size_t buflen) +{ + krb5_error_code ret; + krb5_context ctx; + char *outstr = NULL; + + ret = krb5_init_context(&ctx); + if (ret != 0) + return ret; + + ret = krb5_enctype_to_string(ctx, enctype, &outstr); + if (ret != 0) + goto cleanup; + + strncpy(buffer, outstr, buflen); + +cleanup: + free(outstr); + krb5_free_context(ctx); + return ret; +} +#endif + +/* + * Prints information about the current GSS-Encrypted connection, if GSS + * encryption is in use. + */ +void +PQprintGSSENCInfo(PGconn *conn) +{ + OM_uint32 major, + minor; + gss_OID mech = GSS_C_NO_OID; + gss_krb5_lucid_context_v1_t *lucid = NULL; + gss_buffer_set_t bufset = GSS_C_NO_BUFFER_SET; + char enctype_buf[128]; + krb5_error_code ret; + void *lptr; + + if (!conn || !conn->gctx) + return; + + /* Get underlying GSS mechanism. */ + (void) gss_inquire_context(&minor, conn->gctx, NULL, NULL, NULL, &mech, + NULL, NULL, NULL); + if (gss_oid_equal(mech, gss_mech_krb5)) + { + /* Preferred case - use lucid interface to get underlying enctype. */ + major = gss_krb5_export_lucid_sec_context(&minor, &conn->gctx, 1, + &lptr); + if (major == GSS_S_COMPLETE) + lucid = lptr; + if (major == GSS_S_COMPLETE && lucid->protocol == 1) + { + ret = krb5_enctype_to_name(lucid->cfx_kd.ctx_key.type, 0, + enctype_buf, sizeof(enctype_buf)); + if (ret == 0) + { + printf(_("GSSAPI encrypted connection (krb5 using %s)\n"), + enctype_buf); + goto cleanup; + } + } + } + +#ifdef HAVE_GSS_C_SEC_CONTEXT_SASL_SSF + { + uint32 ssf_result = 0, + tmp4; + + /* + * Fall back to trying for SASL SSF - query the value and range-check + * the result. Not all GSSAPI mechanisms can implement this + * extension, and it's not supported by the Heimdal GSSAPI + * implementation. + */ + major = gss_inquire_sec_context_by_oid(&minor, conn->gctx, + GSS_C_SEC_CONTEXT_SASL_SSF, + &bufset); + if (major == GSS_S_COMPLETE && bufset->elements[0].length == 4) + { + /* + * gss_inquire_sec_context_by_oid() for GSS_C_SEC_CONTEXT_SASL_SSF + * returns a 32bit integer in network byte-order, so we need to + * adjust for that and then print the integer into our text + * string. + */ + memcpy(&tmp4, bufset->elements[0].value, 4); + ssf_result = pg_ntoh32(tmp4); + if (ssf_result >= 56 && ssf_result <= 256) + { + printf(_("GSSAPI encrypted connection (~%d bits)\n"), + ssf_result); + goto cleanup; + } + } + } +#endif + + printf(_("GSSAPI encrypted connection (unknown mechanism)\n")); + +cleanup: + if (lucid) + gss_krb5_free_lucid_sec_context(&minor, lucid); + gss_release_buffer_set(&minor, &bufset); +} diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c index f4f196e3b4..f18fc94e1c 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -434,6 +434,17 @@ PQsslAttributeNames(PGconn *conn) } #endif /* USE_SSL */ +/* Dummy versions of GSSAPI logging function, when built without GSS support */ +#ifndef ENABLE_GSS + +void +PQprintGSSENCInfo(PGconn *conn) +{ + return; +} + +#endif /* ENABLE_GSS */ + #if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32) diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index e8e0a9529a..e41fc3d5ec 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -347,6 +347,9 @@ extern void PQinitSSL(int do_init); /* More detailed way to tell libpq whether it needs to initialize OpenSSL */ extern void PQinitOpenSSL(int do_ssl, int do_crypto); +/* GSSAPI logging function */ +extern void PQprintGSSENCInfo(PGconn *conn); + /* Set verbosity for PQerrorMessage and PQresultErrorMessage */ extern PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity); -- 2.20.1
Attachment
Greetings, * Robbie Harwood (rharwood@redhat.com) wrote: > Stephen Frost <sfrost@snowman.net> writes: > > * Robbie Harwood (rharwood@redhat.com) wrote: > >> Sure! I'll go ahead and hack up the checks and lucid stuff and get > >> back to you. > > > > Great! I'll finish fleshing out the basics of how to have this work > > server-side and once the checks and lucid stuff are in on the psql > > side, it should be pretty straight-forward to copy that same > > information into beentry alongside the SSL info, and expose it through > > pg_stat_get_activity() into a pg_stat_gss view. > > When the mech is gss_mech_krb5 under MIT krb5: > > psql (12devel) > GSSAPI encrypted connection (krb5 using aes256-cts-hmac-sha384-192) > Type "help" for help. > > And the same under Heimdal: > > psql (12devel) > GSSAPI encrypted connection (krb5 using aes256-cts-hmac-sha1-96) > Type "help" for help > > If the mech weren't gss_krb5, or Lucid introspection failed, but we're a > SASL-aware mech (and using MIT): > > psql (12devel) > GSSAPI encrypted connection (~256 bits) > Type "help" for help. > > The third case (no lucid, no SASL SSF): > > psql (12devel) > GSSAPI encrypted connection (unknown mechanism) > Type "help" for help. > > Feel free to tweak these strings as needed. That all looks fantastic! Do you see any reason to not say: GSSAPI encrypted connection (SASL SSF: ~256 bits) instead, since that's what we are technically reporting there? > I've also adjusted the layering somewhat and moved the actual printf() > call down into fe-secure-gssapi.c I don't know whether this model makes > more sense in the long run, but for PoC code it was marginally easier to > reason about. No, I think we need to provide a way for libpq-using applications to get at that information easily.. > Another thing I've been thinking about here is whether the SASL SSF > logic is useful. It'll only get invoked when the mechanism isn't > gss_mech_krb5, and only under MIT. SPNEGO (krb5), NTLM (gss-ntlmssp), > IAKERB (krb5), and EAP (moonshot) all don't support > GSS_C_SEC_CONTEXT_SASL_SSF; I actually couldn't come up with another > mechanism that does. I defer to you on whether to remove that, though. Oh, hmm, I see. Yeah, since there's no case where that could actually end up being used today then perhaps it makes sense to remove it- it's not a terribly good interface anyway since it doesn't provide the actual encryption algorithm, I had just gone down that route because I saw how to. The lucid stuff is much better. :) > I did some testing with Heimdal since we're using some extensions here, > and found out that the current build machinery doesn't support Heimdal. Hah! > (The configure.in detection logic only seems to work for MIT and > Solaris.) However, it's possible to explicitly pass in CFLAGS/LDFLAGS > and it worked prior to my changes, so I've preserved that behavior. Alright. That seems like an independent change, if we decide to make it easier for people to build with heimdal, but there hasn't been much call for it, clearly. > Finally, as this patch touches configure.in, configure needs to be > regenerated; I'm not sure what project policy is on when that happens > (and it produces rather a lot of churn on my machine). There's some magic there due to vendor changes to autoconf, as I recall, which is likely why you're seeing a lot of churn. > Patch attached after the break; apply on top of -20. Ok. I'm pretty amazed at how little code it was to do.. Is there somewhere that these functions are publicly documented and how they can be used from a GSSAPI handle is documented? Thanks a lot for this work! Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > * Robbie Harwood (rharwood@redhat.com) wrote: >> Stephen Frost <sfrost@snowman.net> writes: >>> * Robbie Harwood (rharwood@redhat.com) wrote: >>> >>>> Sure! I'll go ahead and hack up the checks and lucid stuff and get >>>> back to you. >>> >>> Great! I'll finish fleshing out the basics of how to have this work >>> server-side and once the checks and lucid stuff are in on the psql >>> side, it should be pretty straight-forward to copy that same >>> information into beentry alongside the SSL info, and expose it >>> through pg_stat_get_activity() into a pg_stat_gss view. >> >> When the mech is gss_mech_krb5 under MIT krb5: >> >> psql (12devel) >> GSSAPI encrypted connection (krb5 using aes256-cts-hmac-sha384-192) >> Type "help" for help. >> >> And the same under Heimdal: >> >> psql (12devel) >> GSSAPI encrypted connection (krb5 using aes256-cts-hmac-sha1-96) >> Type "help" for help >> >> If the mech weren't gss_krb5, or Lucid introspection failed, but we're a >> SASL-aware mech (and using MIT): >> >> psql (12devel) >> GSSAPI encrypted connection (~256 bits) >> Type "help" for help. >> >> The third case (no lucid, no SASL SSF): >> >> psql (12devel) >> GSSAPI encrypted connection (unknown mechanism) >> Type "help" for help. >> >> Feel free to tweak these strings as needed. > > That all looks fantastic! Do you see any reason to not say: > > GSSAPI encrypted connection (SASL SSF: ~256 bits) > > instead, since that's what we are technically reporting there? Nope, that'd be fine with me! (We'd probably want to get rid of the ~ in that case; I'd included it since SASL SSF is an approximate bit measure, but 256 is the exact SSF.) >> Another thing I've been thinking about here is whether the SASL SSF >> logic is useful. It'll only get invoked when the mechanism isn't >> gss_mech_krb5, and only under MIT. SPNEGO (krb5), NTLM >> (gss-ntlmssp), IAKERB (krb5), and EAP (moonshot) all don't support >> GSS_C_SEC_CONTEXT_SASL_SSF; I actually couldn't come up with another >> mechanism that does. I defer to you on whether to remove that, >> though. > > Oh, hmm, I see. Yeah, since there's no case where that could actually > end up being used today then perhaps it makes sense to remove it- it's > not a terribly good interface anyway since it doesn't provide the > actual encryption algorithm, I had just gone down that route because I > saw how to. The lucid stuff is much better. :) > >> I've also adjusted the layering somewhat and moved the actual >> printf() call down into fe-secure-gssapi.c I don't know whether this >> model makes more sense in the long run, but for PoC code it was >> marginally easier to reason about. > > No, I think we need to provide a way for libpq-using applications to > get at that information easily.. Well, it's easier if there's only one type of thing (string) that can be returned at least. I imagine the interface there has to be pass buffer-and-size into the function in fe-secure-gssapi.c then? Do you want me to make that change, or would you prefer to do it as part of the server logging logic? >> Patch attached after the break; apply on top of -20. > > Ok. I'm pretty amazed at how little code it was to do.. The autotools part took the longest :) > Is there somewhere that these functions are publicly documented and > how they can be used from a GSSAPI handle is documented? Not in the way you're hoping for, I suspect. This interface is only intended for consumption by NFS - which needs to pass contexts in and out of the kernel. Unlike GSSAPI, Kerberos5 interfaces aren't standardized at all - parity between MIT and Heimdal is pretty low on the krb5_*(). I was just referencing MIT's header files: https://github.com/krb5/krb5/blob/master/src/lib/gssapi/krb5/gssapi_krb5.h#L229 (with the goal in mind of hitting krb5_enctype_to_name()) https://github.com/krb5/krb5/blob/master/src/include/krb5/krb5.hin#L6284-L6302 (Heimdal doesn't have any documentation/example code, but it works the same way for lucid stuff; I had to look at the source to see how its variant of krb5_enctype_to_string() worked.) Thanks, --Robbie
Attachment
I don't know much about GSSAPI, but from what I can tell, this seems an attractive feature, and the implementation is compact enough. I have done a bit of work on the internal SSL API refactoring, so I have some thoughts on this patch. Looking at the file structure, we would have be-secure.c be-secure-openssl.c be-secure-[othersslimpl].c be-secure-gssapi.c be-secure-common.c This implies a code structure that isn't really there. be-secure-common.c is used by SSL implementations but not by the GSSAPI implementation. Perhaps we should rename be-secure-openssl.c to be-ssl-openssl.c and be-secure-common.c to be-ssl-common.c. Or maybe we avoid that, and you rename be-secure-gssapi.c to just be-gssapi.c and also combine that with the contents of be-gssapi-common.c. (Or maybe both.) (And similarly in libpq.) About pg_hba.conf: The "hostgss" keyword seems a bit confusing. It only applies to encrypted gss-using connections, not all of them. Maybe "hostgssenc" or "hostgsswrap"? I don't see any tests in the patch. We have a Kerberos test suite at src/test/kerberos/ and an SSL test suite at src/test/ssl/. You can get some ideas there. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Greetings, * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: > I don't know much about GSSAPI, but from what I can tell, this seems an > attractive feature, and the implementation is compact enough. I have > done a bit of work on the internal SSL API refactoring, so I have some > thoughts on this patch. > > Looking at the file structure, we would have > > be-secure.c > be-secure-openssl.c > be-secure-[othersslimpl].c > be-secure-gssapi.c > be-secure-common.c > > This implies a code structure that isn't really there. > be-secure-common.c is used by SSL implementations but not by the GSSAPI > implementation. be-secure-common.c seems very clearly mis-named, I mean, look at the comment at the top of the file: * common implementation-independent SSL support code Seems we've been conflating SSL and "Secure" and we should probably fix that. What I also really don't like is that "secure_read()" is really only *maybe* secure. be-secure.c is really just an IO-abstraction layer that lets other things hook in and implement the read/write themselves. > Perhaps we should rename be-secure-openssl.c to be-ssl-openssl.c and > be-secure-common.c to be-ssl-common.c. This might be overly pedantic, but what we do in other parts of the tree is use these things called directories.. I do think we need to rename be-secure-common since it's just flat out wrong as-is, but that's independent of the GSSAPI encryption work, really. > Or maybe we avoid that, and you rename be-secure-gssapi.c to just > be-gssapi.c and also combine that with the contents of be-gssapi-common.c. I don't know why we would need to, or want to, combine be-secure-gssapi.c and be-gssapi-common.c, they do have different roles in that be-gssapi-common.c is used even if you aren't doing encryption, while bs-secure-gssapi.c is specifically for handling the encryption side of things. Then again, be-gssapi-common.c does currently only have the one function in it, and it's not like there's an option to compile for *just* GSS authentication (and not encryption), or at least, I don't think that would be a useful option to have.. Robbie? > (And similarly in libpq.) I do agree that we should try to keep the frontend and backend code structures similar in this area, that seems to make sense. > About pg_hba.conf: The "hostgss" keyword seems a bit confusing. It only > applies to encrypted gss-using connections, not all of them. Maybe > "hostgssenc" or "hostgsswrap"? Not quite sure what you mean here, but 'hostgss' seems to be quite well in-line with what we do for SSL... as in, we have 'hostssl', we don't say 'hostsslenc'. I feel like I'm just not understanding what you mean by "not all of them". > I don't see any tests in the patch. We have a Kerberos test suite at > src/test/kerberos/ and an SSL test suite at src/test/ssl/. You can get > some ideas there. Yeah, I was going to comment on that as well. We definitely should implement tests around this. Thanks! Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: > >> Or maybe we avoid that, and you rename be-secure-gssapi.c to just >> be-gssapi.c and also combine that with the contents of >> be-gssapi-common.c. > > I don't know why we would need to, or want to, combine > be-secure-gssapi.c and be-gssapi-common.c, they do have different > roles in that be-gssapi-common.c is used even if you aren't doing > encryption, while bs-secure-gssapi.c is specifically for handling the > encryption side of things. > > Then again, be-gssapi-common.c does currently only have the one > function in it, and it's not like there's an option to compile for > *just* GSS authentication (and not encryption), or at least, I don't > think that would be a useful option to have.. Robbie? Yeah, I think I'm opposed to making that an option. Worth pointing out here: up until v6, I had this structured differently, with all the GSSAPI code in fe-gssapi.c and be-gssapi.c. The current separation was suggested by Michael Paquier for ease of reading and to keep the code churn down. >> (And similarly in libpq.) > > I do agree that we should try to keep the frontend and backend code > structures similar in this area, that seems to make sense. Well, I don't know if it's an argument in either direction, but: on the frontend we have about twice as much shared code in fe-gssapi-common.c (pg_GSS_have_ccache() and pg_GSS_load_servicename()). >> I don't see any tests in the patch. We have a Kerberos test suite at >> src/test/kerberos/ and an SSL test suite at src/test/ssl/. You can >> get some ideas there. > > Yeah, I was going to comment on that as well. We definitely should > implement tests around this. Attached. Please note that I don't really speak perl. There's a pile of duplicated code in 002_enc.pl that probably should be shared between the two. (It would also I think be possible for 001_auth.pl to set up the KDC and for 002_enc.pl to then use it.) Thanks, --Robbie From 42ab1ccae8e517934866ee923d80554ef1996709 Mon Sep 17 00:00:00 2001 From: Robbie Harwood <rharwood@redhat.com> Date: Tue, 5 Mar 2019 22:54:11 -0500 Subject: [PATCH] Add tests for GSSAPI/krb5 encryption --- src/test/kerberos/t/002_enc.pl | 197 +++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 src/test/kerberos/t/002_enc.pl diff --git a/src/test/kerberos/t/002_enc.pl b/src/test/kerberos/t/002_enc.pl new file mode 100644 index 0000000000..927abe15e4 --- /dev/null +++ b/src/test/kerberos/t/002_enc.pl @@ -0,0 +1,197 @@ +use strict; +use warnings; +use TestLib; +use PostgresNode; +use Test::More; +use File::Path 'remove_tree'; + +if ($ENV{with_gssapi} eq 'yes') +{ + plan tests => 5; +} +else +{ + plan skip_all => 'GSSAPI/Kerberos not supported by this build'; +} + +my ($krb5_bin_dir, $krb5_sbin_dir); + +if ($^O eq 'darwin') +{ + $krb5_bin_dir = '/usr/local/opt/krb5/bin'; + $krb5_sbin_dir = '/usr/local/opt/krb5/sbin'; +} +elsif ($^O eq 'freebsd') +{ + $krb5_bin_dir = '/usr/local/bin'; + $krb5_sbin_dir = '/usr/local/sbin'; +} +elsif ($^O eq 'linux') +{ + $krb5_sbin_dir = '/usr/sbin'; +} + +my $krb5_config = 'krb5-config'; +my $kinit = 'kinit'; +my $kdb5_util = 'kdb5_util'; +my $kadmin_local = 'kadmin.local'; +my $krb5kdc = 'krb5kdc'; + +if ($krb5_bin_dir && -d $krb5_bin_dir) +{ + $krb5_config = $krb5_bin_dir . '/' . $krb5_config; + $kinit = $krb5_bin_dir . '/' . $kinit; +} +if ($krb5_sbin_dir && -d $krb5_sbin_dir) +{ + $kdb5_util = $krb5_sbin_dir . '/' . $kdb5_util; + $kadmin_local = $krb5_sbin_dir . '/' . $kadmin_local; + $krb5kdc = $krb5_sbin_dir . '/' . $krb5kdc; +} + +my $host = 'auth-test-localhost.postgresql.example.com'; +my $hostaddr = '127.0.0.1'; +my $realm = 'EXAMPLE.COM'; + +my $krb5_conf = "${TestLib::tmp_check}/krb5.conf"; +my $kdc_conf = "${TestLib::tmp_check}/kdc.conf"; +my $krb5_log = "${TestLib::tmp_check}/krb5libs.log"; +my $kdc_log = "${TestLib::tmp_check}/krb5kdc.log"; +my $kdc_port = int(rand() * 16384) + 49152; +my $kdc_datadir = "${TestLib::tmp_check}/krb5kdc"; +my $kdc_pidfile = "${TestLib::tmp_check}/krb5kdc.pid"; +my $keytab = "${TestLib::tmp_check}/krb5.keytab"; + +note "setting up Kerberos"; + +my ($stdout, $krb5_version); +run_log [ $krb5_config, '--version' ], '>', \$stdout + or BAIL_OUT("could not execute krb5-config"); +BAIL_OUT("Heimdal is not supported") if $stdout =~ m/heimdal/; +$stdout =~ m/Kerberos 5 release ([0-9]+\.[0-9]+)/ + or BAIL_OUT("could not get Kerberos version"); +$krb5_version = $1; + +append_to_file( + $krb5_conf, + qq![logging] +default = FILE:$krb5_log +kdc = FILE:$kdc_log + +[libdefaults] +default_realm = $realm + +[realms] +$realm = { + kdc = $hostaddr:$kdc_port +}!); + +append_to_file( + $kdc_conf, + qq![kdcdefaults] +!); + +# For new-enough versions of krb5, use the _listen settings rather +# than the _ports settings so that we can bind to localhost only. +if ($krb5_version >= 1.15) +{ + append_to_file( + $kdc_conf, + qq!kdc_listen = $hostaddr:$kdc_port +kdc_tcp_listen = $hostaddr:$kdc_port +!); +} +else +{ + append_to_file( + $kdc_conf, + qq!kdc_ports = $kdc_port +kdc_tcp_ports = $kdc_port +!); +} +append_to_file( + $kdc_conf, + qq! +[realms] +$realm = { + database_name = $kdc_datadir/principal + admin_keytab = FILE:$kdc_datadir/kadm5.keytab + acl_file = $kdc_datadir/kadm5.acl + key_stash_file = $kdc_datadir/_k5.$realm +}!); + +remove_tree $kdc_datadir; +mkdir $kdc_datadir or die; + +$ENV{'KRB5_CONFIG'} = $krb5_conf; +$ENV{'KRB5_KDC_PROFILE'} = $kdc_conf; + +my $service_principal = "$ENV{with_krb_srvnam}/$host"; + +system_or_bail $kdb5_util, 'create', '-s', '-P', 'secret0'; + +my $test1_password = 'secret1'; +system_or_bail $kadmin_local, '-q', "addprinc -pw $test1_password test1"; + +system_or_bail $kadmin_local, '-q', "addprinc -randkey $service_principal"; +system_or_bail $kadmin_local, '-q', "ktadd -k $keytab $service_principal"; + +system_or_bail $krb5kdc, '-P', $kdc_pidfile; + +END +{ + kill 'INT', `cat $kdc_pidfile` if -f $kdc_pidfile; +} + +note "setting up PostgreSQL instance"; + +my $node = get_new_node('node'); +$node->init; +$node->append_conf('postgresql.conf', "listen_addresses = 'localhost'"); +$node->append_conf('postgresql.conf', "krb_server_keyfile = '$keytab'"); +$node->start; + +$node->safe_psql('postgres', 'CREATE USER test1;'); + +note "running tests"; + +sub test_access +{ + my ($node, $gssmode, $expected_res, $test_name) = @_; + + my $res = $node->psql( + "postgres", + "SELECT 1", + extra_params => [ + "-d", + $node->connstr("postgres") . " host=$host hostaddr=$hostaddr gssmode=$gssmode", + "-U", "test1", + ]); + is($res, $expected_res, $test_name); + return; +} + +unlink($node->data_dir . "/pg_ident.conf"); +$node->append_conf("pg_ident.conf", 'mymap /^(.*)@EXAMPLE.COM$ \1'); +run_log [ $kinit, 'test1' ], \$test1_password or BAIL_OUT($?); + +unlink($node->data_dir . '/pg_hba.conf'); +$node->append_conf('pg_hba.conf', + qq{hostgss all all $hostaddr/32 gss map=mymap}); +$node->restart; +test_access($node, "require", 0, "GSS-encrypted access"); +test_access($node, "disable", 2, "GSS encryption disabled"); + +unlink($node->data_dir . "/pg_hba.conf"); +$node->append_conf("pg_hba.conf", qq{hostgss all all $hostaddr/32 trust}); +$node->restart; +test_access($node, "require", 0, "GSS encryption without auth"); + +unlink($node->data_dir . "/pg_hba.conf"); +$node->append_conf("pg_hba.conf", + qq{hostnogss all all localhost gss map=mymap}); +$node->restart; +test_access($node, "prefer", 0, "GSS unencrypted fallback"); + +# Check that the client can prevent fallback. +test_access($node, "require", 2, "GSS unencrypted fallback prevention"); -- 2.20.1
Attachment
Greetings! * Robbie Harwood (rharwood@redhat.com) wrote: > Stephen Frost <sfrost@snowman.net> writes: > > * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: > >> Or maybe we avoid that, and you rename be-secure-gssapi.c to just > >> be-gssapi.c and also combine that with the contents of > >> be-gssapi-common.c. > > > > I don't know why we would need to, or want to, combine > > be-secure-gssapi.c and be-gssapi-common.c, they do have different > > roles in that be-gssapi-common.c is used even if you aren't doing > > encryption, while bs-secure-gssapi.c is specifically for handling the > > encryption side of things. > > > > Then again, be-gssapi-common.c does currently only have the one > > function in it, and it's not like there's an option to compile for > > *just* GSS authentication (and not encryption), or at least, I don't > > think that would be a useful option to have.. Robbie? > > Yeah, I think I'm opposed to making that an option. Yeah, I tend to agree, seems silly. > Worth pointing out here: up until v6, I had this structured differently, > with all the GSSAPI code in fe-gssapi.c and be-gssapi.c. The current > separation was suggested by Michael Paquier for ease of reading and to > keep the code churn down. I'm still a bit on the fence myself regarding the filenames and where things exist today. I do agree that it might make sense to move things around to make the code structure clearer but I also think that doesn't necessairly have to be done at the same time as this patch. > >> (And similarly in libpq.) > > > > I do agree that we should try to keep the frontend and backend code > > structures similar in this area, that seems to make sense. > > Well, I don't know if it's an argument in either direction, but: on the > frontend we have about twice as much shared code in fe-gssapi-common.c > (pg_GSS_have_ccache() and pg_GSS_load_servicename()). Yeah, that's an interesting point, and I do wonder if we will actually end up having code that's shared between the frontend and backend eventually (if we can figure out how to pull out the encryption algorithm info, for example). > >> I don't see any tests in the patch. We have a Kerberos test suite at > >> src/test/kerberos/ and an SSL test suite at src/test/ssl/. You can > >> get some ideas there. > > > > Yeah, I was going to comment on that as well. We definitely should > > implement tests around this. > > Attached. Please note that I don't really speak perl. There's a pile > of duplicated code in 002_enc.pl that probably should be shared between > the two. (It would also I think be possible for 001_auth.pl to set up > the KDC and for 002_enc.pl to then use it.) I don't think the code duplication between the tests is really all that much of an issue, though I wouldn't complain if someone wanted to work on improving that situation. Thanks a lot for adding those test though! One of the things that I really didn't care for in this patch was the use of the string buffers, without any real checks (except for "oh, you tried to allocated over 1G"...) to make sure that the other side of the connection wasn't feeding us ridiculous packets, and with the resizing of the buffers, etc, that really shouldn't be necessary. After chatting with Robbie about these concerns while reading through the code, we agreed that we should be able to use fixed buffer sizes and use the quite helpful gss_wrap_size_limit() to figure out how much data we can encrypt without going over our fixed buffer size. As Robbie didn't have time to implement those changes this past week, I did so, and added a bunch more comments and such too, and have now gone through and done more testing. Robbie has said that he should have time this upcoming week to review the changes that I made, and I'm planning to go back and review other parts of the patch more closely now as well. Note that there's an issue with exporting the context to get the encryption algorithm used that I've asked Robbie to look into, so that's no longer done and instead we just print that the connection is encrypted, if it is. If we can't figure out a way to make that work then obviously I'll pull out that code, and if we can get it to work then I'll update it to be done through libpq properly, as I had suggested earlier. That's really more of a nice to have in any case though, so I may just exclude it for now anyway if it ends up adding any complications. So, please find attached a new version, which also includes the tests and the other bits that Robbie had sent independent patches for. Thanks! Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > One of the things that I really didn't care for in this patch was the > use of the string buffers, without any real checks (except for "oh, > you tried to allocated over 1G"...) to make sure that the other side > of the connection wasn't feeding us ridiculous packets, and with the > resizing of the buffers, etc, that really shouldn't be necessary. > After chatting with Robbie about these concerns while reading through > the code, we agreed that we should be able to use fixed buffer sizes > and use the quite helpful gss_wrap_size_limit() to figure out how much > data we can encrypt without going over our fixed buffer size. As > Robbie didn't have time to implement those changes this past week, I > did so, and added a bunch more comments and such too, and have now > gone through and done more testing. Robbie has said that he should > have time this upcoming week to review the changes that I made, and > I'm planning to go back and review other parts of the patch more > closely now as well. In general this looks good - there are a couple minor comments inline, but it's fine. I wanted to note a couple things about this approach. It now uses one more buffer than before (in contrast to the previous approach, which reused a buffer for received data that was encrypted and decrypted). Since these are static fixed size buffers, this increases the total steady-state memory usage by 16k as opposed to re-using the buffer. This may be fine; I don't know how tight RAM is here. > Note that there's an issue with exporting the context to get the > encryption algorithm used that I've asked Robbie to look into, so > that's no longer done and instead we just print that the connection is > encrypted, if it is. If we can't figure out a way to make that work > then obviously I'll pull out that code, and if we can get it to work > then I'll update it to be done through libpq properly, as I had > suggested earlier. That's really more of a nice to have in any case > though, so I may just exclude it for now anyway if it ends up adding > any complications. Correct. Unfortunately I'd overlooked that the lucid interface won't meet our needs (destroys the context). So the two options here are: SASL SSF (and I'll separately push more mechs to add support for that), or nothing at all. If you want a patch for that I can make one, but I think there was code already... just needed a ./configure check program for whether the OID is defined. > +ssize_t > +be_gssapi_write(Port *port, void *ptr, size_t len) > +{ > + size_t bytes_to_encrypt = len; > + size_t bytes_encrypted = 0; > + > + /* > + * Loop through encrypting data and sending it out until > + * secure_raw_write() complains (which would likely mean that the socket > + * is non-blocking and the requested send() would block, or there was some > + * kind of actual error) and then return. > + */ > + while (bytes_to_encrypt || PqGSSSendPointer) > + { I guess it's not a view everyone will share, but I think this block is too long. Maybe a helper function around secure_raw_write() would help? (The check-and-send part at the start.) I have similar nits about the other functions that don't fit on my (86-line high) screen, though I guess a lot of this is due to project style using a lot of vertical space. > + if (GSS_ERROR(major)) > + pg_GSS_error(FATAL, gettext_noop("GSSAPI context error"), I'd prefer this to be a different error message than the init/accept errors - maybe something like "GSSAPI size check error"? > + if (GSS_ERROR(major)) > + pg_GSS_error(libpq_gettext("GSSAPI context error"), conn, Same here. Again, these are nits, and I think I'm okay with the changes. Thanks, --Robbie
Attachment
Greetings, * Robbie Harwood (rharwood@redhat.com) wrote: > Stephen Frost <sfrost@snowman.net> writes: > > > One of the things that I really didn't care for in this patch was the > > use of the string buffers, without any real checks (except for "oh, > > you tried to allocated over 1G"...) to make sure that the other side > > of the connection wasn't feeding us ridiculous packets, and with the > > resizing of the buffers, etc, that really shouldn't be necessary. > > After chatting with Robbie about these concerns while reading through > > the code, we agreed that we should be able to use fixed buffer sizes > > and use the quite helpful gss_wrap_size_limit() to figure out how much > > data we can encrypt without going over our fixed buffer size. As > > Robbie didn't have time to implement those changes this past week, I > > did so, and added a bunch more comments and such too, and have now > > gone through and done more testing. Robbie has said that he should > > have time this upcoming week to review the changes that I made, and > > I'm planning to go back and review other parts of the patch more > > closely now as well. > > In general this looks good - there are a couple minor comments inline, > but it's fine. Thanks for reviewing! > I wanted to note a couple things about this approach. It now uses one > more buffer than before (in contrast to the previous approach, which > reused a buffer for received data that was encrypted and decrypted). Yeah, I don't see that as too much of an issue and it certainly seems cleaner and simpler to reason about to me, which seems worth the modest additional buffer cost. That's certainly something we could change if others feel differently. > Since these are static fixed size buffers, this increases the total > steady-state memory usage by 16k as opposed to re-using the buffer. > This may be fine; I don't know how tight RAM is here. It seems unlikely to be an issue to me- and I would contend that the prior implementation didn't actually take any steps to prevent the other side from sending packets of nearly arbitrary size (up to 1G), so while the steady-state memory usage of the prior implementation was less when everyone was playing nicely, it could have certainly been abused. I'm a lot happier having an explicit cap on how much memory will be used, even if that cap is a bit higher. > > Note that there's an issue with exporting the context to get the > > encryption algorithm used that I've asked Robbie to look into, so > > that's no longer done and instead we just print that the connection is > > encrypted, if it is. If we can't figure out a way to make that work > > then obviously I'll pull out that code, and if we can get it to work > > then I'll update it to be done through libpq properly, as I had > > suggested earlier. That's really more of a nice to have in any case > > though, so I may just exclude it for now anyway if it ends up adding > > any complications. > > Correct. Unfortunately I'd overlooked that the lucid interface won't > meet our needs (destroys the context). So the two options here are: > SASL SSF (and I'll separately push more mechs to add support for that), > or nothing at all. I went ahead and ripped out all of that, including the SASL SSF (which really didn't seem like it added all that much... but if others feel like it's useful, then we can add it back in later). That also let me get rid of the configure/configure.in changes, which was nice. I did add in a simple pg_stat_gssapi view, modeled on pg_stat_ssl, so that you can check server-side if GSSAPI was used for authentication and/or encryption, and what principal was used if GSSAPI was used for authentication. I also changed the libpq function to return a boolean to indicate if encryption was used or not; I don't think it's appropriate to have a libpq function that will just print stuff to stdout, that should be the client program's job, so that's handled by psql now. There were some other bits that had been forgotten to get removed from when I moved away from using the string buffers, but I went through and I'm pretty confident that I've cleaned all of that out now. > If you want a patch for that I can make one, but I think there was code > already... just needed a ./configure check program for whether the OID > is defined. > > > +ssize_t > > +be_gssapi_write(Port *port, void *ptr, size_t len) > > +{ > > + size_t bytes_to_encrypt = len; > > + size_t bytes_encrypted = 0; > > + > > + /* > > + * Loop through encrypting data and sending it out until > > + * secure_raw_write() complains (which would likely mean that the socket > > + * is non-blocking and the requested send() would block, or there was some > > + * kind of actual error) and then return. > > + */ > > + while (bytes_to_encrypt || PqGSSSendPointer) > > + { > > I guess it's not a view everyone will share, but I think this block is > too long. Maybe a helper function around secure_raw_write() would help? > (The check-and-send part at the start.) I looked for a way to add a wrapper around secure_raw_write() that would actually be useful here but honestly, it really didn't seem like it'd have done anything but make the code unnecessairly complicated. I mean, there's literally only one call to secure_raw_write() inside of be_gssapi_write().. > I have similar nits about the other functions that don't fit on my > (86-line high) screen, though I guess a lot of this is due to project > style using a lot of vertical space. So, I did think about what you wrote here and we did have multiple calls to be_gssapi_read/write (and similar on the frontend) inside of the secure_open_gssapi() / pqsecure_open_gss() calls, with each call location having to handle the various results from read/write. I went ahead and made wrappers and used them instead, simplifying those functions and reducing the amount of nearly copy/paste code for handling return values from read/write. Those wrappers don't work for the actual secure read/write functions though, since those typically are just able to pass the actual read/write() return back up to the caller, unlike the open calls. > > + if (GSS_ERROR(major)) > > + pg_GSS_error(FATAL, gettext_noop("GSSAPI context error"), > > I'd prefer this to be a different error message than the init/accept > errors - maybe something like "GSSAPI size check error"? > > > + if (GSS_ERROR(major)) > > + pg_GSS_error(libpq_gettext("GSSAPI context error"), conn, > > Same here. Ok, I've fixed those. > Again, these are nits, and I think I'm okay with the changes. Great, thanks again for reviewing! Updated patch attached, if you could take another look through it, that'd be great. Thanks, Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > * Robbie Harwood (rharwood@redhat.com) wrote: >> Stephen Frost <sfrost@snowman.net> writes: >> >> I wanted to note a couple things about this approach. It now uses >> one more buffer than before (in contrast to the previous approach, >> which reused a buffer for received data that was encrypted and >> decrypted). > > Yeah, I don't see that as too much of an issue and it certainly seems > cleaner and simpler to reason about to me, which seems worth the > modest additional buffer cost. That's certainly something we could > change if others feel differently. > >> Since these are static fixed size buffers, this increases the total >> steady-state memory usage by 16k as opposed to re-using the buffer. >> This may be fine; I don't know how tight RAM is here. > > It seems unlikely to be an issue to me- and I would contend that the > prior implementation didn't actually take any steps to prevent the > other side from sending packets of nearly arbitrary size (up to 1G), > so while the steady-state memory usage of the prior implementation was > less when everyone was playing nicely, it could have certainly been > abused. I'm a lot happier having an explicit cap on how much memory > will be used, even if that cap is a bit higher. Yeah, that's definitely a fair point. We could combine the two approaches, but I don't really see a reason to if it's unlikely to be an issue - as you say, this is more readable. It can always be a follow-on if needed. > I did add in a simple pg_stat_gssapi view, modeled on pg_stat_ssl, so > that you can check server-side if GSSAPI was used for authentication > and/or encryption, and what principal was used if GSSAPI was used for > authentication. Good idea. >> Again, these are nits, and I think I'm okay with the changes. > > Great, thanks again for reviewing! > > Updated patch attached, if you could take another look through it, > that'd be great. I'm happy with this! Appreciate you exploring my concerns. Thanks, --Robbie
Attachment
Greetings, * Robbie Harwood (rharwood@redhat.com) wrote: > Stephen Frost <sfrost@snowman.net> writes: > > * Robbie Harwood (rharwood@redhat.com) wrote: > >> Stephen Frost <sfrost@snowman.net> writes: > >> > >> I wanted to note a couple things about this approach. It now uses > >> one more buffer than before (in contrast to the previous approach, > >> which reused a buffer for received data that was encrypted and > >> decrypted). > > > > Yeah, I don't see that as too much of an issue and it certainly seems > > cleaner and simpler to reason about to me, which seems worth the > > modest additional buffer cost. That's certainly something we could > > change if others feel differently. > > > >> Since these are static fixed size buffers, this increases the total > >> steady-state memory usage by 16k as opposed to re-using the buffer. > >> This may be fine; I don't know how tight RAM is here. > > > > It seems unlikely to be an issue to me- and I would contend that the > > prior implementation didn't actually take any steps to prevent the > > other side from sending packets of nearly arbitrary size (up to 1G), > > so while the steady-state memory usage of the prior implementation was > > less when everyone was playing nicely, it could have certainly been > > abused. I'm a lot happier having an explicit cap on how much memory > > will be used, even if that cap is a bit higher. > > Yeah, that's definitely a fair point. We could combine the two > approaches, but I don't really see a reason to if it's unlikely to be an > issue - as you say, this is more readable. It can always be a follow-on > if needed. Agreed, we could do that later if it seems like it would be helpful. > > I did add in a simple pg_stat_gssapi view, modeled on pg_stat_ssl, so > > that you can check server-side if GSSAPI was used for authentication > > and/or encryption, and what principal was used if GSSAPI was used for > > authentication. > > Good idea. Thanks. :) I definitely like being able to see from the backend side of things when a connection is encrypted or not, and being able to see the principal used for authentication is really nice too. > >> Again, these are nits, and I think I'm okay with the changes. > > > > Great, thanks again for reviewing! > > > > Updated patch attached, if you could take another look through it, > > that'd be great. > > I'm happy with this! Appreciate you exploring my concerns. Fantastic, thanks so much for working on this over the years! Unless there's any further comments, I'm going to push this tomorrow. Thanks again! Stephen
Attachment
On 2019-02-23 17:27, Stephen Frost wrote: >> About pg_hba.conf: The "hostgss" keyword seems a bit confusing. It only >> applies to encrypted gss-using connections, not all of them. Maybe >> "hostgssenc" or "hostgsswrap"? > Not quite sure what you mean here, but 'hostgss' seems to be quite well > in-line with what we do for SSL... as in, we have 'hostssl', we don't > say 'hostsslenc'. I feel like I'm just not understanding what you mean > by "not all of them". Reading the latest patch, I think this is still a bit confusing. Consider an entry like hostgss all all 0.0.0.0/0 gss The "hostgss" part means, the connection is GSS-*encrypted*. The "gss" entry in the last column means use gss for *authentication*. But didn't "hostgss" already imply that? No. I understand what's going on, but it seems quite confusing. They both just say "gss"; you have to know a lot about the nuances of pg_hba.conf processing to get that. If you have line like hostgss all all 0.0.0.0/0 md5 it is not obvious that this means, if GSS-encrypted, use md5. It could just as well mean, if GSS-authenticated, use md5. The analogy with SSL is such that we use "hostssl" for connections using SSL encryption and "cert" for the authentication method. So there we use two different words for two different aspects of SSL. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Greetings,
On Tue, Apr 2, 2019 at 18:10 Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
On 2019-02-23 17:27, Stephen Frost wrote:
>> About pg_hba.conf: The "hostgss" keyword seems a bit confusing. It only
>> applies to encrypted gss-using connections, not all of them. Maybe
>> "hostgssenc" or "hostgsswrap"?
> Not quite sure what you mean here, but 'hostgss' seems to be quite well
> in-line with what we do for SSL... as in, we have 'hostssl', we don't
> say 'hostsslenc'. I feel like I'm just not understanding what you mean
> by "not all of them".
Reading the latest patch, I think this is still a bit confusing.
Consider an entry like
hostgss all all 0.0.0.0/0 gss
The "hostgss" part means, the connection is GSS-*encrypted*. The "gss"
entry in the last column means use gss for *authentication*. But didn't
"hostgss" already imply that? No. I understand what's going on, but it
seems quite confusing. They both just say "gss"; you have to know a lot
about the nuances of pg_hba.conf processing to get that.
If you have line like
hostgss all all 0.0.0.0/0 md5
it is not obvious that this means, if GSS-encrypted, use md5. It could
just as well mean, if GSS-authenticated, use md5.
The analogy with SSL is such that we use "hostssl" for connections using
SSL encryption and "cert" for the authentication method. So there we
use two different words for two different aspects of SSL.
I don’t view it as confusing, but I’ll change it to hostgssenc as was suggested earlier to address that concern. It’s a bit wordy but if it helps reduce confusion then that’s a good thing.
Thanks,
Stephen
On 4/2/19 6:18 PM, Stephen Frost wrote: > Greetings, > > On Tue, Apr 2, 2019 at 18:10 Peter Eisentraut > <peter.eisentraut@2ndquadrant.com > <mailto:peter.eisentraut@2ndquadrant.com>> wrote: > > On 2019-02-23 17:27, Stephen Frost wrote: > >> About pg_hba.conf: The "hostgss" keyword seems a bit confusing. > It only > >> applies to encrypted gss-using connections, not all of them. Maybe > >> "hostgssenc" or "hostgsswrap"? > > Not quite sure what you mean here, but 'hostgss' seems to be quite > well > > in-line with what we do for SSL... as in, we have 'hostssl', we don't > > say 'hostsslenc'. I feel like I'm just not understanding what you > mean > > by "not all of them". > > Reading the latest patch, I think this is still a bit confusing. > Consider an entry like > > hostgss all all 0.0.0.0/0 > <http://0.0.0.0/0> gss > > The "hostgss" part means, the connection is GSS-*encrypted*. The "gss" > entry in the last column means use gss for *authentication*. But didn't > "hostgss" already imply that? No. I understand what's going on, but it > seems quite confusing. They both just say "gss"; you have to know a lot > about the nuances of pg_hba.conf processing to get that. > > If you have line like > > hostgss all all 0.0.0.0/0 > <http://0.0.0.0/0> md5 > > it is not obvious that this means, if GSS-encrypted, use md5. It could > just as well mean, if GSS-authenticated, use md5. > > The analogy with SSL is such that we use "hostssl" for connections using > SSL encryption and "cert" for the authentication method. So there we > use two different words for two different aspects of SSL. > > > I don’t view it as confusing, but I’ll change it to hostgssenc as was > suggested earlier to address that concern. It’s a bit wordy but if it > helps reduce confusion then that’s a good thing. Personally I don't find it as confusing as is either, and I find hostgss to be a good analog of hostssl. On the other hand hostgssenc is long and unintuitive. So +1 for leaving as is and -1 one for changing it IMHO. Joe -- Crunchy Data - http://crunchydata.com PostgreSQL Support for Secure Enterprises Consulting, Training, & Open Source Development
On Wed, Apr 3, 2019 at 12:22 AM Joe Conway <mail@joeconway.com> wrote:
On 4/2/19 6:18 PM, Stephen Frost wrote:
> Greetings,
>
> On Tue, Apr 2, 2019 at 18:10 Peter Eisentraut
> <peter.eisentraut@2ndquadrant.com
> <mailto:peter.eisentraut@2ndquadrant.com>> wrote:
>
> On 2019-02-23 17:27, Stephen Frost wrote:
> >> About pg_hba.conf: The "hostgss" keyword seems a bit confusing.
> It only
> >> applies to encrypted gss-using connections, not all of them. Maybe
> >> "hostgssenc" or "hostgsswrap"?
> > Not quite sure what you mean here, but 'hostgss' seems to be quite
> well
> > in-line with what we do for SSL... as in, we have 'hostssl', we don't
> > say 'hostsslenc'. I feel like I'm just not understanding what you
> mean
> > by "not all of them".
>
> Reading the latest patch, I think this is still a bit confusing.
> Consider an entry like
>
> hostgss all all 0.0.0.0/0
> <http://0.0.0.0/0> gss
>
> The "hostgss" part means, the connection is GSS-*encrypted*. The "gss"
> entry in the last column means use gss for *authentication*. But didn't
> "hostgss" already imply that? No. I understand what's going on, but it
> seems quite confusing. They both just say "gss"; you have to know a lot
> about the nuances of pg_hba.conf processing to get that.
>
> If you have line like
>
> hostgss all all 0.0.0.0/0
> <http://0.0.0.0/0> md5
>
> it is not obvious that this means, if GSS-encrypted, use md5. It could
> just as well mean, if GSS-authenticated, use md5.
>
> The analogy with SSL is such that we use "hostssl" for connections using
> SSL encryption and "cert" for the authentication method. So there we
> use two different words for two different aspects of SSL.
>
>
> I don’t view it as confusing, but I’ll change it to hostgssenc as was
> suggested earlier to address that concern. It’s a bit wordy but if it
> helps reduce confusion then that’s a good thing.
Personally I don't find it as confusing as is either, and I find hostgss
to be a good analog of hostssl. On the other hand hostgssenc is long and
unintuitive. So +1 for leaving as is and -1 one for changing it IMHO.
I think for those who are well versed in pg_hba (and maybe gss as well), it's not confusing. That includes me.
However, for a new user, I can definitely see how it can be considered confusing. And confusion in *security configuration* is always a bad idea, even if it's just potential.
Thus +1 on changing it.
If it was on the table it might have been better to keep hostgss and change the authentication method to gssauth or something, but that ship sailed *years* ago.
Greetings, * Magnus Hagander (magnus@hagander.net) wrote: > On Wed, Apr 3, 2019 at 12:22 AM Joe Conway <mail@joeconway.com> wrote: > > On 4/2/19 6:18 PM, Stephen Frost wrote: > > > On Tue, Apr 2, 2019 at 18:10 Peter Eisentraut > > > <peter.eisentraut@2ndquadrant.com > > > <mailto:peter.eisentraut@2ndquadrant.com>> wrote: > > > > > > On 2019-02-23 17:27, Stephen Frost wrote: > > > >> About pg_hba.conf: The "hostgss" keyword seems a bit confusing. > > > It only > > > >> applies to encrypted gss-using connections, not all of them. > > Maybe > > > >> "hostgssenc" or "hostgsswrap"? > > > > Not quite sure what you mean here, but 'hostgss' seems to be quite > > > well > > > > in-line with what we do for SSL... as in, we have 'hostssl', we > > don't > > > > say 'hostsslenc'. I feel like I'm just not understanding what you > > > mean > > > > by "not all of them". > > > > > > Reading the latest patch, I think this is still a bit confusing. > > > Consider an entry like > > > > > > hostgss all all 0.0.0.0/0 > > > <http://0.0.0.0/0> gss > > > > > > The "hostgss" part means, the connection is GSS-*encrypted*. The > > "gss" > > > entry in the last column means use gss for *authentication*. But > > didn't > > > "hostgss" already imply that? No. I understand what's going on, > > but it > > > seems quite confusing. They both just say "gss"; you have to know a > > lot > > > about the nuances of pg_hba.conf processing to get that. > > > > > > If you have line like > > > > > > hostgss all all 0.0.0.0/0 > > > <http://0.0.0.0/0> md5 > > > > > > it is not obvious that this means, if GSS-encrypted, use md5. It > > could > > > just as well mean, if GSS-authenticated, use md5. > > > > > > The analogy with SSL is such that we use "hostssl" for connections > > using > > > SSL encryption and "cert" for the authentication method. So there we > > > use two different words for two different aspects of SSL. > > > > > > > > > I don’t view it as confusing, but I’ll change it to hostgssenc as was > > > suggested earlier to address that concern. It’s a bit wordy but if it > > > helps reduce confusion then that’s a good thing. > > > > Personally I don't find it as confusing as is either, and I find hostgss > > to be a good analog of hostssl. On the other hand hostgssenc is long and > > unintuitive. So +1 for leaving as is and -1 one for changing it IMHO. > > I think for those who are well versed in pg_hba (and maybe gss as well), > it's not confusing. That includes me. > > However, for a new user, I can definitely see how it can be considered > confusing. And confusion in *security configuration* is always a bad idea, > even if it's just potential. > > Thus +1 on changing it. Alright, I've made that change, and also changed "gssmode" to be "gssencmode" to be both consistent and also clearer (that, imv anyway, is actually a much better reason to go to using 'gssenc' instead of just 'gss' for this, since "gssmode" could be thought of as being related to GSS authentication rather than being for GSS encryption). > If it was on the table it might have been better to keep hostgss and change > the authentication method to gssauth or something, but that ship sailed > *years* ago. Agreed, we certainly can't change that now. Updated patch attached with the host[no]gss -> host[no]gssenc and gssmode -> gssencmode changes, along with some other minor improvements. I'll push this in a few hours unless there's anything else. Thanks! Stephen
Attachment
Hi, On 2019-04-03 10:43:33 -0400, Stephen Frost wrote: > I'll push this in a few hours unless there's anything else. The CF entry for this is still open - is there any work missing? Just trying to do some triage... https://commitfest.postgresql.org/22/1647/ - Andres
Greetings,
On Wed, Apr 3, 2019 at 16:01 Andres Freund <andres@anarazel.de> wrote:
Hi,
On 2019-04-03 10:43:33 -0400, Stephen Frost wrote:
> I'll push this in a few hours unless there's anything else.
The CF entry for this is still open - is there any work missing? Just
trying to do some triage...
https://commitfest.postgresql.org/22/1647/
No, I was just waiting to make sure the buildfarm was happy, which it seems to be. I can take care of the entry in 30m or so, or if you’d like to close it out, that would be fine too.
Thanks!
Stephen
Stephen Frost <sfrost@snowman.net> writes: > On Wed, Apr 3, 2019 at 16:01 Andres Freund <andres@anarazel.de> wrote: >> On 2019-04-03 10:43:33 -0400, Stephen Frost wrote: >> >>> I'll push this in a few hours unless there's anything else. >> >> The CF entry for this is still open - is there any work missing? Just >> trying to do some triage... >> >> https://commitfest.postgresql.org/22/1647/ > > No, I was just waiting to make sure the buildfarm was happy, which it > seems to be. I can take care of the entry in 30m or so, or if you’d > like to close it out, that would be fine too. Thanks for merging! I'll stick around the mailing list/IRC for a while on the off-chance that anything comes up, but the project should feel free to reach out to me directly with Kerberos-related issues in the future. Thanks, --Robbie
Attachment
Greetings Robbie,
On Wed, Apr 3, 2019 at 17:47 Robbie Harwood <rharwood@redhat.com> wrote:
Stephen Frost <sfrost@snowman.net> writes:
> On Wed, Apr 3, 2019 at 16:01 Andres Freund <andres@anarazel.de> wrote:
>> On 2019-04-03 10:43:33 -0400, Stephen Frost wrote:
>>
>>> I'll push this in a few hours unless there's anything else.
>>
>> The CF entry for this is still open - is there any work missing? Just
>> trying to do some triage...
>>
>> https://commitfest.postgresql.org/22/1647/
>
> No, I was just waiting to make sure the buildfarm was happy, which it
> seems to be. I can take care of the entry in 30m or so, or if you’d
> like to close it out, that would be fine too.
Thanks for merging! I'll stick around the mailing list/IRC for a while
on the off-chance that anything comes up, but the project should feel
free to reach out to me directly with Kerberos-related issues in the
future.
Thanks so much for pushing on it for so long, it’s a great feature to have!
I would love to see Kerberos/GSSAPI grow a way to find out what the encryption used on the connection is, as we had discussed before... Do we know if that encryption must match the encryption type of the tickets acquired? Would it be possible to inspect the ticket in the same way that klist does, to determine the encryption that must be used? And similarly the key tab on the server side? Though of course there can be more than one but maybe we can find out which was used?
Just some thoughts for future improvements here.
Thanks!
Stephen
On Wed, Apr 03, 2019 at 05:51:06PM -0400, Stephen Frost wrote: > Thanks so much for pushing on it for so long, it’s a great feature to have! Glad to see that the final result is using an API layer in be-secure.c and that we have tests. Now, shouldn't there be some documentation in protocol.sgml for the read and write handling of the encrypted messages? Other drivers could need to implement that stuff, no? We have that for SSL, with SSLRequest and such. -- Michael
Attachment
Greetings,
On Wed, Apr 3, 2019 at 22:02 Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Apr 03, 2019 at 05:51:06PM -0400, Stephen Frost wrote:
> Thanks so much for pushing on it for so long, it’s a great feature to have!
Glad to see that the final result is using an API layer in
be-secure.c and that we have tests. Now, shouldn't there be some
documentation in protocol.sgml for the read and write handling of the
encrypted messages? Other drivers could need to implement that stuff,
no? We have that for SSL, with SSLRequest and such.
Yes, that’s a fair point. I’ll work on adding documentation to protocol.sgml for the GSSAPI encrypted setup and message passing.
Thanks,
Stephen
On Wed, Apr 03, 2019 at 10:09:54PM -0400, Stephen Frost wrote: > Yes, that’s a fair point. I’ll work on adding documentation to > protocol.sgml for the GSSAPI encrypted setup and message passing. Thanks. I have added an open item to track. -- Michael
Attachment
Kerberos tests are now failing for me (macOS). I'm seeing psql: error: could not connect to server: Over-size error packet sent by the server. not ok 3 - GSS encryption without auth # Failed test 'GSS encryption without auth' # at t/002_enc.pl line 170. # got: '2' # expected: '0' (and repeated for several other tests). -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Greetings,
On Thu, Apr 4, 2019 at 05:20 Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
Kerberos tests are now failing for me (macOS). I'm seeing
psql: error: could not connect to server: Over-size error packet sent by
the server.
not ok 3 - GSS encryption without auth
# Failed test 'GSS encryption without auth'
# at t/002_enc.pl line 170.
# got: '2'
# expected: '0'
(and repeated for several other tests).
Interesting, they work locally for me on Ubuntu. Unfortunately, I don’t have macOS. This only happens when encryption is being used, presumably? GSS authentication is still working fine?
Thanks,
Stephen
Stephen Frost <sfrost@snowman.net> writes: > On Thu, Apr 4, 2019 at 05:20 Peter Eisentraut < > peter.eisentraut@2ndquadrant.com> wrote: >> Kerberos tests are now failing for me (macOS). > Interesting, they work locally for me on Ubuntu. Unfortunately, I don’t > have macOS. This only happens when encryption is being used, presumably? > GSS authentication is still working fine? The kerberos test suite passes for me on RHEL6 (kerberos 1.10.3), but I observe some compiler warnings that need to be dealt with: $ ./configure --with-gssapi ... $ time make -j8 -s be-secure-gssapi.c:597: warning: no previous prototype for 'be_gssapi_get_auth' be-secure-gssapi.c:609: warning: no previous prototype for 'be_gssapi_get_enc' be-secure-gssapi.c:621: warning: no previous prototype for 'be_gssapi_get_princ' pgstat.c: In function 'pgstat_bestart': pgstat.c:2986: warning: implicit declaration of function 'be_gssapi_get_auth' pgstat.c:2987: warning: implicit declaration of function 'be_gssapi_get_enc' pgstat.c:2990: warning: implicit declaration of function 'be_gssapi_get_princ' pgstat.c:2990: warning: passing argument 2 of 'strlcpy' makes pointer from integer without a cast ../../../src/include/port.h:429: note: expected 'const char *' but argument is of type 'int' All of PostgreSQL successfully made. Ready to install. I'm not very sure why the integer/pointer confusion in pgstat_bestart doesn't cause hard crashes when using gss auth --- or does this suite not actually test that? regards, tom lane
Greetings, * Tom Lane (tgl@sss.pgh.pa.us) wrote: > Stephen Frost <sfrost@snowman.net> writes: > > On Thu, Apr 4, 2019 at 05:20 Peter Eisentraut < > > peter.eisentraut@2ndquadrant.com> wrote: > >> Kerberos tests are now failing for me (macOS). > > > Interesting, they work locally for me on Ubuntu. Unfortunately, I don’t > > have macOS. This only happens when encryption is being used, presumably? > > GSS authentication is still working fine? > > The kerberos test suite passes for me on RHEL6 (kerberos 1.10.3), > but I observe some compiler warnings that need to be dealt with: Interesting, I don't see those with my build. I'll have to figure out why not. Will fix them in any case. > $ ./configure --with-gssapi ... > $ time make -j8 -s > be-secure-gssapi.c:597: warning: no previous prototype for 'be_gssapi_get_auth' > be-secure-gssapi.c:609: warning: no previous prototype for 'be_gssapi_get_enc' > be-secure-gssapi.c:621: warning: no previous prototype for 'be_gssapi_get_princ' > pgstat.c: In function 'pgstat_bestart': > pgstat.c:2986: warning: implicit declaration of function 'be_gssapi_get_auth' > pgstat.c:2987: warning: implicit declaration of function 'be_gssapi_get_enc' > pgstat.c:2990: warning: implicit declaration of function 'be_gssapi_get_princ' > pgstat.c:2990: warning: passing argument 2 of 'strlcpy' makes pointer from integer without a cast > ../../../src/include/port.h:429: note: expected 'const char *' but argument is of type 'int' > All of PostgreSQL successfully made. Ready to install. > > I'm not very sure why the integer/pointer confusion in pgstat_bestart > doesn't cause hard crashes when using gss auth --- or does > this suite not actually test that? Isn't it just saying that because of the implicit declaration..? Once that's fixed, the integer/pointer warning will go away, but it's actually a pointer in either case, hence why it isn't crashing. The test suite does test GSS authentication and GSS encryption. Thanks, Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > * Tom Lane (tgl@sss.pgh.pa.us) wrote: >> I'm not very sure why the integer/pointer confusion in pgstat_bestart >> doesn't cause hard crashes when using gss auth --- or does >> this suite not actually test that? > Isn't it just saying that because of the implicit declaration..? > Once that's fixed, the integer/pointer warning will go away, but > it's actually a pointer in either case, hence why it isn't crashing. Well, if the caller thinks what is being passed back is an int, it will do a 32-to-64-bit widening, which is almost certainly going to result in a corrupted pointer. > The test suite does test GSS authentication and GSS encryption. Hm. I'll poke at this more closely. regards, tom lane
Greetings, * Tom Lane (tgl@sss.pgh.pa.us) wrote: > Stephen Frost <sfrost@snowman.net> writes: > > * Tom Lane (tgl@sss.pgh.pa.us) wrote: > >> I'm not very sure why the integer/pointer confusion in pgstat_bestart > >> doesn't cause hard crashes when using gss auth --- or does > >> this suite not actually test that? > > > Isn't it just saying that because of the implicit declaration..? > > Once that's fixed, the integer/pointer warning will go away, but > > it's actually a pointer in either case, hence why it isn't crashing. > > Well, if the caller thinks what is being passed back is an int, > it will do a 32-to-64-bit widening, which is almost certainly > going to result in a corrupted pointer. Oh, good point. Interesting that it still works then. I've got a fix for the missing prototypes, I hadn't noticed the issue previously due to always building with SSL enabled as well. I'm testing with a non-SSL build and will push the fix shortly. Thanks! Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > * Tom Lane (tgl@sss.pgh.pa.us) wrote: >> Well, if the caller thinks what is being passed back is an int, >> it will do a 32-to-64-bit widening, which is almost certainly >> going to result in a corrupted pointer. > Oh, good point. Interesting that it still works then. There must be something about the x86_64 ABI that allows this to accidentally work -- maybe integers are presumed to be sign-extended to 64 bits by callee not caller? I added some logging and verified that pgstat.c is seeing the correct string value, so it's working somehow. > I've got a fix for the missing prototypes, I hadn't noticed the issue > previously due to always building with SSL enabled as well. Yeah, I'd just come to the conclusion that it's because I didn't include --with-openssl, and libpq-be.h's #ifdef nest doesn't expect that. BTW, the kerberos test suite takes nearly 4 minutes for me, is it supposed to be so slow? regards, tom lane
On 2019-04-04 17:16, Tom Lane wrote: > BTW, the kerberos test suite takes nearly 4 minutes for me, is > it supposed to be so slow? I've seen this on some virtualized machines that didn't have a lot of entropy. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Greetings, * Tom Lane (tgl@sss.pgh.pa.us) wrote: > Stephen Frost <sfrost@snowman.net> writes: > > * Tom Lane (tgl@sss.pgh.pa.us) wrote: > >> Well, if the caller thinks what is being passed back is an int, > >> it will do a 32-to-64-bit widening, which is almost certainly > >> going to result in a corrupted pointer. > > > Oh, good point. Interesting that it still works then. > > There must be something about the x86_64 ABI that allows this to > accidentally work -- maybe integers are presumed to be sign-extended > to 64 bits by callee not caller? I added some logging and verified > that pgstat.c is seeing the correct string value, so it's working > somehow. Huh, I'm not sure. That's certainly interesting though. > > I've got a fix for the missing prototypes, I hadn't noticed the issue > > previously due to always building with SSL enabled as well. > > Yeah, I'd just come to the conclusion that it's because I didn't > include --with-openssl, and libpq-be.h's #ifdef nest doesn't expect > that. Right, that should be fixed now with the commit I just pushed. > BTW, the kerberos test suite takes nearly 4 minutes for me, is > it supposed to be so slow? Unfortunately, the kerberos test suite requires building a KDC to get tickets from and that takes a bit of time. On my laptop it takes about 8s: make -s check 4.67s user 0.85s system 70% cpu 7.819 total So I'm a bit surprised that it's taking 4 minutes for you. I wonder if there might be an issue related to the KDC wanting to get some amount of random data and the system you're on isn't producing random bytes very fast..? Thanks! Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > * Tom Lane (tgl@sss.pgh.pa.us) wrote: >> There must be something about the x86_64 ABI that allows this to >> accidentally work -- maybe integers are presumed to be sign-extended >> to 64 bits by callee not caller? I added some logging and verified >> that pgstat.c is seeing the correct string value, so it's working >> somehow. > Huh, I'm not sure. That's certainly interesting though. Oh, no, it's simpler than that: the pointer values that be_gssapi_get_princ() is returning just happen to be less than 2^31 on my system. I'd dismissed that as being unlikely, but it's the truth. > So I'm a bit surprised that it's taking 4 minutes for you. I wonder if > there might be an issue related to the KDC wanting to get some amount of > random data and the system you're on isn't producing random bytes very > fast..? Not sure. This is my usual development box and it also does mail, DNS, etc for my household, so I'd expect it to have plenty of entropy. But it's running a pretty old kernel, and old Kerberos too, so maybe the explanation is in there somewhere. regards, tom lane
Greetings, * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: > Kerberos tests are now failing for me (macOS). I'm seeing > > psql: error: could not connect to server: Over-size error packet sent by > the server. > not ok 3 - GSS encryption without auth > > # Failed test 'GSS encryption without auth' > # at t/002_enc.pl line 170. > # got: '2' > # expected: '0' > > (and repeated for several other tests). Ok, it looks like there's a server-side error happening here, and it would be good to see what that is, so can you send the server logs? Thanks! Stephen
Attachment
I wrote: > Stephen Frost <sfrost@snowman.net> writes: >> So I'm a bit surprised that it's taking 4 minutes for you. I wonder if >> there might be an issue related to the KDC wanting to get some amount of >> random data and the system you're on isn't producing random bytes very >> fast..? > Not sure. This is my usual development box and it also does mail, DNS, > etc for my household, so I'd expect it to have plenty of entropy. > But it's running a pretty old kernel, and old Kerberos too, so maybe > the explanation is in there somewhere. Same test on a laptop running Fedora 28 takes a shade under 5 seconds. The laptop has a somewhat better geekbench rating than my workstation, but certainly not 50x better. And I really doubt it's got more entropy sources than the workstation. Gotta be something about the kernel. Watching the test logs, I see that essentially all the time on the RHEL6 machine is consumed by the two # Running: /usr/sbin/kdb5_util create -s -P secret0 steps. Is there a case for merging the two scripts so we only have to do that once? Maybe not, if nobody else sees this. regards, tom lane
Greetings, * Tom Lane (tgl@sss.pgh.pa.us) wrote: > I wrote: > > Stephen Frost <sfrost@snowman.net> writes: > >> So I'm a bit surprised that it's taking 4 minutes for you. I wonder if > >> there might be an issue related to the KDC wanting to get some amount of > >> random data and the system you're on isn't producing random bytes very > >> fast..? > > > Not sure. This is my usual development box and it also does mail, DNS, > > etc for my household, so I'd expect it to have plenty of entropy. > > But it's running a pretty old kernel, and old Kerberos too, so maybe > > the explanation is in there somewhere. > > Same test on a laptop running Fedora 28 takes a shade under 5 seconds. > The laptop has a somewhat better geekbench rating than my workstation, > but certainly not 50x better. And I really doubt it's got more entropy > sources than the workstation. Gotta be something about the kernel. > > Watching the test logs, I see that essentially all the time on the RHEL6 > machine is consumed by the two > > # Running: /usr/sbin/kdb5_util create -s -P secret0 > > steps. Is there a case for merging the two scripts so we only have to > do that once? Maybe not, if nobody else sees this. I do think that mergeing them would be a good idea and I can look into that, though at least locally that step takes less than a second.. I wonder if you might strace (or whatever is appropriate) that kdb5_util and see what's taking so long. I seriously doubt it's the actual kdb5_util code and strongly suspect it's some kernel call. Thanks! Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > * Tom Lane (tgl@sss.pgh.pa.us) wrote: >> Watching the test logs, I see that essentially all the time on the RHEL6 >> machine is consumed by the two >> # Running: /usr/sbin/kdb5_util create -s -P secret0 >> steps. Is there a case for merging the two scripts so we only have to >> do that once? Maybe not, if nobody else sees this. > I do think that mergeing them would be a good idea and I can look into > that, though at least locally that step takes less than a second.. I > wonder if you might strace (or whatever is appropriate) that kdb5_util > and see what's taking so long. I seriously doubt it's the actual > kdb5_util code and strongly suspect it's some kernel call. "strace -r" pins the blame pretty firmly on /dev/random: 0.000076 open("/dev/random", O_RDONLY) = 3 0.000227 fcntl(3, F_SETFD, FD_CLOEXEC) = 0 0.000061 fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 8), ...}) = 0 0.000068 read(3, "\336&\301\310V\344q\217\264-\262\320w-", 64) = 14 0.000091 read(3, "\326\353I\371$\361", 50) = 6 15.328306 read(3, "\214\301\313]I\325", 44) = 6 17.418929 read(3, "z\251\37\275\365\24", 38) = 6 13.366997 read(3, "6\257I\315f\3", 32) = 6 11.457994 read(3, "\370\275\2765\31(", 26) = 6 23.472194 read(3, "\226\r\314\373\2014", 20) = 6 11.746848 read(3, "\335\336BR\30\322", 14) = 6 20.823940 read(3, "\366\214\r\211\0267", 8) = 6 14.429214 read(3, ",g", 2) = 2 15.494835 close(3) = 0 There's no other part of the trace that takes more than ~ 0.1s. So this boils down to the old bugaboo about how much entropy there really is in /dev/random. regards, tom lane
Tom Lane <tgl@sss.pgh.pa.us> writes: > Stephen Frost <sfrost@snowman.net> writes: >> * Tom Lane (tgl@sss.pgh.pa.us) wrote: >>> Well, if the caller thinks what is being passed back is an int, >>> it will do a 32-to-64-bit widening, which is almost certainly >>> going to result in a corrupted pointer. > >> Oh, good point. Interesting that it still works then. > > There must be something about the x86_64 ABI that allows this to > accidentally work -- maybe integers are presumed to be sign-extended > to 64 bits by callee not caller? I added some logging and verified > that pgstat.c is seeing the correct string value, so it's working > somehow. > >> I've got a fix for the missing prototypes, I hadn't noticed the issue >> previously due to always building with SSL enabled as well. > > Yeah, I'd just come to the conclusion that it's because I didn't > include --with-openssl, and libpq-be.h's #ifdef nest doesn't expect > that. > > BTW, the kerberos test suite takes nearly 4 minutes for me, is > it supposed to be so slow? My guess is entropy problems as well. If available, configuring /dev/urandom passthrough from the host is a generally helpful thing to do. My (Fedora, Centos/RHEL 7+) krb5 builds use getrandom() for entropy, so they shouldn't be slow; I believe Debian also has started doing so recently as well. I don't know what other distros/OSs do for this. Thanks, --Robbie
Attachment
Tom Lane <tgl@sss.pgh.pa.us> writes: > I wrote: >> Stephen Frost <sfrost@snowman.net> writes: >>> So I'm a bit surprised that it's taking 4 minutes for you. I wonder if >>> there might be an issue related to the KDC wanting to get some amount of >>> random data and the system you're on isn't producing random bytes very >>> fast..? > >> Not sure. This is my usual development box and it also does mail, DNS, >> etc for my household, so I'd expect it to have plenty of entropy. >> But it's running a pretty old kernel, and old Kerberos too, so maybe >> the explanation is in there somewhere. > > Same test on a laptop running Fedora 28 takes a shade under 5 seconds. > The laptop has a somewhat better geekbench rating than my workstation, > but certainly not 50x better. And I really doubt it's got more entropy > sources than the workstation. Gotta be something about the kernel. > > Watching the test logs, I see that essentially all the time on the RHEL6 > machine is consumed by the two > > # Running: /usr/sbin/kdb5_util create -s -P secret0 > > steps. Is there a case for merging the two scripts so we only have to > do that once? Maybe not, if nobody else sees this. I think that would be a good idea! Unfortunately I don't speak perl well enough to do that, so I'd just copied-and-modified. Thanks, --Robbie
Attachment
On 2019-04-04 17:35, Stephen Frost wrote: > Ok, it looks like there's a server-side error happening here, and it > would be good to see what that is, so can you send the server logs? These errors appear several times in the server logs: FATAL: GSSAPI context error DETAIL: Miscellaneous failure (see text): Decrypt integrity check failed for checksum type hmac-sha1-96-aes256, key type aes256-cts-hmac-sha1-96 FATAL: accepting GSS security context failed DETAIL: Miscellaneous failure (see text): Decrypt integrity check failed for checksum type hmac-sha1-96-aes256, key type aes256-cts-hmac-sha1-96 -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Greetings, * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: > Kerberos tests are now failing for me (macOS). I'm seeing > > psql: error: could not connect to server: Over-size error packet sent by > the server. > not ok 3 - GSS encryption without auth > > # Failed test 'GSS encryption without auth' > # at t/002_enc.pl line 170. > # got: '2' > # expected: '0' > > (and repeated for several other tests). Alright, that over-size error was a bug in the error-handling code, which I've just pushed a fix for. That said... * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: > On 2019-04-04 17:35, Stephen Frost wrote: > > Ok, it looks like there's a server-side error happening here, and it > > would be good to see what that is, so can you send the server logs? > > These errors appear several times in the server logs: > > FATAL: GSSAPI context error > DETAIL: Miscellaneous failure (see text): Decrypt integrity check > failed for checksum type hmac-sha1-96-aes256, key type > aes256-cts-hmac-sha1-96 > > FATAL: accepting GSS security context failed > DETAIL: Miscellaneous failure (see text): Decrypt integrity check > failed for checksum type hmac-sha1-96-aes256, key type > aes256-cts-hmac-sha1-96 This looks like it's a real issue and it's unclear what's going on here. I wonder- are you certain that you're using all the same Kerberos libraries for the KDC, the server, and psql? If you go back to before the GSSAPI encryption patch, does it work..? I've certainly seen interesting issues on MacOS, in particular, due to different Kerberos libraries/tools being installed and I wonder if that's what is going on here. Maybe you could check klist vs. psql wrt what libraries are linked in? Thanks, Stephen
Attachment
On 2019-04-05 04:59, Stephen Frost wrote: > Alright, that over-size error was a bug in the error-handling code, > which I've just pushed a fix for. That said... Yes, that looks better now. > This looks like it's a real issue and it's unclear what's going on here. > I wonder- are you certain that you're using all the same Kerberos > libraries for the KDC, the server, and psql? Right, it was built against the OS-provided Kerberos installation (/usr/bin etc.). If I build against the Homebrew-provided one then the tests pass. So maybe that means that this encryption feature is not supported on that (presumably older) installation? (krb5-config --version says "Kerberos 5 release 1.7-prerelease") Is that plausible? Is a gentler failure mode possible? -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Greetings, * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: > On 2019-04-05 04:59, Stephen Frost wrote: > > Alright, that over-size error was a bug in the error-handling code, > > which I've just pushed a fix for. That said... > > Yes, that looks better now. Great. > > This looks like it's a real issue and it's unclear what's going on here. > > I wonder- are you certain that you're using all the same Kerberos > > libraries for the KDC, the server, and psql? > > Right, it was built against the OS-provided Kerberos installation > (/usr/bin etc.). If I build against the Homebrew-provided one then the > tests pass. All of it was built against the OS-provided Kerberos install, and you got the failure..? > So maybe that means that this encryption feature is not supported on > that (presumably older) installation? (krb5-config --version says > "Kerberos 5 release 1.7-prerelease") Is that plausible? Is a gentler > failure mode possible? On a failure to set up an encrypted connection, we'll actually fall back to a non-encrypted one, using GSSAPI *just* for authentication, which is why I was asking if this worked before the encryption patch went in. Also, which of the tests are still failing, exactly? The authentication ones or the encryption ones or both? If we determine that this is some issue with the MacOS-provided Kerberos libraries, then we could try to detect them and disable GSSAPI encryption in that case explicitly, I suppose, but I've seen odd things with the MacOS-provided Kerberos libraries before on released versions of PG (without any encryption support), so I'm not yet convinced that this is an issue that's specific to adding support for encryption. Thanks! Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: >> On 2019-04-05 04:59, Stephen Frost wrote: >> >>> Alright, that over-size error was a bug in the error-handling code, >>> which I've just pushed a fix for. That said... >> >> Yes, that looks better now. > > Great. > >>> This looks like it's a real issue and it's unclear what's going on >>> here. I wonder- are you certain that you're using all the same >>> Kerberos libraries for the KDC, the server, and psql? >> >> Right, it was built against the OS-provided Kerberos installation >> (/usr/bin etc.). If I build against the Homebrew-provided one then >> the tests pass. > > All of it was built against the OS-provided Kerberos install, and you > got the failure..? > >> So maybe that means that this encryption feature is not supported on >> that (presumably older) installation? (krb5-config --version says >> "Kerberos 5 release 1.7-prerelease") Is that plausible? Is a gentler >> failure mode possible? Heimdal never had a 1.7 release - they went from 1.5.2 to 7.1.0. MIT did have a 1.7 release - in 2009. Apple doesn't open source their Kerberos implementation, so I can't exactly point a debugger at it. But if it's in fact somehow related to MIT 1.7-prerelease, I imagine they inherited a bug or two that's been fixed in the ten years since then. As for the code: I'm not doing anything complicated. The interface I'm using is as specified in RFC2743 and RFC2744, which is from 2000 (though I think technically I'm mostly backward compatible to RFC1509, from 1993), and Kerberos V5 itself is specified in RFC4120 (from 2005). > On a failure to set up an encrypted connection, we'll actually fall > back to a non-encrypted one, using GSSAPI *just* for authentication, > which is why I was asking if this worked before the encryption patch > went in. Also, which of the tests are still failing, exactly? The > authentication ones or the encryption ones or both? Good question. > If we determine that this is some issue with the MacOS-provided > Kerberos libraries, then we could try to detect them and disable > GSSAPI encryption in that case explicitly, I suppose, but I've seen > odd things with the MacOS-provided Kerberos libraries before on > released versions of PG (without any encryption support), so I'm not > yet convinced that this is an issue that's specific to adding support > for encryption. If we have to, a version check >1.7 would probably work. That'll remove the ability to work on RHEL/CentOS 5, but that's probably fine, and I'm not aware of any other supported OSs that would be impacted. Thanks, --Robbie
Attachment
On 2019-04-05 14:48, Stephen Frost wrote: > All of it was built against the OS-provided Kerberos install, and you > got the failure..? right > On a failure to set up an encrypted connection, we'll actually fall back > to a non-encrypted one using GSSAPI *just* for authentication, which is> why I was asking if this worked before the encryptionpatch went in. The tests have always worked before. I've run them probably hundreds of times. > Also, which of the tests are still failing, exactly? The authentication > ones or the encryption ones or both? Only the encryption ones: not ok 1 - GSS-encrypted access # Failed test 'GSS-encrypted access' # at t/002_enc.pl line 170. # got: '2' # expected: '0' ok 2 - GSS encryption disabled not ok 3 - GSS encryption without auth # Failed test 'GSS encryption without auth' # at t/002_enc.pl line 170. # got: '2' # expected: '0' not ok 4 - GSS unencrypted fallback # Failed test 'GSS unencrypted fallback' # at t/002_enc.pl line 170. # got: '2' # expected: '0' ok 5 - GSS unencrypted fallback prevention -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Greetings, * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: > On 2019-04-05 14:48, Stephen Frost wrote: > > On a failure to set up an encrypted connection, we'll actually fall back > > to a non-encrypted one using GSSAPI *just* for authentication, which is> why I was asking if this worked before the encryptionpatch went in. > > The tests have always worked before. I've run them probably hundreds of > times. Ok. > > Also, which of the tests are still failing, exactly? The authentication > > ones or the encryption ones or both? > > Only the encryption ones: > > not ok 1 - GSS-encrypted access > > # Failed test 'GSS-encrypted access' > # at t/002_enc.pl line 170. > # got: '2' > # expected: '0' > ok 2 - GSS encryption disabled > not ok 3 - GSS encryption without auth > > # Failed test 'GSS encryption without auth' > # at t/002_enc.pl line 170. > # got: '2' > # expected: '0' > not ok 4 - GSS unencrypted fallback > > # Failed test 'GSS unencrypted fallback' > # at t/002_enc.pl line 170. > # got: '2' > # expected: '0' > ok 5 - GSS unencrypted fallback prevention So, looking back at the error message you got: FATAL: GSSAPI context error DETAIL: Miscellaneous failure (see text): Decrypt integrity check failed for checksum type hmac-sha1-96-aes256, key type aes256-cts-hmac-sha1-96 FATAL: accepting GSS security context failed DETAIL: Miscellaneous failure (see text): Decrypt integrity check failed for checksum type hmac-sha1-96-aes256, key type aes256-cts-hmac-sha1-96 (assuming that's still what you're getting..?) What this is saying is basically that the key that the PG server is using from its keytab and the key used to encrypt the ticket that the user has (from the KDC) don't match up. I wonder if somehow the keytab file that the server is using isn't getting destroyed between the two test runs and so you're ending up with the server using the key from the old KDC, while the user is using the new one..? Or something is equally going wrong in the tests. Could you try doing something like removing the 001_auth.pl, moving the 002_enc.pl to 001_enc.pl, double-check that everything is cleaned up and that there aren't any old PG servers running, et al, and re-try that way? I've also reached out to some colleagues about having one of them test with MacOS. What version are you on..? Thanks! Stephen
Attachment
On 2019-04-05 23:37, Stephen Frost wrote: > I wonder if somehow the keytab file that the server is using isn't > getting destroyed between the two test runs and so you're ending up with > the server using the key from the old KDC, while the user is using the > new one..? Or something is equally going wrong in the tests. > > Could you try doing something like removing the 001_auth.pl, moving the > 002_enc.pl to 001_enc.pl, double-check that everything is cleaned up and > that there aren't any old PG servers running, et al, and re-try that > way? Running just 002_enc.pl by itself passes the tests! > I've also reached out to some colleagues about having one of them test > with MacOS. What version are you on..? macOS 10.14.14 it says. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Greetings, * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: > On 2019-04-05 23:37, Stephen Frost wrote: > > I wonder if somehow the keytab file that the server is using isn't > > getting destroyed between the two test runs and so you're ending up with > > the server using the key from the old KDC, while the user is using the > > new one..? Or something is equally going wrong in the tests. > > > > Could you try doing something like removing the 001_auth.pl, moving the > > 002_enc.pl to 001_enc.pl, double-check that everything is cleaned up and > > that there aren't any old PG servers running, et al, and re-try that > > way? > > Running just 002_enc.pl by itself passes the tests! Great! I think what I'll do is work to incorporate the two tests back into one script, to avoid whatever the race condition or other confusion is happening on macOS here. Thanks so much for testing it! Stephen
Attachment
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes: > On 2019-04-05 23:37, Stephen Frost wrote: >> I've also reached out to some colleagues about having one of them test >> with MacOS. What version are you on..? > macOS 10.14.14 it says. I tried to replicate this on my own laptop (macOS 10.14.4 ... I do not think there is or ever will be a 10.14.14). I can't, because the kerberos test fails immediately: 1..4 # setting up Kerberos # Running: krb5-config --version # Running: kdb5_util create -s -P secret0 Can't exec "kdb5_util": No such file or directory at /Users/tgl/pgsql/src/test/kerberos/../../../src/test/perl/TestLib.pmline 190. Bail out! system kdb5_util failed and indeed, there's no kdb5_util in /usr/bin/ or anywhere else that I can find. So I speculate that Peter is running some weird hodgepodge of Apple and Homebrew code, making the question not so much "why does it fail" as "how did it ever work". I also notice that the build spews out a bunch of deprecation warnings, because just as with openSSL, Apple has stuck deprecation attributes on everything in gssapi/gssapi.h. They want you to use their GSS "framework" instead. So I'm not convinced we should spend a lot of effort on fooling with the test scripts for this. This platform has got much more fundamental problems than that. regards, tom lane
On 2019-04-09 04:51, Stephen Frost wrote: >> Running just 002_enc.pl by itself passes the tests! > Great! I think what I'll do is work to incorporate the two tests back > into one script, to avoid whatever the race condition or other confusion > is happening on macOS here. That seems reasonable. It also avoids the large amount of duplicate setup code. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 2019-04-09 06:11, Tom Lane wrote: > I tried to replicate this on my own laptop (macOS 10.14.4 ... I do not > think there is or ever will be a 10.14.14). right > kerberos test fails immediately: > > 1..4 > # setting up Kerberos > # Running: krb5-config --version > # Running: kdb5_util create -s -P secret0 > Can't exec "kdb5_util": No such file or directory at /Users/tgl/pgsql/src/test/kerberos/../../../src/test/perl/TestLib.pmline 190. > Bail out! system kdb5_util failed > > and indeed, there's no kdb5_util in /usr/bin/ or anywhere else that > I can find. So I speculate that Peter is running some weird hodgepodge > of Apple and Homebrew code, making the question not so much "why does > it fail" as "how did it ever work". Yes, you need a krb5 installation from either Homebrew or MacPorts. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Wed, Apr 3, 2019 at 08:49:25AM +0200, Magnus Hagander wrote: > On Wed, Apr 3, 2019 at 12:22 AM Joe Conway <mail@joeconway.com> wrote: > Personally I don't find it as confusing as is either, and I find hostgss > to be a good analog of hostssl. On the other hand hostgssenc is long and > unintuitive. So +1 for leaving as is and -1 one for changing it IMHO. > > > I think for those who are well versed in pg_hba (and maybe gss as well), it's > not confusing. That includes me. > > However, for a new user, I can definitely see how it can be considered > confusing. And confusion in *security configuration* is always a bad idea, even > if it's just potential. > > Thus +1 on changing it. > > If it was on the table it might have been better to keep hostgss and change the > authentication method to gssauth or something, but that ship sailed *years* > ago. Uh, did we consider keeping hostgss and changing the auth part at the end to "gssauth"? -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + As you are, so once was I. As I am, so you will be. + + Ancient Roman grave inscription +
Bruce Momjian <bruce@momjian.us> writes: > On Wed, Apr 3, 2019 at 08:49:25AM +0200, Magnus Hagander wrote: >> On Wed, Apr 3, 2019 at 12:22 AM Joe Conway <mail@joeconway.com> wrote: >> >> Personally I don't find it as confusing as is either, and I find >> hostgss to be a good analog of hostssl. On the other hand hostgssenc >> is long and unintuitive. So +1 for leaving as is and -1 one for >> changing it IMHO. >> >> I think for those who are well versed in pg_hba (and maybe gss as >> well), it's not confusing. That includes me. >> >> However, for a new user, I can definitely see how it can be >> considered confusing. And confusion in *security configuration* is >> always a bad idea, even if it's just potential. >> >> Thus +1 on changing it. >> >> If it was on the table it might have been better to keep hostgss and >> change the authentication method to gssauth or something, but that >> ship sailed *years* ago. > > Uh, did we consider keeping hostgss and changing the auth part at the > end to "gssauth"? I think that was implicitly rejected because we'd have to keep the capability to configure "gss" there else break compatibility. Thanks, --Robbie
Attachment
Greetings, * Robbie Harwood (rharwood@redhat.com) wrote: > Bruce Momjian <bruce@momjian.us> writes: > > On Wed, Apr 3, 2019 at 08:49:25AM +0200, Magnus Hagander wrote: > >> On Wed, Apr 3, 2019 at 12:22 AM Joe Conway <mail@joeconway.com> wrote: > >> > >> Personally I don't find it as confusing as is either, and I find > >> hostgss to be a good analog of hostssl. On the other hand hostgssenc > >> is long and unintuitive. So +1 for leaving as is and -1 one for > >> changing it IMHO. > >> > >> I think for those who are well versed in pg_hba (and maybe gss as > >> well), it's not confusing. That includes me. > >> > >> However, for a new user, I can definitely see how it can be > >> considered confusing. And confusion in *security configuration* is > >> always a bad idea, even if it's just potential. > >> > >> Thus +1 on changing it. > >> > >> If it was on the table it might have been better to keep hostgss and > >> change the authentication method to gssauth or something, but that > >> ship sailed *years* ago. > > > > Uh, did we consider keeping hostgss and changing the auth part at the > > end to "gssauth"? > > I think that was implicitly rejected because we'd have to keep the > capability to configure "gss" there else break compatibility. Right, if we changed the name of the auth method then everyone who is using the "gss" auth method would have to update their pg_hba.conf files... That would be very ugly. Also, it wasn't implicitly rejected, it was discussed up-thread (see the comments between Magnus and I, specifically, quoted above- "that ship sailed *years* ago") and explicitly rejected. Thanks! Stephen
Attachment
On Wed, Apr 10, 2019 at 9:47 PM Stephen Frost <sfrost@snowman.net> wrote: > Right, if we changed the name of the auth method then everyone who is > using the "gss" auth method would have to update their pg_hba.conf > files... That would be very ugly. Also, it wasn't implicitly rejected, > it was discussed up-thread (see the comments between Magnus and I, > specifically, quoted above- "that ship sailed *years* ago") and > explicitly rejected. Slightly off-topic, but I am not familiar with GSSAPI and don't quite understand what the benefits of GSSAPI encryption are as compared with OpenSSL encryption. I am sure there must be some; otherwise, nobody would have bothered writing, reviewing, and committing this patch. Can somebody enlighten me? -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
On Thu, Apr 11, 2019 at 3:56 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Apr 10, 2019 at 9:47 PM Stephen Frost <sfrost@snowman.net> wrote:
> Right, if we changed the name of the auth method then everyone who is
> using the "gss" auth method would have to update their pg_hba.conf
> files... That would be very ugly. Also, it wasn't implicitly rejected,
> it was discussed up-thread (see the comments between Magnus and I,
> specifically, quoted above- "that ship sailed *years* ago") and
> explicitly rejected.
Slightly off-topic, but I am not familiar with GSSAPI and don't quite
understand what the benefits of GSSAPI encryption are as compared with
OpenSSL encryption. I am sure there must be some; otherwise, nobody
would have bothered writing, reviewing, and committing this patch.
Can somebody enlighten me?
You don't need to set up an SSL PKI.
Yes you need the similar keys and stuff set up for GSSAPI, but if you already *have* those (which you do if you are using gss authentication for example) then it's a lot less extra overhead.
Stephen Frost <sfrost@snowman.net> writes: > Robbie Harwood (rharwood@redhat.com) wrote: >> Bruce Momjian <bruce@momjian.us> writes: >>> Magnus Hagander wrote: >>>> Joe Conway <mail@joeconway.com> wrote: >>>> >>>> If it was on the table it might have been better to keep hostgss >>>> and change the authentication method to gssauth or something, but >>>> that ship sailed *years* ago. >>> >>> Uh, did we consider keeping hostgss and changing the auth part at >>> the end to "gssauth"? >> >> I think that was implicitly rejected because we'd have to keep the >> capability to configure "gss" there else break compatibility. > > Right, if we changed the name of the auth method then everyone who is > using the "gss" auth method would have to update their pg_hba.conf > files... That would be very ugly. Also, it wasn't implicitly > rejected, it was discussed up-thread (see the comments between Magnus > and I, specifically, quoted above- "that ship sailed *years* ago") and > explicitly rejected. Apologies, you're right of course. I intended to say why *I* had rejected it but got bit by the passive voice. Thanks, --Robbie
Attachment
On 2019-04-09 09:32, Peter Eisentraut wrote: > On 2019-04-09 04:51, Stephen Frost wrote: >>> Running just 002_enc.pl by itself passes the tests! >> Great! I think what I'll do is work to incorporate the two tests back >> into one script, to avoid whatever the race condition or other confusion >> is happening on macOS here. > > That seems reasonable. It also avoids the large amount of duplicate > setup code. Another problem is that the two test files cannot be run in parallel because they use the same hardcoded data directories. That would have to be replaced by temporary directories. The race condition alluded to above appears to be simply that at the end of the test the kdc is shut down by kill -INT but nothing waits for that to finish before a new one is started by the next test. So there is potential for all kinds of confusion. Putting it all in one test file seems to be the easiest way out. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Fri, Apr 12, 2019 at 10:22:03AM +0200, Peter Eisentraut wrote: > Another problem is that the two test files cannot be run in parallel > because they use the same hardcoded data directories. That would have > to be replaced by temporary directories. Please note that I have added an open item about the instability of these tests. If ones's PG_TEST_EXTRA uses kerberos, just using PROVE_FLAGS="-j4" or such to run multiple scripts in parallel makes the test failure very easy to reproduce. That's my case, and I had to disable the test for now... -- Michael
Attachment
Greetings, * Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote: > On 2019-04-09 09:32, Peter Eisentraut wrote: > > On 2019-04-09 04:51, Stephen Frost wrote: > >>> Running just 002_enc.pl by itself passes the tests! > >> Great! I think what I'll do is work to incorporate the two tests back > >> into one script, to avoid whatever the race condition or other confusion > >> is happening on macOS here. > > > > That seems reasonable. It also avoids the large amount of duplicate > > setup code. > > Another problem is that the two test files cannot be run in parallel > because they use the same hardcoded data directories. That would have > to be replaced by temporary directories. The tests are really fast enough with one KDC that I don't think it makes sense to have two independent tests. > The race condition alluded to above appears to be simply that at the end > of the test the kdc is shut down by kill -INT but nothing waits for that > to finish before a new one is started by the next test. So there is > potential for all kinds of confusion. Ah, good to know that's what was happening. > Putting it all in one test file seems to be the easiest way out. Please find attached a patch which updates the protocol.sgml docs that Michael mentioned before, and merges the tests into one test file (while adding in some additional tests to make sure that the server also agrees with what our expectations are, using the pg_stat_gssapi view). I'll push this soon unless there are concerns. If you get a chance to test the patch out, that would be great. It's working happily for me locally. Thanks! Stephen
Attachment
Stephen Frost <sfrost@snowman.net> writes: > Please find attached a patch which updates the protocol.sgml docs that > Michael mentioned before, and merges the tests into one test file > (while adding in some additional tests to make sure that the server > also agrees with what our expectations are, using the pg_stat_gssapi > view). I can't speak to the Perl, but the documentation matches what I think the code does :) Thanks, --Robbie
Attachment
On Mon, Apr 15, 2019 at 08:24:52AM -0400, Stephen Frost wrote: > The tests are really fast enough with one KDC that I don't think it > makes sense to have two independent tests. Perhaps you should add a comment about the need of unicity at the top of 001_auth.pl with a short description of the test? > Please find attached a patch which updates the protocol.sgml docs that > Michael mentioned before, and merges the tests into one test file (while > adding in some additional tests to make sure that the server also agrees > with what our expectations are, using the pg_stat_gssapi view). Thanks for addressing all that feedback. Parallel runs look more stable on my side. At least it seems that I can re-enable it safely. > I'll push this soon unless there are concerns. If you get a chance to > test the patch out, that would be great. It's working happily for me > locally. + calling gss_init_sec_context() in a loop and sending the result to the Some markups should be added here for all function names. Not all the clients use C either, so you may want to say "or equivalent"? +test_access($node, 'test1', 'SELECT gss_authenticated AND encrypted from pg_stat_gssapi where pid = pg_backend_pid();', 0, '', 'succeeds with mapping with default gssencmode and host hba'); +test_access($node, "test1", 'SELECT gss_authenticated AND encrypted from pg_stat_gssapi where pid = pg_backend_pid();', 0, "gssencmode=prefer", "succeeds with GSS-encrypted access preferred with host hba"); +test_access($node, "test1", 'SELECT gss_authenticated AND encrypted from pg_stat_gssapi where pid = pg_backend_pid();', 0, "gssencmode=require", "succeeds with GSS-encrypted access required with host hba"); If you could rework a bit the indentation of the new code added in kerberos/t/001_auth.pl that would be nice. I am afraid that the current format makes debugging harder than necessary. +$node->append_conf('pg_hba.conf', + qq{hostgssenc all all $hostaddr/32 gss map=mymap}); +$node->restart; A reload should be enough but not race-condition free, which is why a set of restarts is done in this test right? (I have noticed that it is done this way since the beginning.) -- Michael
Attachment
On 2019-04-16 06:36, Michael Paquier wrote: > +$node->append_conf('pg_hba.conf', > + qq{hostgssenc all all $hostaddr/32 gss map=mymap}); > +$node->restart; > A reload should be enough but not race-condition free, which is why a > set of restarts is done in this test right? (I have noticed that it > is done this way since the beginning.) We got rid of all the reloads in these kinds of tests because they have the effect that if the configuration has an error, the reload just ignores it. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Greetings, * Michael Paquier (michael@paquier.xyz) wrote: > On Mon, Apr 15, 2019 at 08:24:52AM -0400, Stephen Frost wrote: > > The tests are really fast enough with one KDC that I don't think it > > makes sense to have two independent tests. > > Perhaps you should add a comment about the need of unicity at the top > of 001_auth.pl with a short description of the test? I added some comments there that I think explain why it makes sense to have just one test file there. > > Please find attached a patch which updates the protocol.sgml docs that > > Michael mentioned before, and merges the tests into one test file (while > > adding in some additional tests to make sure that the server also agrees > > with what our expectations are, using the pg_stat_gssapi view). > > Thanks for addressing all that feedback. Parallel runs look more > stable on my side. At least it seems that I can re-enable it safely. Great, glad to hear it. > > I'll push this soon unless there are concerns. If you get a chance to > > test the patch out, that would be great. It's working happily for me > > locally. > > + calling gss_init_sec_context() in a loop and sending the result to the > Some markups should be added here for all function names. Not all the > clients use C either, so you may want to say "or equivalent"? I added the markups for function names along with a sentence fragment saying that the functions referenced are the C GSSAPI bindings, and that equivilants can be used. > +test_access($node, 'test1', 'SELECT gss_authenticated AND encrypted > from pg_stat_gssapi where pid = pg_backend_pid();', 0, '', 'succeeds > with mapping with default gssencmode and host hba'); > +test_access($node, "test1", 'SELECT gss_authenticated AND encrypted > from pg_stat_gssapi where pid = pg_backend_pid();', 0, > "gssencmode=prefer", "succeeds with GSS-encrypted access preferred > with host hba"); > +test_access($node, "test1", 'SELECT gss_authenticated AND encrypted > from pg_stat_gssapi where pid = pg_backend_pid();', 0, > "gssencmode=require", "succeeds with GSS-encrypted access required > with host hba"); > If you could rework a bit the indentation of the new code added in > kerberos/t/001_auth.pl that would be nice. I am afraid that the > current format makes debugging harder than necessary. I ran perltidy on it, sorry, should have done that before. > +$node->append_conf('pg_hba.conf', > + qq{hostgssenc all all $hostaddr/32 gss map=mymap}); > +$node->restart; > A reload should be enough but not race-condition free, which is why a > set of restarts is done in this test right? (I have noticed that it > is done this way since the beginning.) Right, we want this to be a restart as Peter mentions downthread. I've now pushed these changes and will mark the open item as addressed. Thanks! Stephen
Attachment
On Fri, Apr 19, 2019 at 09:25:14PM -0400, Stephen Frost wrote: > Great, glad to hear it. What you have committed looks fine seen from here. Thanks for taking care of the issue, Stephen. -- Michael