diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index d23df0261c..42263431ed 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1342,10 +1342,11 @@
SASL> is a framework for authentication in connection-oriented
-protocols. At the moment, 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, 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.
@@ -1430,7 +1431,9 @@ the password is in.
-Channel binding> has not been implemented yet.
+Channel binding> is supported in builds with SSL support, and
+uses as mechanism name SCRAM-SHA-256-PLUS> for this purpose as
+defined per IANA.
@@ -1439,14 +1442,18 @@ the password is in.
The server sends an AuthenticationSASL message. It includes a list of
SASL authentication mechanisms that the server can accept.
+ SCRAM-SHA-256> and 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 and if the connection attempt is done without SSL.
The client responds by sending a SASLInitialResponse message, which
- indicates the chosen mechanism, SCRAM-SHA-256>. In the Initial
- Client response field, the message contains the SCRAM
- client-first-message>.
+ indicates the chosen mechanism, SCRAM-SHA-256> or
+ SCRAM-SHA-256-PLUS>. In the Initial Client response field,
+ the message contains the SCRAM client-first-message>.
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 99feb0ce94..a372b08bba 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,10 @@ typedef struct
const char *username; /* username from startup packet */
+ bool ssl_in_use;
+ char *tls_finish_message;
+ int tls_finish_len;
+
int iterations;
char *salt; /* base64-encoded */
uint8 StoredKey[SCRAM_KEY_LEN];
@@ -168,7 +170,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_finish_message,
+ int tls_finish_len)
{
scram_state *state;
bool got_verifier;
@@ -176,6 +182,9 @@ 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_finish_message = tls_finish_message;
+ state->tls_finish_len = tls_finish_len;
/*
* Parse the stored password verifier.
@@ -767,43 +776,97 @@ 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 */
+ /*
+ * Client does not support channel binding, this is authorized
+ * only in builds not supporting SSL. If SSL is supported, the
+ * server cannot support this option either.
+ */
+#ifdef USE_SSL
+ if (state->ssl_in_use)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("client does not support SCRAM channel binding, but server needs it for SSL connections")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("client does not support SCRAM channel binding, but server does")));
+#endif
+ input++;
+ if (*input != ',')
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed SCRAM message (comma expected, got %s)",
+ sanitize_char(*input))));
input++;
break;
case 'y':
- /* Client supports channel binding, but we're not doing it today */
+ /*
+ * Client supports channel binding, but we're not doing it today
+ * in the context of a non-SSL connection. Complain though if
+ * the client is trying to trick the server in not doing channel
+ * binding with a downgrade attack if a SSL connection is
+ * attempted.
+ */
+ if (state->ssl_in_use)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("client support SCRAM channel binding, but server needs it for SSL connections")));
+ input++;
+ if (*input != ',')
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed SCRAM message (comma expected, got %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 supports SCRAM channel binding, but server does not need it for non-SSL connections")));
+
+ /*
+ * Read value provided by client, only tls-unique is supported
+ * for now.
+ */
+ channel_name = read_attr_value(&input, 'p');
+ if (strcmp(channel_name, "tls-unique") != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ (errmsg("unexpected SCRAM channel-binding type"))));
+#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),
(errmsg("malformed SCRAM message (unexpected channel-binding flag %s)",
sanitize_char(*input)))));
}
- if (*input != ',')
- ereport(ERROR,
- (errcode(ERRCODE_PROTOCOL_VIOLATION),
- errmsg("malformed SCRAM message (comma expected, got %s)",
- sanitize_char(*input))));
- input++;
/*
* Forbid optional authzid (authorization identity). We don't support it.
@@ -1023,14 +1086,47 @@ 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, 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.
*/
channel_binding = read_attr_value(&p, 'c');
+#ifdef USE_SSL
+ if (state->ssl_in_use)
+ {
+ char *enc_tls_message;
+ int enc_tls_len;
+
+ enc_tls_message = palloc(pg_b64_enc_len(state->tls_finish_len) + 1);
+ enc_tls_len = pg_b64_encode(state->tls_finish_message,
+ state->tls_finish_len,
+ enc_tls_message);
+ enc_tls_message[enc_tls_len] = '\0';
+
+ /*
+ * Compare the value sent by the client with the TLS finish message
+ * expected by the server.
+ */
+ if (strcmp(channel_binding, enc_tls_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 5b68e3b7a1..89dfa744b1 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -841,8 +841,10 @@ 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;
@@ -861,12 +863,38 @@ 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);
/*
* Initialize the status tracker for message exchanges.
@@ -879,7 +907,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,
+ port->tls_finish,
+ port->tls_finish_len);
/*
* Loop through SASL message exchange. This exchange can consist of
@@ -933,7 +965,8 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
* 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(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 44c84a7869..eff8b300de 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -458,6 +458,7 @@ be_tls_open_server(Port *port)
int err;
int waitfor;
unsigned long ecode;
+ char tls_finish_buf[20];
Assert(!port->ssl);
Assert(!port->peer);
@@ -616,6 +617,25 @@ aloop:
/* set up debugging/info callback */
SSL_CTX_set_info_callback(SSL_context, info_cb);
+ /*
+ * Save the TLS finish message expected to be found, useful for
+ * authentication checks related to channel binding.
+ * SSL_get_peer_finished() does not offer a way to know the exact length
+ * of a TLS finish message beforehand, so attempt first with a fixed-length
+ * buffer, and try again if the message does not fit.
+ */
+ port->tls_finish_len = SSL_get_peer_finished(port->ssl,
+ tls_finish_buf,
+ sizeof(tls_finish_buf));
+ port->tls_finish = MemoryContextAlloc(TopMemoryContext,
+ port->tls_finish_len);
+ if (port->tls_finish_len > sizeof(tls_finish_buf))
+ memcpy(port->tls_finish, tls_finish_buf, port->tls_finish_len);
+ else
+ (void) SSL_get_peer_finished(port->ssl,
+ port->tls_finish,
+ port->tls_finish_len);
+
return 0;
}
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 0669b924cf..6eb39e0678 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -181,6 +181,8 @@ typedef struct Port
bool ssl_in_use;
char *peer_cn;
bool peer_cert_valid;
+ char *tls_finish; /* TLS message expected from client */
+ int tls_finish_len; /* length expected of TLS message */
/*
* OpenSSL structures. (Keep these last so that the locations of other
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index 14b48af12f..fc6fe2431f 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -13,8 +13,9 @@
#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 */
/* Status codes for message exchange */
#define SASL_EXCHANGE_CONTINUE 0
@@ -22,7 +23,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_finish_message,
+ int tls_finish_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 d2e355a8b8..5e0837a2c9 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -44,6 +44,9 @@ typedef struct
/* These are supplied by the user */
const char *username;
char *password;
+ bool ssl_in_use;
+ char *tls_finish_message;
+ int tls_finish_len;
/* We construct these */
uint8 SaltedPassword[SCRAM_KEY_LEN];
@@ -81,7 +84,11 @@ 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,
+ char *tls_finish_message,
+ int tls_finish_len)
{
fe_scram_state *state;
char *prep_password;
@@ -93,6 +100,9 @@ pg_fe_scram_init(const char *username, const char *password)
memset(state, 0, sizeof(fe_scram_state));
state->state = FE_SCRAM_INIT;
state->username = username;
+ state->ssl_in_use = ssl_in_use;
+ state->tls_finish_message = tls_finish_message;
+ state->tls_finish_len = tls_finish_len;
/* Normalize the password with SASLprep, if possible */
rc = pg_saslprep(password, &prep_password);
@@ -297,9 +307,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 +339,55 @@ 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)
- {
- printfPQExpBuffer(errormessage,
- libpq_gettext("out of memory\n"));
- return NULL;
- }
- snprintf(buf, buflen, "n,,n=,r=%s", state->client_nonce);
+ initPQExpBuffer(&buf);
- state->client_first_message_bare = strdup(buf + 3);
+ /*
+ * First build the query field for channel binding. If the client is not
+ * built with SSL support, it cannot support channel binding so it needs
+ * to use "n" to let the server know. If built with SSL support, client
+ * needs to use "y" to let the server know that client has such support
+ * but that it is not using it as a non-SSL connection is requested.
+ * Finally if a SSL connection is done, use p=cb-name, for which only
+ * "tls-unique" is supported now.
+ */
+#ifdef USE_SSL
+ if (state->ssl_in_use)
+ appendPQExpBuffer(&buf, "p=tls-unique");
+ else
+ appendPQExpBuffer(&buf, "y");
+#else
+ appendPQExpBuffer(&buf, "n");
+#endif
+
+ if (PQExpBufferDataBroken(buf))
+ goto oom_error;
+
+ 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)
- {
- free(buf);
- printfPQExpBuffer(errormessage,
- libpq_gettext("out of memory\n"));
- return NULL;
- }
+ goto oom_error;
- return buf;
+ 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 +405,30 @@ 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 (state->ssl_in_use)
+ {
+ appendPQExpBuffer(&buf, "c=");
+ if (!enlargePQExpBuffer(&buf, pg_b64_enc_len(state->tls_finish_len)))
+ goto oom_error;
+ buf.len += pg_b64_encode(state->tls_finish_message,
+ state->tls_finish_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 f4397afc64..ea4bbbae95 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -494,7 +494,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 and SCRAM-SHA-256-PLUS are the only ones
+ * supported at the moment.
*/
selected_mechanism = NULL;
for (;;)
@@ -522,7 +523,8 @@ 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;
@@ -537,10 +539,18 @@ pg_SASL_init(PGconn *conn, int payloadlen)
goto error;
}
- conn->sasl_state = pg_fe_scram_init(conn->pguser, password);
+ conn->sasl_state = pg_fe_scram_init(conn->pguser,
+ password,
+ conn->ssl_in_use,
+ conn->tls_finish,
+ conn->tls_finish_len);
if (!conn->sasl_state)
goto oom_error;
- selected_mechanism = SCRAM_SHA256_NAME;
+
+ if (strcmp(mechanism_buf.data, SCRAM_SHA256_NAME) == 0)
+ selected_mechanism = SCRAM_SHA256_NAME;
+ else
+ selected_mechanism = SCRAM_SHA256_PLUS_NAME;
}
}
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 9f4c2a50d8..2ee9c6c48c 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -23,7 +23,9 @@ 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, char *tls_finish_message,
+ int tls_finish_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 a7c3d7af64..b701b18c1c 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -1297,6 +1297,7 @@ static PostgresPollingStatusType
open_client_SSL(PGconn *conn)
{
int r;
+ char tls_finish_buf[20];
ERR_clear_error();
r = SSL_connect(conn->ssl);
@@ -1376,6 +1377,24 @@ open_client_SSL(PGconn *conn)
return PGRES_POLLING_FAILED;
}
+ /*
+ * Save the TLS finish message sent to the server, useful for
+ * authentication checks related to channel binding. SSL_get_finished()
+ * does not offer a way to know the exact length of a TLS finish message
+ * beforehand, so attempt first with a fixed-length buffer, and try again
+ * if the message does not fit.
+ */
+ conn->tls_finish_len = SSL_get_finished(conn->ssl,
+ tls_finish_buf,
+ sizeof(tls_finish_buf));
+ conn->tls_finish = malloc(conn->tls_finish_len);
+ if (conn->tls_finish_len > sizeof(tls_finish_buf))
+ memcpy(conn->tls_finish, tls_finish_buf, conn->tls_finish_len);
+ else
+ (void) SSL_get_finished(conn->ssl,
+ conn->tls_finish,
+ conn->tls_finish_len);
+
/* SSL handshake is complete */
return PGRES_POLLING_OK;
}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 335568b790..ab2b9befbf 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -454,11 +454,15 @@ struct pg_conn
/* Assorted state for SASL, SSL, GSS, etc */
void *sasl_state;
+ /* SSL structures */
+ bool ssl_in_use;
+ char *tls_finish; /* TLS finish message sent */
+ int tls_finish_len; /* length of TLS message sent */
+
#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 */