mirror of
https://github.com/postgres/postgres.git
synced 2025-11-07 19:06:32 +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:
@@ -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);
|
||||
|
||||
/*----------------------------------------------------------------
|
||||
* Plaintext password authentication
|
||||
*----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
|
||||
/*----------------------------------------------------------------
|
||||
* 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,16 +700,116 @@ 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 *passwd;
|
||||
int result;
|
||||
char *shadow_pass;
|
||||
|
||||
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
|
||||
|
||||
passwd = recv_password_packet(port);
|
||||
if (passwd == NULL)
|
||||
return STATUS_EOF; /* client wouldn't send password */
|
||||
|
||||
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);
|
||||
pfree(passwd);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
CheckMD5Auth(Port *port, char *shadow_pass, char **logdetail)
|
||||
{
|
||||
char md5Salt[4]; /* Password salt */
|
||||
char *passwd;
|
||||
char *shadow_pass;
|
||||
int result;
|
||||
|
||||
if (Db_user_namespace)
|
||||
@@ -741,54 +831,19 @@ CheckMD5Auth(Port *port, char **logdetail)
|
||||
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)
|
||||
if (shadow_pass)
|
||||
result = md5_crypt_verify(port->user_name, shadow_pass, passwd,
|
||||
md5Salt, 4, logdetail);
|
||||
else
|
||||
result = STATUS_ERROR;
|
||||
|
||||
if (shadow_pass)
|
||||
pfree(shadow_pass);
|
||||
pfree(passwd);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------
|
||||
* Plaintext password authentication
|
||||
*----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static int
|
||||
CheckPasswordAuth(Port *port, char **logdetail)
|
||||
{
|
||||
char *passwd;
|
||||
int result;
|
||||
char *shadow_pass;
|
||||
|
||||
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 = plain_crypt_verify(port->user_name, shadow_pass, passwd,
|
||||
logdetail);
|
||||
|
||||
if (shadow_pass)
|
||||
pfree(shadow_pass);
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user