aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/libpq/auth-scram.c104
-rw-r--r--src/backend/libpq/auth.c179
-rw-r--r--src/backend/libpq/crypt.c44
-rw-r--r--src/backend/libpq/hba.c2
-rw-r--r--src/include/libpq/crypt.h3
-rw-r--r--src/include/libpq/hba.h2
-rw-r--r--src/include/libpq/scram.h2
-rw-r--r--src/test/authentication/t/001_password.pl6
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);
}