1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-15 19:21:59 +03:00

Improve the SASL authentication protocol.

This contains some protocol changes to SASL authentiation (which is new
in v10):

* For future-proofing, in the AuthenticationSASL message that begins SASL
  authentication, provide a list of SASL mechanisms that the server
  supports, for the client to choose from. Currently, it's always just
  SCRAM-SHA-256.

* Add a separate authentication message type for the final server->client
  SASL message, which the client doesn't need to respond to. This makes
  it unambiguous whether the client is supposed to send a response or not.
  The SASL mechanism should know that anyway, but better to be explicit.

Also, in the server, support clients that don't send an Initial Client
response in the first SASLInitialResponse message. The server is supposed
to first send an empty request in that case, to which the client will
respond with the data that usually comes in the Initial Client Response.
libpq uses the Initial Client Response field and doesn't need this, and I
would assume any other sensible implementation to use Initial Client
Response, too, but let's follow the SASL spec.

Improve the documentation on SASL authentication in protocol. Add a
section describing the SASL message flow, and some details on our
SCRAM-SHA-256 implementation.

Document the different kinds of PasswordMessages that the frontend sends
in different phases of SASL authentication, as well as GSS/SSPI
authentication as separate message formats. Even though they're all 'p'
messages, and the exact format depends on the context, describing them as
separate message formats makes the documentation more clear.

Reviewed by Michael Paquier and Álvaro Hernández Tortosa.

Discussion: https://www.postgresql.org/message-id/CAB7nPqS-aFg0iM3AQOJwKDv_0WkAedRjs1W2X8EixSz+sKBXCQ@mail.gmail.com
This commit is contained in:
Heikki Linnakangas
2017-04-13 19:34:16 +03:00
parent 61bf96cab0
commit 4f3b87ab78
5 changed files with 593 additions and 93 deletions

View File

@ -475,88 +475,129 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadlen)
static int
pg_SASL_init(PGconn *conn, int payloadlen)
{
char auth_mechanism[21];
char *initialresponse;
char *initialresponse = NULL;
int initialresponselen;
bool done;
bool success;
int res;
const char *selected_mechanism;
PQExpBufferData mechanism_buf;
initPQExpBuffer(&mechanism_buf);
if (conn->sasl_state)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("duplicate SASL authentication request\n"));
goto error;
}
/*
* Read the authentication mechanism the server told us to use.
* 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.)
*/
if (payloadlen > sizeof(auth_mechanism) - 1)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SASL authentication mechanism not supported\n"));
if (pqGetnchar(auth_mechanism, payloadlen, conn))
selected_mechanism = NULL;
for (;;)
{
printfPQExpBuffer(&conn->errorMessage,
"fe_sendauth: invalid authentication request from server: invalid authentication mechanism\n");
return STATUS_ERROR;
}
auth_mechanism[payloadlen] = '\0';
/*
* Check the authentication mechanism (only SCRAM-SHA-256 is supported at
* the moment.)
*/
if (strcmp(auth_mechanism, SCRAM_SHA256_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')
if (pqGets(&mechanism_buf, conn))
{
printfPQExpBuffer(&conn->errorMessage,
PQnoPasswordSupplied);
return STATUS_ERROR;
"fe_sendauth: invalid authentication request from server: invalid list of authentication mechanisms\n");
goto error;
}
if (PQExpBufferDataBroken(mechanism_buf))
goto oom_error;
conn->sasl_state = pg_fe_scram_init(conn->pguser, password);
if (!conn->sasl_state)
/* An empty string indicates end of list */
if (mechanism_buf.data[0] == '\0')
break;
/*
* If we have already selected a mechanism, just skip through the rest
* of the list.
*/
if (selected_mechanism)
continue;
/*
* Do we support this mechanism?
*/
if (strcmp(mechanism_buf.data, SCRAM_SHA256_NAME) == 0)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n"));
return STATUS_ERROR;
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;
}
}
else
if (!selected_mechanism)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SASL authentication mechanism %s not supported\n"),
auth_mechanism);
return STATUS_ERROR;
libpq_gettext("none of the server's SASL authentication mechanisms are supported\n"));
goto error;
}
/* Send the initial client response */
/* Get the mechanism-specific Initial Client Response, if any */
pg_fe_scram_exchange(conn->sasl_state,
NULL, -1,
&initialresponse, &initialresponselen,
&done, &success, &conn->errorMessage);
if (done && !success)
goto error;
/*
* Build a SASLInitialResponse message, and send it.
*/
if (pqPutMsgStart('p', true, conn))
goto error;
if (pqPuts(selected_mechanism, conn))
goto error;
if (initialresponse)
{
res = pqPacketSend(conn, 'p', initialresponse, initialresponselen);
if (pqPutInt(initialresponselen, 4, conn))
goto error;
if (pqPutnchar(initialresponse, initialresponselen, conn))
goto error;
}
if (pqPutMsgEnd(conn))
goto error;
if (pqFlush(conn))
goto error;
termPQExpBuffer(&mechanism_buf);
if (initialresponse)
free(initialresponse);
if (res != STATUS_OK)
return STATUS_ERROR;
}
if (done && !success)
{
/* Use error message, if set already */
if (conn->errorMessage.len == 0)
printfPQExpBuffer(&conn->errorMessage,
"fe_sendauth: error in SASL authentication\n");
return STATUS_ERROR;
}
return STATUS_OK;
error:
termPQExpBuffer(&mechanism_buf);
if (initialresponse)
free(initialresponse);
return STATUS_ERROR;
oom_error:
termPQExpBuffer(&mechanism_buf);
if (initialresponse)
free(initialresponse);
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n"));
return STATUS_ERROR;
}
/*
@ -565,7 +606,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
* the protocol.
*/
static int
pg_SASL_continue(PGconn *conn, int payloadlen)
pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
{
char *output;
int outputlen;
@ -598,9 +639,20 @@ pg_SASL_continue(PGconn *conn, int payloadlen)
&done, &success, &conn->errorMessage);
free(challenge); /* don't need the input anymore */
/* Send the SASL response to the server, if any. */
if (final && !done)
{
if (outputlen != 0)
free(output);
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("AuthenticationSASLFinal received from server, but SASL authentication was not completed\n"));
return STATUS_ERROR;
}
if (outputlen != 0)
{
/*
* Send the SASL response to the server.
*/
res = pqPacketSend(conn, 'p', output, outputlen);
free(output);
@ -918,13 +970,15 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
break;
case AUTH_REQ_SASL_CONT:
case AUTH_REQ_SASL_FIN:
if (conn->sasl_state == NULL)
{
printfPQExpBuffer(&conn->errorMessage,
"fe_sendauth: invalid authentication request from server: AUTH_REQ_SASL_CONT without AUTH_REQ_SASL\n");
return STATUS_ERROR;
}
if (pg_SASL_continue(conn, payloadlen) != STATUS_OK)
if (pg_SASL_continue(conn, payloadlen,
(areq == AUTH_REQ_SASL_FIN)) != STATUS_OK)
{
/* Use error message, if set already */
if (conn->errorMessage.len == 0)