diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/libpq/auth-scram.c | 104 | ||||
-rw-r--r-- | src/backend/libpq/auth.c | 179 | ||||
-rw-r--r-- | src/backend/libpq/crypt.c | 44 | ||||
-rw-r--r-- | src/backend/libpq/hba.c | 2 | ||||
-rw-r--r-- | src/include/libpq/crypt.h | 3 | ||||
-rw-r--r-- | src/include/libpq/hba.h | 2 | ||||
-rw-r--r-- | src/include/libpq/scram.h | 2 | ||||
-rw-r--r-- | src/test/authentication/t/001_password.pl | 6 |
8 files changed, 196 insertions, 146 deletions
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c index db15a2fac6c..bcc8d03ef59 100644 --- a/src/backend/libpq/auth-scram.c +++ b/src/backend/libpq/auth-scram.c @@ -130,79 +130,91 @@ static char *scram_MockSalt(const char *username); * after the beginning of the exchange with verifier data. * * 'username' is the provided by the client. 'shadow_pass' is the role's - * password verifier, from pg_authid.rolpassword. If 'doomed' is true, the - * authentication must fail, as if an incorrect password was given. - * 'shadow_pass' may be NULL, when 'doomed' is set. + * password verifier, from pg_authid.rolpassword. If 'shadow_pass' is NULL, we + * still perform an authentication exchange, but it will fail, as if an + * incorrect password was given. */ void * -pg_be_scram_init(const char *username, const char *shadow_pass, bool doomed) +pg_be_scram_init(const char *username, const char *shadow_pass) { scram_state *state; - int password_type; + bool got_verifier; state = (scram_state *) palloc0(sizeof(scram_state)); state->state = SCRAM_AUTH_INIT; state->username = username; /* - * Perform sanity checks on the provided password after catalog lookup. - * The authentication is bound to fail if the lookup itself failed or if - * the password stored is MD5-encrypted. Authentication is possible for - * users with a valid plain password though. + * Parse the stored password verifier. */ + if (shadow_pass) + { + int password_type = get_password_type(shadow_pass); - if (shadow_pass == NULL || doomed) - password_type = -1; - else - password_type = get_password_type(shadow_pass); + if (password_type == PASSWORD_TYPE_SCRAM) + { + if (parse_scram_verifier(shadow_pass, &state->salt, &state->iterations, + state->StoredKey, state->ServerKey)) + got_verifier = true; + else + { + /* + * The password looked like a SCRAM verifier, but could not be + * parsed. + */ + elog(LOG, "invalid SCRAM verifier for user \"%s\"", username); + got_verifier = false; + } + } + else if (password_type == PASSWORD_TYPE_PLAINTEXT) + { + /* + * The stored password is in plain format. Generate a fresh SCRAM + * verifier from it, and proceed with that. + */ + char *verifier; - if (password_type == PASSWORD_TYPE_SCRAM) - { - if (!parse_scram_verifier(shadow_pass, &state->salt, &state->iterations, - state->StoredKey, state->ServerKey)) + verifier = scram_build_verifier(username, shadow_pass, 0); + + (void) parse_scram_verifier(verifier, &state->salt, &state->iterations, + state->StoredKey, state->ServerKey); + pfree(verifier); + + got_verifier = true; + } + else { /* - * The password looked like a SCRAM verifier, but could not be - * parsed. + * The user doesn't have SCRAM verifier, nor could we generate + * one. (You cannot do SCRAM authentication with an MD5 hash.) */ - elog(LOG, "invalid SCRAM verifier for user \"%s\"", username); - doomed = true; + state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM verifier."), + state->username); + got_verifier = false; } } - else if (password_type == PASSWORD_TYPE_PLAINTEXT) + else { - char *verifier; - /* - * The password provided is in plain format, in which case a fresh - * SCRAM verifier can be generated and used for the rest of the - * processing. + * The caller requested us to perform a dummy authentication. This is + * considered normal, since the caller requested it, so don't set log + * detail. */ - verifier = scram_build_verifier(username, shadow_pass, 0); - - (void) parse_scram_verifier(verifier, &state->salt, &state->iterations, - state->StoredKey, state->ServerKey); - pfree(verifier); + got_verifier = false; } - else - doomed = true; - if (doomed) + /* + * If the user did not have a valid SCRAM verifier, we still go through + * the motions with a mock one, and fail as if the client supplied an + * incorrect password. This is to avoid revealing information to an + * attacker. + */ + if (!got_verifier) { - /* - * We don't have a valid SCRAM verifier, nor could we generate one, or - * the caller requested us to perform a dummy authentication. - * - * The authentication is bound to fail, but to avoid revealing - * information to the attacker, go through the motions with a fake - * SCRAM verifier, and fail as if the password was incorrect. - */ - state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM verifier."), - state->username); mock_scram_verifier(username, &state->salt, &state->iterations, state->StoredKey, state->ServerKey); + state->doomed = true; } - state->doomed = doomed; return state; } diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index a699a09e9ad..5f4f55760c1 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -24,6 +24,7 @@ #include <sys/select.h> #endif +#include "commands/user.h" #include "common/ip.h" #include "common/md5.h" #include "libpq/auth.h" @@ -49,17 +50,15 @@ static char *recv_password_packet(Port *port); /*---------------------------------------------------------------- - * MD5 authentication + * Password-based authentication methods (password, md5, and scram) *---------------------------------------------------------------- */ -static int CheckMD5Auth(Port *port, char **logdetail); +static int CheckPasswordAuth(Port *port, char **logdetail); +static int CheckPWChallengeAuth(Port *port, char **logdetail); -/*---------------------------------------------------------------- - * Plaintext password authentication - *---------------------------------------------------------------- - */ +static int CheckMD5Auth(Port *port, char *shadow_pass, char **logdetail); +static int CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail); -static int CheckPasswordAuth(Port *port, char **logdetail); /*---------------------------------------------------------------- * Ident authentication @@ -200,12 +199,6 @@ static int CheckRADIUSAuth(Port *port); static int PerformRadiusTransaction(char *server, char *secret, char *portstr, char *identifier, char *user_name, char *passwd); -/*---------------------------------------------------------------- - * SASL authentication - *---------------------------------------------------------------- - */ -static int CheckSASLAuth(Port *port, char **logdetail); - /* * Maximum accepted size of GSS and SSPI authentication tokens. * @@ -291,7 +284,7 @@ auth_failed(Port *port, int status, char *logdetail) break; case uaPassword: case uaMD5: - case uaSASL: + case uaSCRAM: errstr = gettext_noop("password authentication failed for user \"%s\""); /* We use it to indicate if a .pgpass password failed. */ errcode_return = ERRCODE_INVALID_PASSWORD; @@ -552,17 +545,14 @@ ClientAuthentication(Port *port) break; case uaMD5: - status = CheckMD5Auth(port, &logdetail); + case uaSCRAM: + status = CheckPWChallengeAuth(port, &logdetail); break; case uaPassword: status = CheckPasswordAuth(port, &logdetail); break; - case uaSASL: - status = CheckSASLAuth(port, &logdetail); - break; - case uaPAM: #ifdef USE_PAM status = CheckPAMAuth(port, port->user_name, ""); @@ -710,41 +700,34 @@ recv_password_packet(Port *port) /*---------------------------------------------------------------- - * MD5 authentication + * Password-based authentication mechanisms *---------------------------------------------------------------- */ +/* + * Plaintext password authentication. + */ static int -CheckMD5Auth(Port *port, char **logdetail) +CheckPasswordAuth(Port *port, char **logdetail) { - char md5Salt[4]; /* Password salt */ char *passwd; - char *shadow_pass; int result; + char *shadow_pass; - if (Db_user_namespace) - ereport(FATAL, - (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), - errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"))); - - /* include the salt to use for computing the response */ - if (!pg_backend_random(md5Salt, 4)) - { - ereport(LOG, - (errmsg("could not generate random MD5 salt"))); - return STATUS_ERROR; - } - - sendAuthRequest(port, AUTH_REQ_MD5, md5Salt, 4); + sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0); passwd = recv_password_packet(port); if (passwd == NULL) return STATUS_EOF; /* client wouldn't send password */ - result = get_role_password(port->user_name, &shadow_pass, logdetail); - if (result == STATUS_OK) - result = md5_crypt_verify(port->user_name, shadow_pass, passwd, - md5Salt, 4, logdetail); + shadow_pass = get_role_password(port->user_name, logdetail); + if (shadow_pass) + { + result = plain_crypt_verify(port->user_name, shadow_pass, passwd, + logdetail); + } + else + result = STATUS_ERROR; if (shadow_pass) pfree(shadow_pass); @@ -753,42 +736,114 @@ CheckMD5Auth(Port *port, char **logdetail) return result; } -/*---------------------------------------------------------------- - * Plaintext password authentication - *---------------------------------------------------------------- +/* + * MD5 and SCRAM authentication. */ +static int +CheckPWChallengeAuth(Port *port, char **logdetail) +{ + int auth_result; + char *shadow_pass; + PasswordType pwtype; + + Assert(port->hba->auth_method == uaSCRAM || + port->hba->auth_method == uaMD5); + + /* First look up the user's password. */ + shadow_pass = get_role_password(port->user_name, logdetail); + + /* + * If the user does not exist, or has no password, we still go through the + * motions of authentication, to avoid revealing to the client that the + * user didn't exist. If 'md5' is allowed, we choose whether to use 'md5' + * or 'scram' authentication based on current password_encryption setting. + * The idea is that most genuine users probably have a password of that + * type, if we pretend that this user had a password of that type, too, it + * "blends in" best. + * + * If the user had a password, but it was expired, we'll use the details + * of the expired password for the authentication, but report it as + * failure to the client even if correct password was given. + */ + if (!shadow_pass) + pwtype = Password_encryption; + else + pwtype = get_password_type(shadow_pass); + + /* + * If 'md5' authentication is allowed, decide whether to perform 'md5' or + * 'scram' authentication based on the type of password the user has. If + * it's an MD5 hash, we must do MD5 authentication, and if it's a SCRAM + * verifier, we must do SCRAM authentication. If it's stored in + * plaintext, we could do either one, so we opt for the more secure + * mechanism, SCRAM. + * + * If MD5 authentication is not allowed, always use SCRAM. If the user + * had an MD5 password, CheckSCRAMAuth() 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); + } + + if (shadow_pass) + pfree(shadow_pass); + + /* + * If get_role_password() returned error, return error, even if the + * authentication succeeded. + */ + if (!shadow_pass) + { + Assert(auth_result != STATUS_OK); + return STATUS_ERROR; + } + return auth_result; +} static int -CheckPasswordAuth(Port *port, char **logdetail) +CheckMD5Auth(Port *port, char *shadow_pass, char **logdetail) { + char md5Salt[4]; /* Password salt */ char *passwd; int result; - char *shadow_pass; - sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0); + if (Db_user_namespace) + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"))); + + /* include the salt to use for computing the response */ + if (!pg_backend_random(md5Salt, 4)) + { + ereport(LOG, + (errmsg("could not generate random MD5 salt"))); + return STATUS_ERROR; + } + + sendAuthRequest(port, AUTH_REQ_MD5, md5Salt, 4); passwd = recv_password_packet(port); if (passwd == NULL) return STATUS_EOF; /* client wouldn't send password */ - result = get_role_password(port->user_name, &shadow_pass, logdetail); - if (result == STATUS_OK) - result = plain_crypt_verify(port->user_name, shadow_pass, passwd, - logdetail); - if (shadow_pass) - pfree(shadow_pass); + result = md5_crypt_verify(port->user_name, shadow_pass, passwd, + md5Salt, 4, logdetail); + else + result = STATUS_ERROR; + pfree(passwd); return result; } -/*---------------------------------------------------------------- - * SASL authentication system - *---------------------------------------------------------------- - */ static int -CheckSASLAuth(Port *port, char **logdetail) +CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) { int mtype; StringInfoData buf; @@ -796,8 +851,6 @@ CheckSASLAuth(Port *port, char **logdetail) char *output = NULL; int outputlen = 0; int result; - char *shadow_pass; - bool doomed = false; /* * SASL auth is not supported for protocol versions before 3, because it @@ -827,11 +880,9 @@ CheckSASLAuth(Port *port, 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. */ - if (get_role_password(port->user_name, &shadow_pass, logdetail) != STATUS_OK) - doomed = true; /* Initialize the status tracker for message exchanges */ - scram_opaq = pg_be_scram_init(port->user_name, shadow_pass, doomed); + scram_opaq = pg_be_scram_init(port->user_name, shadow_pass); /* * Loop through SASL message exchange. This exchange can consist of @@ -875,7 +926,7 @@ CheckSASLAuth(Port *port, char **logdetail) */ result = pg_be_scram_exchange(scram_opaq, buf.data, buf.len, &output, &outputlen, - doomed ? NULL : logdetail); + logdetail); /* input buffer no longer used */ pfree(buf.data); diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index ac10751ec20..34beab53342 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -31,25 +31,18 @@ /* * Fetch stored password for a user, for authentication. * - * Returns STATUS_OK on success. On error, returns STATUS_ERROR, and stores - * a palloc'd string describing the reason, for the postmaster log, in - * *logdetail. The error reason should *not* be sent to the client, to avoid - * giving away user information! - * - * If the password is expired, it is still returned in *shadow_pass, but the - * return code is STATUS_ERROR. On other errors, *shadow_pass is set to - * NULL. + * On error, returns NULL, and stores a palloc'd string describing the reason, + * for the postmaster log, in *logdetail. The error reason should *not* be + * sent to the client, to avoid giving away user information! */ -int -get_role_password(const char *role, char **shadow_pass, char **logdetail) +char * +get_role_password(const char *role, char **logdetail) { - int retval = STATUS_ERROR; TimestampTz vuntil = 0; HeapTuple roleTup; Datum datum; bool isnull; - - *shadow_pass = NULL; + char *shadow_pass; /* Get role info from pg_authid */ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role)); @@ -57,7 +50,7 @@ get_role_password(const char *role, char **shadow_pass, char **logdetail) { *logdetail = psprintf(_("Role \"%s\" does not exist."), role); - return STATUS_ERROR; /* no such user */ + return NULL; /* no such user */ } datum = SysCacheGetAttr(AUTHNAME, roleTup, @@ -67,9 +60,9 @@ get_role_password(const char *role, char **shadow_pass, char **logdetail) ReleaseSysCache(roleTup); *logdetail = psprintf(_("User \"%s\" has no password assigned."), role); - return STATUS_ERROR; /* user has no password */ + return NULL; /* user has no password */ } - *shadow_pass = TextDatumGetCString(datum); + shadow_pass = TextDatumGetCString(datum); datum = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_rolvaliduntil, &isnull); @@ -78,30 +71,25 @@ get_role_password(const char *role, char **shadow_pass, char **logdetail) ReleaseSysCache(roleTup); - if (**shadow_pass == '\0') + if (*shadow_pass == '\0') { *logdetail = psprintf(_("User \"%s\" has an empty password."), role); - pfree(*shadow_pass); - *shadow_pass = NULL; - return STATUS_ERROR; /* empty password */ + pfree(shadow_pass); + return NULL; /* empty password */ } /* - * Password OK, now check to be sure we are not past rolvaliduntil + * Password OK, but check to be sure we are not past rolvaliduntil */ - if (isnull) - retval = STATUS_OK; - else if (vuntil < GetCurrentTimestamp()) + if (!isnull && vuntil < GetCurrentTimestamp()) { *logdetail = psprintf(_("User \"%s\" has an expired password."), role); - retval = STATUS_ERROR; + return NULL; } - else - retval = STATUS_OK; - return retval; + return shadow_pass; } /* diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 49be6638b86..af89fe898a6 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1328,7 +1328,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) parsedline->auth_method = uaMD5; } else if (strcmp(token->string, "scram") == 0) - parsedline->auth_method = uaSASL; + parsedline->auth_method = uaSCRAM; else if (strcmp(token->string, "pam") == 0) #ifdef USE_PAM parsedline->auth_method = uaPAM; diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h index 0502d6a0e5b..3b5da69b087 100644 --- a/src/include/libpq/crypt.h +++ b/src/include/libpq/crypt.h @@ -32,8 +32,7 @@ extern PasswordType get_password_type(const char *shadow_pass); extern char *encrypt_password(PasswordType target_type, const char *role, const char *password); -extern int get_role_password(const char *role, char **shadow_pass, - char **logdetail); +extern char *get_role_password(const char *role, char **logdetail); extern int md5_crypt_verify(const char *role, const char *shadow_pass, const char *client_pass, const char *md5_salt, diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 6c7382e67fd..9a4f228d6a7 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -30,7 +30,7 @@ typedef enum UserAuth uaIdent, uaPassword, uaMD5, - uaSASL, + uaSCRAM, uaGSS, uaSSPI, uaPAM, diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h index fb21e056c81..e373f0c07e8 100644 --- a/src/include/libpq/scram.h +++ b/src/include/libpq/scram.h @@ -22,7 +22,7 @@ #define SASL_EXCHANGE_FAILURE 2 /* Routines dedicated to authentication */ -extern void *pg_be_scram_init(const char *username, const char *shadow_pass, bool doomed); +extern void *pg_be_scram_init(const char *username, const char *shadow_pass); extern int pg_be_scram_exchange(void *opaq, char *input, int inputlen, char **output, int *outputlen, char **logdetail); diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl index 8726a23e0df..d7bc13bd58e 100644 --- a/src/test/authentication/t/001_password.pl +++ b/src/test/authentication/t/001_password.pl @@ -75,10 +75,10 @@ SKIP: test_role($node, 'md5_role', 'scram', 2); test_role($node, 'plain_role', 'scram', 0); - # For "md5" method, users "plain_role" and "md5_role" should be able to - # connect. + # For "md5" method, all users should be able to connect (SCRAM + # authentication will be performed for the user with a scram verifier.) reset_pg_hba($node, 'md5'); - test_role($node, 'scram_role', 'md5', 2); + test_role($node, 'scram_role', 'md5', 0); test_role($node, 'md5_role', 'md5', 0); test_role($node, 'plain_role', 'md5', 0); } |