diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/libpq/Makefile | 1 | ||||
-rw-r--r-- | src/backend/libpq/auth-sasl.c | 195 | ||||
-rw-r--r-- | src/backend/libpq/auth-scram.c | 51 | ||||
-rw-r--r-- | src/backend/libpq/auth.c | 167 |
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 |