1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-21 00:42:43 +03:00

Allow SCRAM authentication, when pg_hba.conf says 'md5'.

If a user has a SCRAM verifier in pg_authid.rolpassword, there's no reason
we cannot attempt to perform SCRAM authentication instead of MD5. The worst
that can happen is that the client doesn't support SCRAM, and the
authentication will fail. But previously, it would fail for sure, because
we would not even try. SCRAM is strictly more secure than MD5, so there's
no harm in trying it. This allows for a more graceful transition from MD5
passwords to SCRAM, as user passwords can be changed to SCRAM verifiers
incrementally, without changing pg_hba.conf.

Refactor the code in auth.c to support that better. Notably, we now have to
look up the user's pg_authid entry before sending the password challenge,
also when performing MD5 authentication. Also simplify the concept of a
"doomed" authentication. Previously, if a user had a password, but it had
expired, we still performed SCRAM authentication (but always returned error
at the end) using the salt and iteration count from the expired password.
Now we construct a fake salt, like we do when the user doesn't have a
password or doesn't exist at all. That simplifies get_role_password(), and
we can don't need to distinguish the  "user has expired password", and
"user does not exist" cases in auth.c.

On second thoughts, also rename uaSASL to uaSCRAM. It refers to the
mechanism specified in pg_hba.conf, and while we use SASL for SCRAM
authentication at the protocol level, the mechanism should be called SCRAM,
not SASL. As a comparison, we have uaLDAP, even though it looks like the
plain 'password' authentication at the protocol level.

Discussion: https://www.postgresql.org/message-id/6425.1489506016@sss.pgh.pa.us
Reviewed-by: Michael Paquier
This commit is contained in:
Heikki Linnakangas
2017-03-24 13:32:21 +02:00
parent 78874531ba
commit 7ac955b347
9 changed files with 221 additions and 172 deletions

View File

@@ -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 == NULL || doomed)
password_type = -1;
else
password_type = get_password_type(shadow_pass);
if (password_type == PASSWORD_TYPE_SCRAM)
if (shadow_pass)
{
if (!parse_scram_verifier(shadow_pass, &state->salt, &state->iterations,
state->StoredKey, state->ServerKey))
int 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 password looked like a SCRAM verifier, but could not be
* parsed.
* The stored password is in plain format. Generate a fresh SCRAM
* verifier from it, and proceed with that.
*/
elog(LOG, "invalid SCRAM verifier for user \"%s\"", username);
doomed = true;
char *verifier;
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 user doesn't have SCRAM verifier, nor could we generate
* one. (You cannot do SCRAM authentication with an MD5 hash.)
*/
state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM verifier."),
state->username);
got_verifier = false;
}
}
else if (password_type == PASSWORD_TYPE_PLAINTEXT)
{
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.
*/
verifier = scram_build_verifier(username, shadow_pass, 0);
(void) parse_scram_verifier(verifier, &state->salt, &state->iterations,
state->StoredKey, state->ServerKey);
pfree(verifier);
}
else
doomed = true;
if (doomed)
{
/*
* 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.
* The caller requested us to perform a dummy authentication. This is
* considered normal, since the caller requested it, so don't set log
* detail.
*/
state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM verifier."),
state->username);
got_verifier = false;
}
/*
* 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)
{
mock_scram_verifier(username, &state->salt, &state->iterations,
state->StoredKey, state->ServerKey);
state->doomed = true;
}
state->doomed = doomed;
return state;
}