On Mon, 2019-08-19 at 14:51 +0900, Michael Paquier wrote: > On Fri, Aug 16, 2019 at 02:11:57PM -0400, Jonathan S. Katz wrote: > > To be pedantic, +1 on the channel_binding param. > > Seems like we are moving in this direction then. I don't object to > the introduction of this parameter.
OK, new patch attached. Seems like everyone is in agreement that we need a channel_binding param. Regards, Jeff Davis
From 0eb8e76d08a64979c3070761efab95d61c4ff887 Mon Sep 17 00:00:00 2001 From: Jeff Davis <j...@j-davis.com> Date: Tue, 20 Aug 2019 18:59:23 -0700 Subject: [PATCH] Add libpq parameter 'channel_binding'. Allow clients to require channel binding to enhance security against untrusted servers. Author: Jeff Davis Discussion: https://postgr.es/m/227015d8417f2b4fef03f8966dbfa5cbcc4f44da.camel%40j-davis.com --- doc/src/sgml/libpq.sgml | 19 +++++++++++++++++++ src/interfaces/libpq/fe-auth.c | 30 +++++++++++++++++++++++++++++- src/interfaces/libpq/fe-connect.c | 28 ++++++++++++++++++++++++++++ src/interfaces/libpq/libpq-int.h | 2 ++ 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index e7295abda28..1785cb1bcc5 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1118,6 +1118,25 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname </listitem> </varlistentry> + <varlistentry id="libpq-connect-channel-binding" xreflabel="channel_binding"> + <term><literal>channel_binding</literal></term> + <listitem> + <para> + A setting of <literal>require</literal> means that the connection must + employ channel binding; and that the client will not respond to + requests from the server for cleartext password or MD5 + authentication. The default setting is <literal>prefer</literal>, + which employs channel binding if available. + </para> + <para> + Channel binding is a method for the server to authenticate itself to + the client. It is only supported over SSL connections + with <productname>PostgreSQL</productname> 11.0 or later servers using + the <literal>scram-sha-256</literal> authentication method. + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-connect-connect-timeout" xreflabel="connect_timeout"> <term><literal>connect_timeout</literal></term> <listitem> diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index ab227421b3b..5df30913337 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -423,6 +423,13 @@ pg_SASL_init(PGconn *conn, int payloadlen) initPQExpBuffer(&mechanism_buf); + if (conn->channel_binding[0] == 'r' && !conn->ssl_in_use) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Channel binding required, but SSL not in use\n")); + goto error; + } + if (conn->sasl_state) { printfPQExpBuffer(&conn->errorMessage, @@ -488,7 +495,8 @@ pg_SASL_init(PGconn *conn, int payloadlen) goto error; } } - else if (strcmp(mechanism_buf.data, SCRAM_SHA_256_NAME) == 0 && + else if (conn->channel_binding[0] != 'r' && + strcmp(mechanism_buf.data, SCRAM_SHA_256_NAME) == 0 && !selected_mechanism) selected_mechanism = SCRAM_SHA_256_NAME; } @@ -565,6 +573,9 @@ pg_SASL_init(PGconn *conn, int payloadlen) if (initialresponse) free(initialresponse); + if (strcmp(selected_mechanism, SCRAM_SHA_256_PLUS_NAME) == 0) + conn->channel_bound = true; + return STATUS_OK; error: @@ -791,6 +802,12 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn) switch (areq) { case AUTH_REQ_OK: + if (conn->channel_binding[0] == 'r' && !conn->channel_bound) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Channel binding required but not offered by server\n")); + return STATUS_ERROR; + } break; case AUTH_REQ_KRB4: @@ -919,6 +936,17 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn) { char *password; + if (conn->channel_binding[0] == 'r') + { + if (areq == AUTH_REQ_PASSWORD) + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Channel binding required but server requested cleartext password authentication\n")); + else + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("Channel binding required but server requested MD5 authentication\n")); + return STATUS_ERROR; + } + conn->password_needed = true; password = conn->connhost[conn->whichhost].password; if (password == NULL) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index fa5af18ffac..e9627c06ec1 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -124,6 +124,7 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, #define DefaultTty "" #define DefaultOption "" #define DefaultAuthtype "" +#define DefaultChannelBinding "prefer" #define DefaultTargetSessionAttrs "any" #ifdef USE_SSL #define DefaultSSLMode "prefer" @@ -211,6 +212,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Database-Password-File", "", 64, offsetof(struct pg_conn, pgpassfile)}, + {"channel_binding", "PGCHANNELBINDING", NULL, NULL, + "Channel-Binding", "", 7, /* sizeof("require") */ + offsetof(struct pg_conn, channel_binding)}, + {"connect_timeout", "PGCONNECT_TIMEOUT", NULL, NULL, "Connect-timeout", "", 10, /* strlen(INT32_MAX) == 10 */ offsetof(struct pg_conn, connect_timeout)}, @@ -556,6 +561,7 @@ pqDropServerData(PGconn *conn) conn->last_sqlstate[0] = '\0'; conn->auth_req_received = false; conn->password_needed = false; + conn->channel_bound = false; conn->write_failed = false; if (conn->write_err_msg) free(conn->write_err_msg); @@ -1197,6 +1203,28 @@ connectOptions2(PGconn *conn) } } + /* + * validate channel_binding option + */ + if (conn->channel_binding) + { + if (strcmp(conn->channel_binding, "prefer") != 0 + && strcmp(conn->channel_binding, "require") != 0) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("invalid channel_binding value: \"%s\"\n"), + conn->channel_binding); + return false; + } + } + else + { + conn->channel_binding = strdup(DefaultChannelBinding); + if (!conn->channel_binding) + goto oom_error; + } + /* * validate sslmode option */ diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index d37bb3ce404..70a5b445d72 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -347,6 +347,7 @@ struct pg_conn char *pguser; /* Postgres username and password, if any */ char *pgpass; char *pgpassfile; /* path to a file containing password(s) */ + char *channel_binding; /* channel binding mode (require,prefer) */ char *keepalives; /* use TCP keepalives? */ char *keepalives_idle; /* time between TCP keepalives */ char *keepalives_interval; /* time between TCP keepalive @@ -410,6 +411,7 @@ struct pg_conn int sversion; /* server version, e.g. 70401 for 7.4.1 */ bool auth_req_received; /* true if any type of auth req received */ bool password_needed; /* true if server demanded a password */ + bool channel_bound; /* true if channel_binding happened */ bool sigpipe_so; /* have we masked SIGPIPE via SO_NOSIGPIPE? */ bool sigpipe_flag; /* can we mask SIGPIPE via MSG_NOSIGNAL? */ bool write_failed; /* have we had a write failure on sock? */ -- 2.17.1