1
0
mirror of https://github.com/postgres/postgres.git synced 2025-08-25 20:23:07 +03:00

Add libpq parameter 'channel_binding'.

Allow clients to require channel binding to enhance security against
untrusted servers.

Author: Jeff Davis
Reviewed-by: Michael Paquier
Discussion: https://postgr.es/m/227015d8417f2b4fef03f8966dbfa5cbcc4f44da.camel%40j-davis.com
This commit is contained in:
Jeff Davis
2019-09-23 13:45:23 -07:00
parent 13cd97e6c8
commit d6e612f837
9 changed files with 233 additions and 20 deletions

View File

@@ -119,6 +119,35 @@ pg_fe_scram_init(PGconn *conn,
return state;
}
/*
* Return true if channel binding was employed and the SCRAM exchange
* completed. This should be used after a successful exchange to determine
* whether the server authenticated itself to the client.
*
* Note that the caller must also ensure that the exchange was actually
* successful.
*/
bool
pg_fe_scram_channel_bound(void *opaq)
{
fe_scram_state *state = (fe_scram_state *) opaq;
/* no SCRAM exchange done */
if (state == NULL)
return false;
/* SCRAM exchange not completed */
if (state->state != FE_SCRAM_FINISHED)
return false;
/* channel binding mechanism not used */
if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0)
return false;
/* all clear! */
return true;
}
/*
* Free SCRAM exchange status
*/
@@ -225,9 +254,7 @@ pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
/*
* Verify server signature, to make sure we're talking to the
* genuine server. XXX: A fake server could simply not require
* authentication, though. There is currently no option in libpq
* to reject a connection, if SCRAM authentication did not happen.
* genuine server.
*/
if (verify_server_signature(state))
*success = true;
@@ -358,7 +385,8 @@ build_client_first_message(fe_scram_state *state)
appendPQExpBufferStr(&buf, "p=tls-server-end-point");
}
#ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
else if (conn->ssl_in_use)
else if (conn->channel_binding[0] != 'd' && /* disable */
conn->ssl_in_use)
{
/*
* Client supports channel binding, but thinks the server does not.
@@ -369,7 +397,7 @@ build_client_first_message(fe_scram_state *state)
else
{
/*
* Client does not support channel binding.
* Client does not support channel binding, or has disabled it.
*/
appendPQExpBufferChar(&buf, 'n');
}
@@ -498,7 +526,8 @@ build_client_final_message(fe_scram_state *state)
#endif /* HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH */
}
#ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
else if (conn->ssl_in_use)
else if (conn->channel_binding[0] != 'd' && /* disable */
conn->ssl_in_use)
appendPQExpBufferStr(&buf, "c=eSws"); /* base64 of "y,," */
#endif
else

View File

@@ -423,6 +423,14 @@ pg_SASL_init(PGconn *conn, int payloadlen)
initPQExpBuffer(&mechanism_buf);
if (conn->channel_binding[0] == 'r' && /* require */
!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,
@@ -454,10 +462,10 @@ pg_SASL_init(PGconn *conn, int payloadlen)
/*
* Select the mechanism to use. Pick SCRAM-SHA-256-PLUS over anything
* else if a channel binding type is set and if the client supports
* it. Pick SCRAM-SHA-256 if nothing else has already been picked. If
* we add more mechanisms, a more refined priority mechanism might
* become necessary.
* else if a channel binding type is set and if the client supports it
* (and did not set channel_binding=disable). Pick SCRAM-SHA-256 if
* nothing else has already been picked. If we add more mechanisms, a
* more refined priority mechanism might become necessary.
*/
if (strcmp(mechanism_buf.data, SCRAM_SHA_256_PLUS_NAME) == 0)
{
@@ -466,10 +474,11 @@ pg_SASL_init(PGconn *conn, int payloadlen)
/*
* The server has offered SCRAM-SHA-256-PLUS, which is only
* supported by the client if a hash of the peer certificate
* can be created.
* can be created, and if channel_binding is not disabled.
*/
#ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
if (conn->channel_binding[0] != 'd') /* disable */
selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
#endif
}
else
@@ -493,6 +502,14 @@ pg_SASL_init(PGconn *conn, int payloadlen)
selected_mechanism = SCRAM_SHA_256_NAME;
}
if (conn->channel_binding[0] == 'r' && /* require */
strcmp(selected_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("channel binding is required, but server did not offer an authentication method that supports channel binding\n"));
goto error;
}
if (!selected_mechanism)
{
printfPQExpBuffer(&conn->errorMessage,
@@ -774,6 +791,50 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
return ret;
}
/*
* Verify that the authentication request is expected, given the connection
* parameters. This is especially important when the client wishes to
* authenticate the server before any sensitive information is exchanged.
*/
static bool
check_expected_areq(AuthRequest areq, PGconn *conn)
{
bool result = true;
/*
* When channel_binding=require, we must protect against two cases: (1) we
* must not respond to non-SASL authentication requests, which might leak
* information such as the client's password; and (2) even if we receive
* AUTH_REQ_OK, we still must ensure that channel binding has happened in
* order to authenticate the server.
*/
if (conn->channel_binding[0] == 'r' /* require */ )
{
switch (areq)
{
case AUTH_REQ_SASL:
case AUTH_REQ_SASL_CONT:
case AUTH_REQ_SASL_FIN:
break;
case AUTH_REQ_OK:
if (!pg_fe_scram_channel_bound(conn->sasl_state))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("Channel binding required, but server authenticated client without channel binding\n"));
result = false;
}
break;
default:
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("Channel binding required but not supported by server's authentication request\n"));
result = false;
break;
}
}
return result;
}
/*
* pg_fe_sendauth
* client demux routine for processing an authentication request
@@ -788,6 +849,9 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
int
pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
{
if (!check_expected_areq(areq, conn))
return STATUS_ERROR;
switch (areq)
{
case AUTH_REQ_OK:

View File

@@ -26,6 +26,7 @@ extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
extern void *pg_fe_scram_init(PGconn *conn,
const char *password,
const char *sasl_mechanism);
extern bool pg_fe_scram_channel_bound(void *opaq);
extern void pg_fe_scram_free(void *opaq);
extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
char **output, int *outputlen,

View File

@@ -124,6 +124,11 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
#define DefaultTty ""
#define DefaultOption ""
#define DefaultAuthtype ""
#ifdef USE_SSL
#define DefaultChannelBinding "prefer"
#else
#define DefaultChannelBinding "disable"
#endif
#define DefaultTargetSessionAttrs "any"
#ifdef USE_SSL
#define DefaultSSLMode "prefer"
@@ -211,6 +216,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)},
@@ -1197,6 +1206,29 @@ connectOptions2(PGconn *conn)
}
}
/*
* validate channel_binding option
*/
if (conn->channel_binding)
{
if (strcmp(conn->channel_binding, "disable") != 0
&& 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
*/
@@ -3485,10 +3517,11 @@ keep_going: /* We will come back to here until there is
case CONNECTION_SETENV:
{
/*
* Do post-connection housekeeping (only needed in protocol 2.0).
* Do post-connection housekeeping (only needed in protocol
* 2.0).
*
* We pretend that the connection is OK for the duration of these
* queries.
* We pretend that the connection is OK for the duration of
* these queries.
*/
conn->status = CONNECTION_OK;
@@ -3905,6 +3938,8 @@ freePGconn(PGconn *conn)
}
if (conn->pgpassfile)
free(conn->pgpassfile);
if (conn->channel_binding)
free(conn->channel_binding);
if (conn->keepalives)
free(conn->keepalives);
if (conn->keepalives_idle)

View File

@@ -347,6 +347,8 @@ 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,disable) */
char *keepalives; /* use TCP keepalives? */
char *keepalives_idle; /* time between TCP keepalives */
char *keepalives_interval; /* time between TCP keepalive