Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Paquier2021-07-07 01:55:15 +0000
committerMichael Paquier2021-07-07 01:55:15 +0000
commit9fd85570d179f10f93344d722005f7086b3c31ca (patch)
treea678636aee49619b4e69f45a67df6f9498d59104 /src/backend
parent955b3e0f9269639fb916cee3dea37aee50b82df0 (diff)
Refactor SASL code with a generic interface for its mechanisms
The code of SCRAM and SASL have been tightly linked together since SCRAM exists in the core code, making hard to apprehend the addition of new SASL mechanisms, but these are by design different facilities, with SCRAM being an option for SASL. This refactors the code related to both so as the backend and the frontend use a set of callbacks for SASL mechanisms, documenting while on it what is expected by anybody adding a new SASL mechanism. The separation between both layers is neat, using two sets of callbacks for the frontend and the backend to mark the frontier between both facilities. The shape of the callbacks is now directly inspired from the routines used by SCRAM, so the code change is straight-forward, and the SASL code is moved into its own set of files. These will likely change depending on how and if new SASL mechanisms get added in the future. Author: Jacob Champion Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/3d2a6f5d50e741117d6baf83eb67ebf1a8a35a11.camel@vmware.com
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/libpq/Makefile1
-rw-r--r--src/backend/libpq/auth-sasl.c195
-rw-r--r--src/backend/libpq/auth-scram.c51
-rw-r--r--src/backend/libpq/auth.c167
4 files changed, 232 insertions, 182 deletions
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8d1d16b0fc5..6d385fd6a45 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
# be-fsstubs is here for historical reasons, probably belongs elsewhere
OBJS = \
+ auth-sasl.o \
auth-scram.o \
auth.o \
be-fsstubs.o \
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
new file mode 100644
index 00000000000..3e4f763b609
--- /dev/null
+++ b/src/backend/libpq/auth-sasl.c
@@ -0,0 +1,195 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-sasl.c
+ * Routines to handle authentication via SASL
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/auth-sasl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "libpq/auth.h"
+#include "libpq/libpq.h"
+#include "libpq/pqformat.h"
+#include "libpq/sasl.h"
+
+/*
+ * Maximum accepted size of SASL messages.
+ *
+ * The messages that the server or libpq generate are much smaller than this,
+ * but have some headroom.
+ */
+#define PG_MAX_SASL_MESSAGE_LENGTH 1024
+
+/*
+ * Perform a SASL exchange with a libpq client, using a specific mechanism
+ * implementation.
+ *
+ * shadow_pass is an optional pointer to the stored secret of the role
+ * authenticated, from pg_authid.rolpassword. For mechanisms that use
+ * shadowed passwords, a NULL pointer here means that an entry could not
+ * be found for the role (or the user does not exist), and the mechanism
+ * should fail the authentication exchange.
+ *
+ * Mechanisms must take care not to reveal to the client that a user entry
+ * does not exist; ideally, the external failure mode is identical to that
+ * of an incorrect password. Mechanisms may instead use the logdetail
+ * output parameter to internally differentiate between failure cases and
+ * assist debugging by the server admin.
+ *
+ * A mechanism is not required to utilize a shadow entry, or even a password
+ * system at all; for these cases, shadow_pass may be ignored and the caller
+ * should just pass NULL.
+ */
+int
+CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
+ char **logdetail)
+{
+ StringInfoData sasl_mechs;
+ int mtype;
+ StringInfoData buf;
+ void *opaq = NULL;
+ char *output = NULL;
+ int outputlen = 0;
+ const char *input;
+ int inputlen;
+ int result;
+ bool initial;
+
+ /*
+ * Send the SASL authentication request to user. It includes the list of
+ * authentication mechanisms that are supported.
+ */
+ initStringInfo(&sasl_mechs);
+
+ mech->get_mechanisms(port, &sasl_mechs);
+ /* Put another '\0' to mark that list is finished. */
+ appendStringInfoChar(&sasl_mechs, '\0');
+
+ sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
+ pfree(sasl_mechs.data);
+
+ /*
+ * Loop through SASL message exchange. This exchange can consist of
+ * multiple messages sent in both directions. First message is always
+ * from the client. All messages from client to server are password
+ * packets (type 'p').
+ */
+ initial = true;
+ do
+ {
+ pq_startmsgread();
+ mtype = pq_getbyte();
+ if (mtype != 'p')
+ {
+ /* Only log error if client didn't disconnect. */
+ if (mtype != EOF)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("expected SASL response, got message type %d",
+ mtype)));
+ }
+ else
+ return STATUS_EOF;
+ }
+
+ /* Get the actual SASL message */
+ initStringInfo(&buf);
+ if (pq_getmessage(&buf, PG_MAX_SASL_MESSAGE_LENGTH))
+ {
+ /* EOF - pq_getmessage already logged error */
+ pfree(buf.data);
+ return STATUS_ERROR;
+ }
+
+ elog(DEBUG4, "processing received SASL response of length %d", buf.len);
+
+ /*
+ * The first SASLInitialResponse message is different from the others.
+ * It indicates which SASL mechanism the client selected, and contains
+ * an optional Initial Client Response payload. The subsequent
+ * SASLResponse messages contain just the SASL payload.
+ */
+ if (initial)
+ {
+ const char *selected_mech;
+
+ selected_mech = pq_getmsgrawstring(&buf);
+
+ /*
+ * Initialize the status tracker for message exchanges.
+ *
+ * If the user doesn't exist, or doesn't have a valid password, or
+ * it's expired, we still go through the motions of SASL
+ * authentication, but tell the authentication method that the
+ * authentication is "doomed". That is, it's going to fail, no
+ * matter what.
+ *
+ * This is because we don't want to reveal to an attacker what
+ * usernames are valid, nor which users have a valid password.
+ */
+ opaq = mech->init(port, selected_mech, shadow_pass);
+
+ inputlen = pq_getmsgint(&buf, 4);
+ if (inputlen == -1)
+ input = NULL;
+ else
+ input = pq_getmsgbytes(&buf, inputlen);
+
+ initial = false;
+ }
+ else
+ {
+ inputlen = buf.len;
+ input = pq_getmsgbytes(&buf, buf.len);
+ }
+ pq_getmsgend(&buf);
+
+ /*
+ * The StringInfo guarantees that there's a \0 byte after the
+ * response.
+ */
+ Assert(input == NULL || input[inputlen] == '\0');
+
+ /*
+ * Hand the incoming message to the mechanism implementation.
+ */
+ result = mech->exchange(opaq, input, inputlen,
+ &output, &outputlen,
+ logdetail);
+
+ /* input buffer no longer used */
+ pfree(buf.data);
+
+ if (output)
+ {
+ /*
+ * Negotiation generated data to be sent to the client.
+ */
+ elog(DEBUG4, "sending SASL challenge of length %u", outputlen);
+
+ if (result == PG_SASL_EXCHANGE_SUCCESS)
+ sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
+ else
+ sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
+
+ pfree(output);
+ }
+ } while (result == PG_SASL_EXCHANGE_CONTINUE);
+
+ /* Oops, Something bad happened */
+ if (result != PG_SASL_EXCHANGE_SUCCESS)
+ {
+ return STATUS_ERROR;
+ }
+
+ return STATUS_OK;
+}
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index f9e1026a12c..9df8f178376 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -101,11 +101,25 @@
#include "common/sha2.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
+#include "libpq/sasl.h"
#include "libpq/scram.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
+static void scram_get_mechanisms(Port *port, StringInfo buf);
+static void *scram_init(Port *port, const char *selected_mech,
+ const char *shadow_pass);
+static int scram_exchange(void *opaq, const char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail);
+
+/* Mechanism declaration */
+const pg_be_sasl_mech pg_be_scram_mech = {
+ scram_get_mechanisms,
+ scram_init,
+ scram_exchange
+};
+
/*
* Status data for a SCRAM authentication exchange. This should be kept
* internal to this file.
@@ -170,16 +184,14 @@ static char *sanitize_str(const char *s);
static char *scram_mock_salt(const char *username);
/*
- * pg_be_scram_get_mechanisms
- *
* Get a list of SASL mechanisms that this module supports.
*
* For the convenience of building the FE/BE packet that lists the
* mechanisms, the names are appended to the given StringInfo buffer,
* separated by '\0' bytes.
*/
-void
-pg_be_scram_get_mechanisms(Port *port, StringInfo buf)
+static void
+scram_get_mechanisms(Port *port, StringInfo buf)
{
/*
* Advertise the mechanisms in decreasing order of importance. So the
@@ -199,15 +211,13 @@ pg_be_scram_get_mechanisms(Port *port, StringInfo buf)
}
/*
- * pg_be_scram_init
- *
* Initialize a new SCRAM authentication exchange status tracker. This
* needs to be called before doing any exchange. It will be filled later
* after the beginning of the exchange with authentication information.
*
* 'selected_mech' identifies the SASL mechanism that the client selected.
* It should be one of the mechanisms that we support, as returned by
- * pg_be_scram_get_mechanisms().
+ * scram_get_mechanisms().
*
* 'shadow_pass' is the role's stored secret, from pg_authid.rolpassword.
* The username was provided by the client in the startup message, and is
@@ -215,10 +225,8 @@ pg_be_scram_get_mechanisms(Port *port, StringInfo buf)
* an authentication exchange, but it will fail, as if an incorrect password
* was given.
*/
-void *
-pg_be_scram_init(Port *port,
- const char *selected_mech,
- const char *shadow_pass)
+static void *
+scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
{
scram_state *state;
bool got_secret;
@@ -325,9 +333,9 @@ pg_be_scram_init(Port *port,
* string at *logdetail that will be sent to the postmaster log (but not
* the client).
*/
-int
-pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
- char **output, int *outputlen, char **logdetail)
+static int
+scram_exchange(void *opaq, const char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail)
{
scram_state *state = (scram_state *) opaq;
int result;
@@ -346,7 +354,7 @@ pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
*output = pstrdup("");
*outputlen = 0;
- return SASL_EXCHANGE_CONTINUE;
+ return PG_SASL_EXCHANGE_CONTINUE;
}
/*
@@ -379,7 +387,7 @@ pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
*output = build_server_first_message(state);
state->state = SCRAM_AUTH_SALT_SENT;
- result = SASL_EXCHANGE_CONTINUE;
+ result = PG_SASL_EXCHANGE_CONTINUE;
break;
case SCRAM_AUTH_SALT_SENT:
@@ -408,7 +416,8 @@ pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
* erroring out in an application-specific way. We choose to do
* the latter, so that the error message for invalid password is
* the same for all authentication methods. The caller will call
- * ereport(), when we return SASL_EXCHANGE_FAILURE with no output.
+ * ereport(), when we return PG_SASL_EXCHANGE_FAILURE with no
+ * output.
*
* NB: the order of these checks is intentional. We calculate the
* client proof even in a mock authentication, even though it's
@@ -417,7 +426,7 @@ pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
*/
if (!verify_client_proof(state) || state->doomed)
{
- result = SASL_EXCHANGE_FAILURE;
+ result = PG_SASL_EXCHANGE_FAILURE;
break;
}
@@ -425,16 +434,16 @@ pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
*output = build_server_final_message(state);
/* Success! */
- result = SASL_EXCHANGE_SUCCESS;
+ result = PG_SASL_EXCHANGE_SUCCESS;
state->state = SCRAM_AUTH_FINISHED;
break;
default:
elog(ERROR, "invalid SCRAM exchange state");
- result = SASL_EXCHANGE_FAILURE;
+ result = PG_SASL_EXCHANGE_FAILURE;
}
- if (result == SASL_EXCHANGE_FAILURE && state->logdetail && logdetail)
+ if (result == PG_SASL_EXCHANGE_FAILURE && state->logdetail && logdetail)
*logdetail = state->logdetail;
if (*output)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 967b5ef73cc..8cc23ef7fb4 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -26,11 +26,11 @@
#include "commands/user.h"
#include "common/ip.h"
#include "common/md5.h"
-#include "common/scram-common.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/sasl.h"
#include "libpq/scram.h"
#include "miscadmin.h"
#include "port/pg_bswap.h"
@@ -45,8 +45,6 @@
* Global authentication functions
*----------------------------------------------------------------
*/
-static void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata,
- int extralen);
static void auth_failed(Port *port, int status, char *logdetail);
static char *recv_password_packet(Port *port);
static void set_authn_id(Port *port, const char *id);
@@ -60,7 +58,6 @@ static int CheckPasswordAuth(Port *port, char **logdetail);
static int CheckPWChallengeAuth(Port *port, char **logdetail);
static int CheckMD5Auth(Port *port, char *shadow_pass, char **logdetail);
-static int CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail);
/*----------------------------------------------------------------
@@ -224,14 +221,6 @@ static int PerformRadiusTransaction(const char *server, const char *secret, cons
*/
#define PG_MAX_AUTH_TOKEN_LENGTH 65535
-/*
- * Maximum accepted size of SASL messages.
- *
- * The messages that the server or libpq generate are much smaller than this,
- * but have some headroom.
- */
-#define PG_MAX_SASL_MESSAGE_LENGTH 1024
-
/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
@@ -668,7 +657,7 @@ ClientAuthentication(Port *port)
/*
* Send an authentication request packet to the frontend.
*/
-static void
+void
sendAuthRequest(Port *port, AuthRequest areq, const char *extradata, int extralen)
{
StringInfoData buf;
@@ -848,12 +837,14 @@ CheckPWChallengeAuth(Port *port, char **logdetail)
* SCRAM secret, we must do SCRAM authentication.
*
* If MD5 authentication is not allowed, always use SCRAM. If the user
- * had an MD5 password, CheckSCRAMAuth() will fail.
+ * had an MD5 password, CheckSASLAuth() with the SCRAM mechanism will
+ * fail.
*/
if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5)
auth_result = CheckMD5Auth(port, shadow_pass, logdetail);
else
- auth_result = CheckSCRAMAuth(port, shadow_pass, logdetail);
+ auth_result = CheckSASLAuth(&pg_be_scram_mech, port, shadow_pass,
+ logdetail);
if (shadow_pass)
pfree(shadow_pass);
@@ -911,152 +902,6 @@ CheckMD5Auth(Port *port, char *shadow_pass, char **logdetail)
return result;
}
-static int
-CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
-{
- StringInfoData sasl_mechs;
- int mtype;
- StringInfoData buf;
- void *scram_opaq = NULL;
- char *output = NULL;
- int outputlen = 0;
- const char *input;
- int inputlen;
- int result;
- bool initial;
-
- /*
- * Send the SASL authentication request to user. It includes the list of
- * authentication mechanisms that are supported.
- */
- initStringInfo(&sasl_mechs);
-
- pg_be_scram_get_mechanisms(port, &sasl_mechs);
- /* Put another '\0' to mark that list is finished. */
- appendStringInfoChar(&sasl_mechs, '\0');
-
- sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
- pfree(sasl_mechs.data);
-
- /*
- * Loop through SASL message exchange. This exchange can consist of
- * multiple messages sent in both directions. First message is always
- * from the client. All messages from client to server are password
- * packets (type 'p').
- */
- initial = true;
- do
- {
- pq_startmsgread();
- mtype = pq_getbyte();
- if (mtype != 'p')
- {
- /* Only log error if client didn't disconnect. */
- if (mtype != EOF)
- {
- ereport(ERROR,
- (errcode(ERRCODE_PROTOCOL_VIOLATION),
- errmsg("expected SASL response, got message type %d",
- mtype)));
- }
- else
- return STATUS_EOF;
- }
-
- /* Get the actual SASL message */
- initStringInfo(&buf);
- if (pq_getmessage(&buf, PG_MAX_SASL_MESSAGE_LENGTH))
- {
- /* EOF - pq_getmessage already logged error */
- pfree(buf.data);
- return STATUS_ERROR;
- }
-
- elog(DEBUG4, "processing received SASL response of length %d", buf.len);
-
- /*
- * The first SASLInitialResponse message is different from the others.
- * It indicates which SASL mechanism the client selected, and contains
- * an optional Initial Client Response payload. The subsequent
- * SASLResponse messages contain just the SASL payload.
- */
- if (initial)
- {
- const char *selected_mech;
-
- selected_mech = pq_getmsgrawstring(&buf);
-
- /*
- * Initialize the status tracker for message exchanges.
- *
- * If the user doesn't exist, or doesn't have a valid password, or
- * it's expired, we still go through the motions of SASL
- * authentication, but tell the authentication method that the
- * authentication is "doomed". That is, it's going to fail, no
- * matter what.
- *
- * 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, selected_mech, shadow_pass);
-
- inputlen = pq_getmsgint(&buf, 4);
- if (inputlen == -1)
- input = NULL;
- else
- input = pq_getmsgbytes(&buf, inputlen);
-
- initial = false;
- }
- else
- {
- inputlen = buf.len;
- input = pq_getmsgbytes(&buf, buf.len);
- }
- pq_getmsgend(&buf);
-
- /*
- * The StringInfo guarantees that there's a \0 byte after the
- * response.
- */
- Assert(input == NULL || input[inputlen] == '\0');
-
- /*
- * we pass 'logdetail' as NULL when doing a mock authentication,
- * because we should already have a better error message in that case
- */
- result = pg_be_scram_exchange(scram_opaq, input, inputlen,
- &output, &outputlen,
- logdetail);
-
- /* input buffer no longer used */
- pfree(buf.data);
-
- if (output)
- {
- /*
- * Negotiation generated data to be sent to the client.
- */
- elog(DEBUG4, "sending SASL challenge of length %u", outputlen);
-
- if (result == SASL_EXCHANGE_SUCCESS)
- sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
- else
- sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
-
- pfree(output);
- }
- } while (result == SASL_EXCHANGE_CONTINUE);
-
- /* Oops, Something bad happened */
- if (result != SASL_EXCHANGE_SUCCESS)
- {
- return STATUS_ERROR;
- }
-
- return STATUS_OK;
-}
-
/*----------------------------------------------------------------
* GSSAPI authentication system