On Fri, May 18, 2018 at 10:46:46AM +0900, Michael Paquier wrote:
> From a security point of view, 1) is important for libpq, but I am not
> much enthusiast about 2) as a whole.  The backend has proper support for
> channel binding, hence other drivers speaking the protocol could have
> their own restriction mechanisms.

So, I have been playing with libpq to address point 1), and added a new
connection parameter called channel_binding_mode which can be set to
'prefer', which is what libpq uses now, and 'require'.  The patch has
two important parts:
1) If a server does not support channel binding, still it is sending
back a SCRAM authentication, but the client still wants to enforce the
use of channel binding, then libpq reacts as follows:
$ psql -d "dbname=foo channel_binding_mode=require"
psql: channel binding required for SASL authentication but no valid
mechanism could be selected
This requires pg_SASL_init() to be patched after the SASL mechanism has
been selected.  That error can be triggered with a v10 server with whom
a SCRAM authentication is done, as well as with a v11 server where SSL
is not used.  Some people may use sslmode=prefer in combination to
channel_binding_mode=require, in which case an error should be raised if
the SSL connection cannot be achieved first.  That addresses a bit of
the craziness of sslmode=prefer...
2) If client wants to use channel binding, but the server is trying to
enforce another protocol than SCRAM, like MD5, trust, gssapi or such,
then the following error happens:
$ psql -d "dbname=foo channel_binding_mode=require"
psql: channel binding required for authentication but no valid protocol are used
In this case, it seems to me that the best bet is to patch
pg_fe_sendauth() and filter by message types.

In the attached, I have added the parameter and some documentation.  I
have not added tests, but some things could be tested in the SSL suite:
- Check for incorrect values in the parameter.
- Test connection without SCRAM with "require"
- Test connection without SSL but SCRAM with "require"
I have not put much thoughts into the documentation, but the patch is
rather simple so hopefully that helps in making progress in the
discussion.
--
Michael
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 498b8df988..aedc65b63f 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1238,6 +1238,49 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-channel-binding-mode" xreflabel="channel_binding_mode">
+      <term><literal>channel_binding_mode</literal></term>
+      <listitem>
+       <para>
+        This option determines whether channel binding should be used for
+        authentication or not.  As of now, channel binding is only supported
+        with SCRAM authentication.  There are two modes:
+
+        <variablelist>
+         <varlistentry>
+          <term><literal>prefer</literal> (default)</term>
+          <listitem>
+           <para>
+            Try to use channel binding if available but rely on the server
+            depending on what types of SASL mechanisms are published during
+            the message exchange.  This is the default, and it can be
+            considered as insecure as this does not protect from servers
+            attempting downgrade authentication attacks to a client.
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>require</literal></term>
+          <listitem>
+           <para>
+            Reject any connection attempts to a server if channel binding
+            is not supported by the protocol used.  Channel binding being
+            supported only for SCRAM in the context of an SSL connection,
+            a connection with this option enabled would fail except in this
+            authentication context.  This protects from downgrade attacks
+            and ensures that a SCRAM connection with channel binding is
+            able to transparently achieve MITM protection with
+            <application>libpq</application>.
+           </para>
+          </listitem>
+         </varlistentry>
+        </variablelist>
+       </para>
+
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-scram-channel-binding" xreflabel="scram_channel_binding">
       <term><literal>scram_channel_binding</literal></term>
       <listitem>
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 3b2073a47f..e23ab83c1b 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -547,6 +547,25 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 		goto error;
 	}
 
+	/*
+	 * If client is willing to enforce the use the channel binding but
+	 * it has not been previously selected, because it was either not
+	 * published by the server or could not be selected, then complain
+	 * back.  If SSL is not used for this connection, still complain
+	 * similarly, as the client may want channel binding but forgot
+	 * to set it up to do so which could be the case with sslmode=prefer
+	 * for example.  This protects from any kind of downgrade attacks
+	 * from rogue servers attempting to bypass channel binding.
+	 */
+	if (conn->channel_binding_mode &&
+		strcmp(conn->channel_binding_mode, "require") == 0 &&
+		strcmp(selected_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("channel binding required for SASL authentication but no valid mechanism could be selected\n"));
+		goto error;
+	}
+
 	/*
 	 * Now that the SASL mechanism has been chosen for the exchange,
 	 * initialize its state information.
@@ -835,6 +854,24 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
 int
 pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
 {
+	/*
+	 * As of now, channel binding is only used for SASL authentication.
+	 * Hence if a server is not willing to use channel binding but
+	 * client wants to enforce its use, then check that valid
+	 * message types only are used.
+	 */
+	if (areq != AUTH_REQ_SASL &&
+		areq != AUTH_REQ_SASL_CONT &&
+		areq != AUTH_REQ_SASL_FIN &&
+		areq != AUTH_REQ_OK &&
+		conn->channel_binding_mode &&
+		strcmp(conn->channel_binding_mode, "require") == 0)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("channel binding required for authentication but invalid protocol is used\n"));
+		return STATUS_ERROR;
+	}
+
 	switch (areq)
 	{
 		case AUTH_REQ_OK:
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a7e969d7c1..b4df044ebf 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 DefaultAuthtype		  ""
 #define DefaultTargetSessionAttrs	"any"
 #define DefaultSCRAMChannelBinding	SCRAM_CHANNEL_BINDING_TLS_UNIQUE
+#define DefaultChannelBindingMode	"prefer"
 #ifdef USE_SSL
 #define DefaultSSLMode "prefer"
 #else
@@ -269,6 +270,11 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		21,						/* sizeof("tls-server-end-point") == 21 */
 	offsetof(struct pg_conn, scram_channel_binding)},
 
+	{"channel_binding_mode", NULL, DefaultChannelBindingMode, NULL,
+		"Channel-Binding-Mode", "D",
+		8,						/* sizeof("require") == 8 */
+	offsetof(struct pg_conn, channel_binding_mode)},
+
 	/*
 	 * ssl options are allowed even without client SSL support because the
 	 * client can still handle SSL modes "disable" and "allow". Other
@@ -1166,6 +1172,20 @@ connectOptions2(PGconn *conn)
 			goto oom_error;
 	}
 
+	/* validate channel_binding_mode option */
+	if (conn->channel_binding_mode)
+	{
+		if (strcmp(conn->channel_binding_mode, "prefer") != 0 &&
+			strcmp(conn->channel_binding_mode, "require") != 0)
+		{
+			conn->status = CONNECTION_BAD;
+			printfPQExpBuffer(&conn->errorMessage,
+							  libpq_gettext("invalid channel_binding_mode value: \"%s\"\n"),
+							  conn->channel_binding_mode);
+			return false;
+		}
+	}
+
 	/*
 	 * Resolve special "auto" client_encoding from the locale
 	 */
@@ -3478,6 +3498,8 @@ freePGconn(PGconn *conn)
 		free(conn->keepalives_count);
 	if (conn->scram_channel_binding)
 		free(conn->scram_channel_binding);
+	if (conn->channel_binding_mode)
+		free(conn->channel_binding_mode);
 	if (conn->sslmode)
 		free(conn->sslmode);
 	if (conn->sslcert)
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 9a586ff25a..9d4192b7d8 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -350,6 +350,7 @@ struct pg_conn
 	char	   *keepalives_count;	/* maximum number of TCP keepalive
 									 * retransmits */
 	char	   *scram_channel_binding;	/* SCRAM channel binding type */
+	char	   *channel_binding_mode;	/* channel binding mode */
 	char	   *sslmode;		/* SSL mode (require,prefer,allow,disable) */
 	char	   *sslcompression; /* SSL compression (0 or 1) */
 	char	   *sslkey;			/* client key filename */

Attachment: signature.asc
Description: PGP signature

Reply via email to