From bb80b82d16597f725b4dc79d6b539715b27a2cf0 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 10 Oct 2017 21:51:18 +0900
Subject: [PATCH 2/4] Support channel binding 'tls-unique' in SCRAM

This is the basic feature set using OpenSSL to support the feature.
In order to allow the frontend and the backend to fetch the sent and
expected TLS finish messages, a PG-like API is added to be able to
make the interface pluggable for other SSL implementations.

This commit also adds a lot of basic infrastructure to facilitate the
addition of future channel binding types as well as libpq parameters
to control the SASL mechanism names and channel binding names. Those
will be added by upcoming commits.

A set of basic tests to stress default channel binding handling is
added as well, though those are part of the SSL suite.
---
 doc/src/sgml/protocol.sgml               |  25 +++--
 src/backend/libpq/auth-scram.c           | 162 +++++++++++++++++++++-----
 src/backend/libpq/auth.c                 |  69 ++++++++++--
 src/backend/libpq/be-secure-openssl.c    |  24 ++++
 src/include/libpq/libpq-be.h             |   1 +
 src/include/libpq/scram.h                |  10 +-
 src/interfaces/libpq/fe-auth-scram.c     | 187 +++++++++++++++++++++++++++----
 src/interfaces/libpq/fe-auth.c           | 100 +++++++++++++----
 src/interfaces/libpq/fe-auth.h           |   5 +-
 src/interfaces/libpq/fe-secure-openssl.c |  26 +++++
 src/interfaces/libpq/libpq-int.h         |   5 +-
 src/test/ssl/ServerSetup.pm              |  19 +++-
 src/test/ssl/t/001_ssltests.pl           |   2 +-
 src/test/ssl/t/002_sasl.pl               |  41 +++++++
 14 files changed, 584 insertions(+), 92 deletions(-)
 create mode 100644 src/test/ssl/t/002_sasl.pl

diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 526e8011de..3ba8575fba 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1461,10 +1461,11 @@ SELCT 1/0;
 
 <para>
 <firstterm>SASL</> is a framework for authentication in connection-oriented
-protocols. At the moment, <productname>PostgreSQL</> implements only one SASL
-authentication mechanism, SCRAM-SHA-256, but more might be added in the
-future. The below steps illustrate how SASL authentication is performed in
-general, while the next subsection gives more details on SCRAM-SHA-256.
+protocols. At the moment, <productname>PostgreSQL</> implements only two SASL
+authentication mechanisms, SCRAM-SHA-256 and SCRAM-SHA-256-PLUS, but more
+might be added in the future. The below steps illustrate how SASL
+authentication is performed in general, while the next subsection gives
+more details on SCRAM-SHA-256 and SCRAM-SHA-256-PLUS.
 </para>
 
 <procedure>
@@ -1547,7 +1548,10 @@ the password is in.
   </para>
 
   <para>
-<firstterm>Channel binding</> has not been implemented yet.
+<firstterm>Channel binding</> is supported in builds with SSL support, and
+uses as mechanism name <literal>SCRAM-SHA-256-PLUS</> for this purpose as
+defined per IANA. The only channel binding type supported by the server
+is <literal>tls-unique</>.
   </para>
 
 <procedure>
@@ -1556,14 +1560,19 @@ the password is in.
 <para>
   The server sends an AuthenticationSASL message. It includes a list of
   SASL authentication mechanisms that the server can accept.
+  <literal>SCRAM-SHA-256</> and <literal>SCRAM-SHA-256-PLUS</> are the
+  two mechanism names that the server lists in this message. Support for
+  channel binding is not included if the server is built without SSL
+  support. The client can still choose to not use channel binding for
+  a connection attempt with SSL.
 </para>
 </step>
 <step id="scram-client-first">
 <para>
   The client responds by sending a SASLInitialResponse message, which
-  indicates the chosen mechanism, <literal>SCRAM-SHA-256</>. In the Initial
-  Client response field, the message contains the SCRAM
-  <structname>client-first-message</>.
+  indicates the chosen mechanism, <literal>SCRAM-SHA-256</> or
+  <literal>SCRAM-SHA-256-PLUS</>. In the Initial Client response field,
+  the message contains the SCRAM <structname>client-first-message</>.
 </para>
 </step>
 <step id="scram-server-first">
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 9161c885e1..2efc198459 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -17,8 +17,6 @@
  *	 by the SASLprep profile, we skip the SASLprep pre-processing and use
  *	 the raw bytes in calculating the hash.
  *
- * - Channel binding is not supported yet.
- *
  *
  * The password stored in pg_authid consists of the iteration count, salt,
  * StoredKey and ServerKey.
@@ -112,6 +110,11 @@ typedef struct
 
 	const char *username;		/* username from startup packet */
 
+	bool		ssl_in_use;
+	char	   *tls_finished_message;
+	int			tls_finished_len;
+	char	   *channel_binding;
+
 	int			iterations;
 	char	   *salt;			/* base64-encoded */
 	uint8		StoredKey[SCRAM_KEY_LEN];
@@ -168,7 +171,11 @@ static char *scram_mock_salt(const char *username);
  * it will fail, as if an incorrect password was given.
  */
 void *
-pg_be_scram_init(const char *username, const char *shadow_pass)
+pg_be_scram_init(const char *username,
+				 const char *shadow_pass,
+				 bool ssl_in_use,
+				 char *tls_finished_message,
+				 int tls_finished_len)
 {
 	scram_state *state;
 	bool		got_verifier;
@@ -176,6 +183,10 @@ pg_be_scram_init(const char *username, const char *shadow_pass)
 	state = (scram_state *) palloc0(sizeof(scram_state));
 	state->state = SCRAM_AUTH_INIT;
 	state->username = username;
+	state->ssl_in_use = ssl_in_use;
+	state->tls_finished_message = tls_finished_message;
+	state->tls_finished_len = tls_finished_len;
+	state->channel_binding = NULL;
 
 	/*
 	 * Parse the stored password verifier.
@@ -773,31 +784,84 @@ read_client_first_message(scram_state *state, char *input)
 	 *------
 	 */
 
-	/* read gs2-cbind-flag */
+	/*
+	 * Read gs2-cbind-flag. Server handles its value as described in RFC 5802,
+	 * section 6 dealing with channel binding.
+	 */
 	switch (*input)
 	{
 		case 'n':
-			/* Client does not support channel binding */
+			/*
+			 * The client does not support channel binding, or has simply
+			 * decided to not use it. In this case just let go.
+			 */
+			input++;
+			if (*input != ',')
+				ereport(ERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 errmsg("malformed SCRAM message"),
+						 errdetail("Comma expected, but found character \"%s\".",
+								   sanitize_char(*input))));
 			input++;
 			break;
 		case 'y':
-			/* Client supports channel binding, but we're not doing it today */
+			/*
+			 * Client supports channel binding and thinks that the server does
+			 * not. In this case, the server must fail authentication if it
+			 * supports channel binding, which is only the case if a connection
+			 * is done when SSL is used.
+			 */
+			if (state->ssl_in_use)
+				ereport(ERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 errmsg("client supports SCRAM channel binding, but server needs it for SSL connections")));
+			input++;
+			if (*input != ',')
+				ereport(ERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 errmsg("malformed SCRAM message"),
+						 errdetail("Comma expected, but found character \"%s\".",
+								   sanitize_char(*input))));
 			input++;
 			break;
 		case 'p':
+			{
+#ifdef USE_SSL
+				char *channel_name;
 
-			/*
-			 * Client requires channel binding.  We don't support it.
-			 *
-			 * RFC 5802 specifies a particular error code,
-			 * e=server-does-support-channel-binding, for this.  But it can
-			 * only be sent in the server-final message, and we don't want to
-			 * go through the motions of the authentication, knowing it will
-			 * fail, just to send that error message.
-			 */
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("client requires SCRAM channel binding, but it is not supported")));
+				if (!state->ssl_in_use)
+					ereport(ERROR,
+							(errcode(ERRCODE_PROTOCOL_VIOLATION),
+							 errmsg("client requires SCRAM channel binding, but it is not supported")));
+
+				/*
+				 * Read value provided by client, only tls-unique is supported
+				 * for now.
+				 */
+				channel_name = read_attr_value(&input, 'p');
+				if (strcmp(channel_name, SCRAM_CHANNEL_TLS_UNIQUE) != 0)
+					ereport(ERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 (errmsg("unexpected SCRAM channel-binding type"))));
+
+				/* Save the name for handling of subsequent messages */
+				state->channel_binding = pstrdup(channel_name);
+#else
+				/*
+				 * Client requires channel binding.  We don't support it.
+				 *
+				 * RFC 5802 specifies a particular error code,
+				 * e=server-does-support-channel-binding, for this.  But it
+				 * can only be sent in the server-final message, and we don't
+				 * want to go through the motions of the authentication,
+				 * knowing it will fail, just to send that error message.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("client requires SCRAM channel binding, but it is not supported")));
+#endif
+			}
+			break;
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_PROTOCOL_VIOLATION),
@@ -805,13 +869,6 @@ read_client_first_message(scram_state *state, char *input)
 					 errdetail("Unexpected channel-binding flag \"%s\".",
 							   sanitize_char(*input))));
 	}
-	if (*input != ',')
-		ereport(ERROR,
-				(errcode(ERRCODE_PROTOCOL_VIOLATION),
-				 errmsg("malformed SCRAM message"),
-				 errdetail("Comma expected, but found character \"%s\".",
-						   sanitize_char(*input))));
-	input++;
 
 	/*
 	 * Forbid optional authzid (authorization identity).  We don't support it.
@@ -1032,14 +1089,65 @@ read_client_final_message(scram_state *state, char *input)
 	 */
 
 	/*
-	 * Read channel-binding.  We don't support channel binding, so it's
-	 * expected to always be "biws", which is "n,,", base64-encoded.
+	 * Read channel-binding.  We don't support channel binding for builds
+	 * without SSL support or if the client does not want to use channel
+	 * binding, so it's expected to always be "biws" in this case, which
+	 * is "n,,", base64-encoded.  In builds supporting SSL, "biws" is
+	 * used for Non-SSL connection attempts, and for SSL connections the
+	 * client has to provide channel binding value if needed.
 	 */
 	channel_binding = read_attr_value(&p, 'c');
+#ifdef USE_SSL
+	if (state->ssl_in_use &&
+		state->channel_binding)
+	{
+		char	   *b64_message, *raw_data;
+		int			b64_message_len, raw_data_len;
+
+		/* Fetch data for each channel binding type */
+		if (strcmp(state->channel_binding, SCRAM_CHANNEL_TLS_UNIQUE) == 0)
+		{
+			raw_data = state->tls_finished_message;
+			raw_data_len = state->tls_finished_len;
+		}
+		else
+		{
+			/* should not happen */
+			elog(ERROR, "invalid channel binding type");
+		}
+
+		/* should not happen, but better safe than sorry */
+		if (raw_data == NULL)
+			elog(ERROR, "empty binding data for channel name \"%s\"",
+				 state->channel_binding);
+
+		b64_message = palloc(pg_b64_enc_len(raw_data_len) + 1);
+		b64_message_len = pg_b64_encode(raw_data, raw_data_len,
+										b64_message);
+		b64_message[b64_message_len] = '\0';
+
+		/*
+		 * Compare the value sent by the client with the value expected by
+		 * the server.
+		 */
+		if (strcmp(channel_binding, b64_message) != 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_PROTOCOL_VIOLATION),
+					 (errmsg("no match for SCRAM channel-binding attribute in client-final-message"))));
+	}
+	else
+	{
+		if (strcmp(channel_binding, "biws") != 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_PROTOCOL_VIOLATION),
+					 (errmsg("unexpected SCRAM channel-binding attribute in client-final-message"))));
+	}
+#else
 	if (strcmp(channel_binding, "biws") != 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_PROTOCOL_VIOLATION),
 				 (errmsg("unexpected SCRAM channel-binding attribute in client-final-message"))));
+#endif
 	state->client_final_nonce = read_attr_value(&p, 'r');
 
 	/* ignore optional extensions */
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 480e344eb3..5bceae2162 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -859,10 +859,14 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
 	void	   *scram_opaq;
 	char	   *output = NULL;
 	int			outputlen = 0;
-	char	   *input;
+	char	   *input, *p;
+	char	   *sasl_mechs;
 	int			inputlen;
+	int			listlen = 0;
 	int			result;
 	bool		initial;
+	char	   *tls_finished = NULL;
+	int			tls_finished_len = 0;
 
 	/*
 	 * SASL auth is not supported for protocol versions before 3, because it
@@ -879,12 +883,49 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
 
 	/*
 	 * Send the SASL authentication request to user.  It includes the list of
-	 * authentication mechanisms (which is trivial, because we only support
-	 * SCRAM-SHA-256 at the moment).  The extra "\0" is for an empty string to
-	 * terminate the list.
+	 * authentication mechanisms that are supported:
+	 * - SCRAM-SHA-256, which is the mechanism with the same name.
+	 * - SCRAM-SHA-256-PLUS, which is SCRAM-SHA-256 with channel binding. This
+	 * is advertised to the client only if connection is attempted with SSL.
+	 * The order of mechanisms is advertised in decreasing order of importance.
+	 * The extra "\0" is for an empty string to terminate the list, and each
+	 * mechanism listed needs to be separated with "\0".
 	 */
-	sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME "\0",
-					strlen(SCRAM_SHA256_NAME) + 2);
+	listlen = 0;
+	sasl_mechs = (char *) palloc(strlen(SCRAM_SHA256_PLUS_NAME) +
+								 strlen(SCRAM_SHA256_NAME) + 3);
+	p = sasl_mechs;
+
+	/* add SCRAM-SHA-256-PLUS, which depends on if SSL is in use */
+	if (port->ssl_in_use)
+	{
+		strcpy(p, SCRAM_SHA256_PLUS_NAME);
+		listlen += strlen(SCRAM_SHA256_PLUS_NAME) + 1;
+		p += strlen(SCRAM_SHA256_PLUS_NAME) + 1;
+	}
+
+	/* add generic SCRAM-SHA-256 */
+	strcpy(p, SCRAM_SHA256_NAME);
+	listlen += strlen(SCRAM_SHA256_NAME) + 1;
+	p += strlen(SCRAM_SHA256_NAME) + 1;
+
+	/* put "\0" to mark that list is finished */
+	p[0] = '\0';
+	listlen++;
+
+	sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs, listlen);
+	pfree(sasl_mechs);
+
+#ifdef USE_SSL
+	/*
+	 * Fetch the data related to the SSL finish message to be used in the
+	 * exchange.
+	 */
+	if (port->ssl_in_use)
+	{
+		tls_finished = be_tls_get_peer_finish(port, &tls_finished_len);
+	}
+#endif
 
 	/*
 	 * Initialize the status tracker for message exchanges.
@@ -897,7 +938,11 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
 	 * This is because we don't want to reveal to an attacker what usernames
 	 * are valid, nor which users have a valid password.
 	 */
-	scram_opaq = pg_be_scram_init(port->user_name, shadow_pass);
+	scram_opaq = pg_be_scram_init(port->user_name,
+								  shadow_pass,
+								  port->ssl_in_use,
+								  tls_finished,
+								  tls_finished_len);
 
 	/*
 	 * Loop through SASL message exchange.  This exchange can consist of
@@ -946,15 +991,17 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
 			const char *selected_mech;
 
 			/*
-			 * We only support SCRAM-SHA-256 at the moment, so anything else
-			 * is an error.
+			 * We only support SCRAM-SHA-256 and SCRAM-SHA-256-PLUS at the
+			 * moment, so anything else is an error.
 			 */
 			selected_mech = pq_getmsgrawstring(&buf);
-			if (strcmp(selected_mech, SCRAM_SHA256_NAME) != 0)
+			if (strcmp(selected_mech, SCRAM_SHA256_NAME) != 0 &&
+				strcmp(selected_mech, SCRAM_SHA256_PLUS_NAME) != 0)
 			{
 				ereport(ERROR,
 						(errcode(ERRCODE_PROTOCOL_VIOLATION),
-						 errmsg("client selected an invalid SASL authentication mechanism")));
+						 errmsg("client selected an invalid SASL authentication mechanism"),
+						 errdetail("Incorrect SASL mechanism name specified")));
 			}
 
 			inputlen = pq_getmsgint(&buf, 4);
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index fe15227a77..d063a587d0 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1215,6 +1215,30 @@ be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
 		ptr[0] = '\0';
 }
 
+/*
+ * Routine to get the expected TLS finish message information from the
+ * client, useful for authorization when doing channel binding.
+ *
+ * Result is a palloc'd copy of the TLS finish message with its size.
+ */
+char *
+be_tls_get_peer_finish(Port *port, int *len)
+{
+	char		dummy[1];
+	char	   *result;
+
+	/*
+	 * OpenSSL does not offer an API to get directly the length of the
+	 * expected TLS finish message, so just do a dummy call to grab this
+	 * information to allow caller to do an allocation with a correct size.
+	 */
+	*len = SSL_get_peer_finished(port->ssl, dummy, sizeof(dummy));
+	result = (char *) palloc(*len * sizeof(char));
+	(void) SSL_get_peer_finished(port->ssl, result, *len);
+
+	return result;
+}
+
 /*
  * Convert an X509 subject name to a cstring.
  *
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7bde744d51..5d59d79822 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -209,6 +209,7 @@ extern bool be_tls_get_compression(Port *port);
 extern void be_tls_get_version(Port *port, char *ptr, size_t len);
 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);
+extern char *be_tls_get_peer_finish(Port *port, int *len);
 #endif
 
 extern ProtocolVersion FrontendProtocol;
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index 0166e1945d..43cde0e46e 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -13,8 +13,12 @@
 #ifndef PG_SCRAM_H
 #define PG_SCRAM_H
 
-/* Name of SCRAM-SHA-256 per IANA */
+/* Name of SCRAM mechanisms per IANA */
 #define SCRAM_SHA256_NAME "SCRAM-SHA-256"
+#define SCRAM_SHA256_PLUS_NAME "SCRAM-SHA-256-PLUS"	/* with channel binding */
+
+/* Channel binding names */
+#define SCRAM_CHANNEL_TLS_UNIQUE	"tls-unique"
 
 /* Status codes for message exchange */
 #define SASL_EXCHANGE_CONTINUE		0
@@ -22,7 +26,9 @@
 #define SASL_EXCHANGE_FAILURE		2
 
 /* Routines dedicated to authentication */
-extern void *pg_be_scram_init(const char *username, const char *shadow_pass);
+extern void *pg_be_scram_init(const char *username, const char *shadow_pass,
+					 bool ssl_in_use, char *tls_finished_message,
+					 int tls_finished_len);
 extern int pg_be_scram_exchange(void *opaq, char *input, int inputlen,
 					 char **output, int *outputlen, char **logdetail);
 
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index edfd42df85..9bce4d980a 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -17,6 +17,7 @@
 #include "common/base64.h"
 #include "common/saslprep.h"
 #include "common/scram-common.h"
+#include "libpq/scram.h"
 #include "fe-auth.h"
 
 /* These are needed for getpid(), in the fallback implementation */
@@ -44,6 +45,13 @@ typedef struct
 	/* These are supplied by the user */
 	const char *username;
 	char	   *password;
+	bool		ssl_in_use;
+	bool		channel_binding_advertised;
+	char	   *tls_finished_message;
+	int			tls_finished_len;
+	/* enforce-able user parameters */
+	char	   *saslmechanism;		/* name of mechanism used */
+	char	   *saslchannelbinding;	/* name of channel binding to use */
 
 	/* We construct these */
 	uint8		SaltedPassword[SCRAM_KEY_LEN];
@@ -81,18 +89,41 @@ static bool pg_frontend_random(char *dst, int len);
  * Initialize SCRAM exchange status.
  */
 void *
-pg_fe_scram_init(const char *username, const char *password)
+pg_fe_scram_init(const char *username,
+				 const char *password,
+				 bool ssl_in_use,
+				 bool channel_binding_advertised,
+				 const char *saslmechanism,
+				 char *saslchannelbinding,
+				 char *tls_finished_message,
+				 int tls_finished_len)
 {
 	fe_scram_state *state;
 	char	   *prep_password;
 	pg_saslprep_rc rc;
 
+	Assert(saslmechanism != NULL);
+
 	state = (fe_scram_state *) malloc(sizeof(fe_scram_state));
 	if (!state)
 		return NULL;
 	memset(state, 0, sizeof(fe_scram_state));
 	state->state = FE_SCRAM_INIT;
 	state->username = username;
+	state->ssl_in_use = ssl_in_use;
+	state->channel_binding_advertised = channel_binding_advertised;
+	state->tls_finished_message = tls_finished_message;
+	state->tls_finished_len = tls_finished_len;
+	state->saslmechanism = strdup(saslmechanism);
+
+	/*
+	 * If user has specified a channel binding to use, enforce the
+	 * channel binding sent to it.  The default is "tls-unique".
+	 */
+	if (saslchannelbinding && strlen(saslchannelbinding) > 0)
+		state->saslchannelbinding = strdup(saslchannelbinding);
+	else
+		state->saslchannelbinding = strdup(SCRAM_CHANNEL_TLS_UNIQUE);
 
 	/* Normalize the password with SASLprep, if possible */
 	rc = pg_saslprep(password, &prep_password);
@@ -125,6 +156,12 @@ pg_fe_scram_free(void *opaq)
 
 	if (state->password)
 		free(state->password);
+	if (state->tls_finished_message)
+		free(state->tls_finished_message);
+	if (state->saslmechanism)
+		free(state->saslmechanism);
+	if (state->saslchannelbinding)
+		free(state->saslchannelbinding);
 
 	/* client messages */
 	if (state->client_nonce)
@@ -297,9 +334,10 @@ static char *
 build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage)
 {
 	char		raw_nonce[SCRAM_RAW_NONCE_LEN + 1];
-	char	   *buf;
-	char		buflen;
+	char	   *result;
+	int			channel_info_len;
 	int			encoded_len;
+	PQExpBufferData buf;
 
 	/*
 	 * Generate a "raw" nonce.  This is converted to ASCII-printable form by
@@ -328,26 +366,88 @@ build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage)
 	 * prepared with SASLprep, the message parsing would fail if it includes
 	 * '=' or ',' characters.
 	 */
-	buflen = 8 + strlen(state->client_nonce) + 1;
-	buf = malloc(buflen);
-	if (buf == NULL)
+	initPQExpBuffer(&buf);
+
+	/*
+	 * First build the query field for channel binding.  Conditions used for
+	 * the decision-making are described in more details below.
+	 */
+#ifdef USE_SSL
+	if (strcmp(state->saslmechanism, SCRAM_SHA256_PLUS_NAME) == 0)
 	{
-		printfPQExpBuffer(errormessage,
-						  libpq_gettext("out of memory\n"));
-		return NULL;
-	}
-	snprintf(buf, buflen, "n,,n=,r=%s", state->client_nonce);
+		if (!state->ssl_in_use)
+		{
+			/*
+			 * Attempting to use channel binding, but this is not supported
+			 * in non-SSL contexts, so complain.
+			 */
+			printfPQExpBuffer(errormessage,
+				  libpq_gettext("channel binding not supported in non-SSL context.\n"));
+			return NULL;
+		}
 
-	state->client_first_message_bare = strdup(buf + 3);
-	if (!state->client_first_message_bare)
+		if (state->channel_binding_advertised)
+		{
+			/*
+			 * Channel binding is wanted by client and server supports it
+			 * so allow its use.
+			 */
+			appendPQExpBuffer(&buf, "p=%s", state->saslchannelbinding);
+		}
+		else
+		{
+			/*
+			 * Client supports channel binding and expects the server to
+			 * do so as well, so let the server know about that. Note that
+			 * as authentication is not complete yet, we do not know the
+			 * backend version, so there is not much we can do about it.
+			 */
+			appendPQExpBuffer(&buf, "y");
+			return NULL;
+		}
+	}
+	else
 	{
-		free(buf);
-		printfPQExpBuffer(errormessage,
-						  libpq_gettext("out of memory\n"));
-		return NULL;
+		/*
+		 * Caller may not wish to use channel binding, so let server know
+		 * that.
+		 */
+		appendPQExpBuffer(&buf, "n");
 	}
+#else
+	/* Build does not support SSL, so it does not support channel binding */
+	appendPQExpBuffer(&buf, "n");
+#endif
+
+	if (PQExpBufferDataBroken(buf))
+		goto oom_error;
 
-	return buf;
+	channel_info_len = buf.len;
+
+	appendPQExpBuffer(&buf, ",,n=,r=%s", state->client_nonce);
+	if (PQExpBufferDataBroken(buf))
+		goto oom_error;
+
+	/*
+	 * The first message content needs to be saved without channel binding
+	 * information.
+	 */
+	state->client_first_message_bare = strdup(buf.data + channel_info_len + 2);
+	if (!state->client_first_message_bare)
+		goto oom_error;
+
+	result = strdup(buf.data);
+	if (result == NULL)
+		goto oom_error;
+
+	termPQExpBuffer(&buf);
+	return result;
+
+oom_error:
+	termPQExpBuffer(&buf);
+	printfPQExpBuffer(errormessage,
+					  libpq_gettext("out of memory\n"));
+	return NULL;
 }
 
 /*
@@ -365,8 +465,57 @@ build_client_final_message(fe_scram_state *state, PQExpBuffer errormessage)
 	/*
 	 * Construct client-final-message-without-proof.  We need to remember it
 	 * for verifying the server proof in the final step of authentication.
+	 * Client needs to provide a b64 encoded string of the TLS finish message
+	 * only if a SSL connection is attempted.
 	 */
-	appendPQExpBuffer(&buf, "c=biws,r=%s", state->nonce);
+#ifdef USE_SSL
+	if (strcmp(state->saslmechanism, SCRAM_SHA256_PLUS_NAME) == 0)
+	{
+		char	   *raw_data;
+		int			raw_data_len;
+
+		if (strcmp(state->saslchannelbinding, SCRAM_CHANNEL_TLS_UNIQUE) == 0)
+		{
+			raw_data = state->tls_finished_message;
+			raw_data_len = state->tls_finished_len;
+		}
+		else
+		{
+			/* should not happen */
+			termPQExpBuffer(&buf);
+			printfPQExpBuffer(errormessage,
+							  libpq_gettext("incorrect channel binding name\n"));
+			return NULL;
+		}
+
+		/* should not happen, but better safe than sorry */
+		if (raw_data == NULL)
+		{
+			/* should not happen */
+			termPQExpBuffer(&buf);
+			printfPQExpBuffer(errormessage,
+							  libpq_gettext("empty binding data for channel name \"%s\"\n"),
+							  state->saslchannelbinding);
+			return NULL;
+		}
+
+		appendPQExpBuffer(&buf, "c=");
+
+		if (!enlargePQExpBuffer(&buf, pg_b64_enc_len(raw_data_len)))
+			goto oom_error;
+		buf.len += pg_b64_encode(raw_data, raw_data_len, buf.data + buf.len);
+		buf.data[buf.len] = '\0';
+	}
+	else
+		appendPQExpBuffer(&buf, "c=biws");
+#else
+	appendPQExpBuffer(&buf, "c=biws");
+#endif
+
+	if (PQExpBufferDataBroken(buf))
+		goto oom_error;
+
+	appendPQExpBuffer(&buf, ",r=%s", state->nonce);
 	if (PQExpBufferDataBroken(buf))
 		goto oom_error;
 
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 382558f3f8..fa24c88522 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -491,6 +491,10 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 	bool		success;
 	const char *selected_mechanism;
 	PQExpBufferData mechanism_buf;
+	bool		channel_binding_advertised = false;
+	char	   *tls_finished = NULL;
+	int			tls_finished_len = 0;
+	char	   *password;
 
 	initPQExpBuffer(&mechanism_buf);
 
@@ -504,7 +508,8 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 	/*
 	 * Parse the list of SASL authentication mechanisms in the
 	 * AuthenticationSASL message, and select the best mechanism that we
-	 * support.  (Only SCRAM-SHA-256 is supported at the moment.)
+	 * support.  SCRAM-SHA-256-PLUS and SCRAM-SHA-256 are the only ones
+	 * supported at the moment, listed by order of decreasing importance.
 	 */
 	selected_mechanism = NULL;
 	for (;;)
@@ -522,6 +527,15 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 		if (mechanism_buf.data[0] == '\0')
 			break;
 
+		/*
+		 * The SASL exchange needs to know if channel binding has been
+		 * advertized. This prevents attacks from rogue servers that
+		 * may have the correct version but are not telling clients that
+		 * about channel binding.
+		 */
+		if (strcmp(mechanism_buf.data, SCRAM_SHA256_PLUS_NAME) == 0)
+			channel_binding_advertised = true;
+
 		/*
 		 * If we have already selected a mechanism, just skip through the rest
 		 * of the list.
@@ -532,25 +546,26 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 		/*
 		 * Do we support this mechanism?
 		 */
-		if (strcmp(mechanism_buf.data, SCRAM_SHA256_NAME) == 0)
+		if (strcmp(mechanism_buf.data, SCRAM_SHA256_NAME) == 0 ||
+			strcmp(mechanism_buf.data, SCRAM_SHA256_PLUS_NAME) == 0)
 		{
-			char	   *password;
-
-			conn->password_needed = true;
-			password = conn->connhost[conn->whichhost].password;
-			if (password == NULL)
-				password = conn->pgpass;
-			if (password == NULL || password[0] == '\0')
-			{
-				printfPQExpBuffer(&conn->errorMessage,
-								  PQnoPasswordSupplied);
-				goto error;
-			}
-
-			conn->sasl_state = pg_fe_scram_init(conn->pguser, password);
-			if (!conn->sasl_state)
-				goto oom_error;
-			selected_mechanism = SCRAM_SHA256_NAME;
+			/*
+			 * Select the mechanism to use by default. If SSL connection
+			 * is attempted, the server will expect the -PLUS mechanism.
+			 * If not, fallback to SCRAM-SHA-256.
+			 */
+#ifdef USE_SSL
+			if (conn->ssl_in_use &&
+				strcmp(mechanism_buf.data, SCRAM_SHA256_PLUS_NAME) == 0)
+				selected_mechanism = SCRAM_SHA256_PLUS_NAME;
+			else if (!conn->ssl_in_use &&
+					 strcmp(mechanism_buf.data, SCRAM_SHA256_NAME) == 0)
+				selected_mechanism = SCRAM_SHA256_NAME;
+#else
+			/* No channel binding can be selected without SSL support */
+			if (strcmp(mechanism_buf.data, SCRAM_SHA256_NAME) == 0)
+				selected_mechanism = SCRAM_SHA256_NAME;
+#endif
 		}
 	}
 
@@ -561,6 +576,53 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 		goto error;
 	}
 
+	/*
+	 * Now that the SASL mechanism has been chosen for the exchange,
+	 * initialize its state information. First select the password to
+	 * use for the exchange protocol, complaining back if none are
+	 * provided.
+	 */
+	conn->password_needed = true;
+	password = conn->connhost[conn->whichhost].password;
+	if (password == NULL)
+		password = conn->pgpass;
+	if (password == NULL || password[0] == '\0')
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  PQnoPasswordSupplied);
+		goto error;
+	}
+
+#ifdef USE_SSL
+	/*
+	 * Fetch information about the TLS finish message and client
+	 * certificate if any.
+	 */
+	if (conn->ssl_in_use)
+	{
+		tls_finished = pgtls_get_finish(conn, &tls_finished_len);
+		if (tls_finished == NULL)
+			goto oom_error;
+	}
+#endif
+
+	/*
+	 * Initialize the SASL state information with all the information
+	 * gathered during the initial exchange.
+	 *
+	 * Note: Only tls-unique is supported for the moment.
+	 */
+	conn->sasl_state = pg_fe_scram_init(conn->pguser,
+										password,
+										conn->ssl_in_use,
+										channel_binding_advertised,
+										selected_mechanism,
+										NULL,	/* default channel binding */
+										tls_finished,
+										tls_finished_len);
+	if (!conn->sasl_state)
+		goto oom_error;
+
 	/* Get the mechanism-specific Initial Client Response, if any */
 	pg_fe_scram_exchange(conn->sasl_state,
 						 NULL, -1,
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 5dc6bb5341..81378d0008 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -23,7 +23,10 @@ extern int	pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn);
 extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
 
 /* Prototypes for functions in fe-auth-scram.c */
-extern void *pg_fe_scram_init(const char *username, const char *password);
+extern void *pg_fe_scram_init(const char *username, const char *password,
+					 bool ssl_in_use, bool channel_binding_advertised,
+					 const char *saslmechanism,char *saslchannelbinding,
+					 char *tls_finished_message, int tls_finished_len);
 extern void pg_fe_scram_free(void *opaq);
 extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
 					 char **output, int *outputlen,
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 2f29820e82..84a6e3c322 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -393,6 +393,32 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 	return n;
 }
 
+/*
+ *	Get the TLS finish message sent during last handshake
+ *
+ * This information is useful for callers doing channel binding during
+ * authentication.
+ */
+char *
+pgtls_get_finish(PGconn *conn, int *len)
+{
+	char		dummy[1];
+	char	   *result;
+
+	/*
+	 * OpenSSL does not offer an API to get directly the length of the
+	 * TLS finish message sent, so first do a dummy call to grab this
+	 * information and then do an allocation with the correct size.
+	 */
+	*len = SSL_get_finished(conn->ssl, dummy, sizeof(dummy));
+	result = malloc(*len);
+	if (result == NULL)
+		return NULL;
+	(void) SSL_get_finished(conn->ssl, result, *len);
+	return result;
+}
+
+
 /* ------------------------------------------------------------ */
 /*						OpenSSL specific code					*/
 /* ------------------------------------------------------------ */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 42913604e3..0eb8b60c95 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -453,11 +453,13 @@ struct pg_conn
 	/* Assorted state for SASL, SSL, GSS, etc */
 	void	   *sasl_state;
 
+	/* SSL structures */
+	bool		ssl_in_use;
+
 #ifdef USE_SSL
 	bool		allow_ssl_try;	/* Allowed to try SSL negotiation */
 	bool		wait_ssl_try;	/* Delay SSL negotiation until after
 								 * attempting normal connection */
-	bool		ssl_in_use;
 #ifdef USE_OPENSSL
 	SSL		   *ssl;			/* SSL status, if have SSL connection */
 	X509	   *peer;			/* X509 cert of server */
@@ -668,6 +670,7 @@ extern void pgtls_close(PGconn *conn);
 extern ssize_t pgtls_read(PGconn *conn, void *ptr, size_t len);
 extern bool pgtls_read_pending(PGconn *conn);
 extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
+extern char *pgtls_get_finish(PGconn *conn, int *len);
 
 /*
  * this is so that we can check if a connection is non-blocking internally
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index ad2e036602..b71969ac75 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -91,6 +91,9 @@ sub configure_test_server_for_ssl
 {
 	my $node       = $_[0];
 	my $serverhost = $_[1];
+	my $sslmethod  = $_[2];
+	my $passwd     = $_[3];
+	my $passwdhash = $_[4];
 
 	my $pgdata = $node->data_dir;
 
@@ -100,6 +103,15 @@ sub configure_test_server_for_ssl
 	$node->psql('postgres', "CREATE DATABASE trustdb");
 	$node->psql('postgres', "CREATE DATABASE certdb");
 
+	# Update password of each user as needed.
+	if (defined($passwd))
+	{
+		$node->psql('postgres',
+"SET password_encryption='$passwdhash'; ALTER USER ssltestuser PASSWORD '$passwd';");
+		$node->psql('postgres',
+"SET password_encryption='$passwdhash'; ALTER USER anotheruser PASSWORD '$passwd';");
+	}
+
 	# enable logging etc.
 	open my $conf, '>>', "$pgdata/postgresql.conf";
 	print $conf "fsync=off\n";
@@ -129,7 +141,7 @@ sub configure_test_server_for_ssl
 	$node->restart;
 
 	# Change pg_hba after restart because hostssl requires ssl=on
-	configure_hba_for_ssl($node, $serverhost);
+	configure_hba_for_ssl($node, $serverhost, $sslmethod);
 }
 
 # Change the configuration to use given server cert file, and reload
@@ -159,6 +171,7 @@ sub configure_hba_for_ssl
 {
 	my $node       = $_[0];
 	my $serverhost = $_[1];
+	my $sslmethod  = $_[2];
 	my $pgdata     = $node->data_dir;
 
   # Only accept SSL connections from localhost. Our tests don't depend on this
@@ -169,9 +182,9 @@ sub configure_hba_for_ssl
 	print $hba
 "# TYPE  DATABASE        USER            ADDRESS                 METHOD\n";
 	print $hba
-"hostssl trustdb         ssltestuser     $serverhost/32            trust\n";
+"hostssl trustdb         ssltestuser     $serverhost/32          $sslmethod\n";
 	print $hba
-"hostssl trustdb         ssltestuser     ::1/128                 trust\n";
+"hostssl trustdb         ssltestuser     ::1/128                 $sslmethod\n";
 	print $hba
 "hostssl certdb          ssltestuser     $serverhost/32            cert\n";
 	print $hba
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 890e3051a2..e690c1fa15 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -32,7 +32,7 @@ $node->init;
 $ENV{PGHOST} = $node->host;
 $ENV{PGPORT} = $node->port;
 $node->start;
-configure_test_server_for_ssl($node, $SERVERHOSTADDR);
+configure_test_server_for_ssl($node, $SERVERHOSTADDR, "trust");
 switch_server_cert($node, 'server-cn-only');
 
 ### Part 1. Run client-side tests.
diff --git a/src/test/ssl/t/002_sasl.pl b/src/test/ssl/t/002_sasl.pl
new file mode 100644
index 0000000000..d43a970b55
--- /dev/null
+++ b/src/test/ssl/t/002_sasl.pl
@@ -0,0 +1,41 @@
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 1;
+use ServerSetup;
+use File::Copy;
+
+# test combinations of SASL authentication for SCRAM mechanism:
+# - SCRAM-SHA-256 and SCRAM-SHA-256-PLUS
+# - Channel bindings
+
+# This is the hostname used to connect to the server.
+my $SERVERHOSTADDR = '127.0.0.1';
+
+# Allocation of base connection string shared among multiple tests.
+my $common_connstr;
+
+#### Part 0. Set up the server.
+
+note "setting up data directory";
+my $node = get_new_node('master');
+$node->init;
+
+# PGHOST is enforced here to set up the node, subsequent connections
+# will use a dedicated connection string.
+$ENV{PGHOST} = $node->host;
+$ENV{PGPORT} = $node->port;
+$node->start;
+
+# Configure server for SSL connections, with password handling.
+configure_test_server_for_ssl($node, $SERVERHOSTADDR, "scram-sha-256",
+							  "pass", "scram-sha-256");
+switch_server_cert($node, 'server-cn-only');
+$ENV{PGPASSWORD} = "pass";
+$common_connstr =
+"user=ssltestuser dbname=trustdb sslmode=require hostaddr=$SERVERHOSTADDR";
+
+# Tests with default channel binding and SASL mechanism names.
+# tls-unique is used here with channel binding.
+test_connect_ok($common_connstr, "");
-- 
2.14.2

