0002-multiple-passwords-work-with-scram-and-md5.patch

application/octet-stream

Filename: 0002-multiple-passwords-work-with-scram-and-md5.patch
Type: application/octet-stream
Part: 0
Message: [PoC/RFC] Multiple passwords, interval expirations

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch 0002
Subject: multiple passwords work with scram and md5
File+
src/backend/commands/user.c 163 31
src/backend/libpq/auth.c 40 54
src/backend/libpq/auth-sasl.c 2 2
src/backend/libpq/auth-scram.c 108 76
src/backend/libpq/crypt.c 28 22
src/backend/parser/gram.y 12 1
src/backend/utils/cache/catcache.c 1 1
src/backend/utils/cache/syscache.c 3 3
src/include/catalog/pg_auth_password.h 5 4
src/include/commands/user.h 1 1
src/include/libpq/crypt.h 1 1
src/include/libpq/sasl.h 2 2
src/include/libpq/scram.h 1 1
src/include/parser/kwlist.h 1 0
src/include/utils/syscache.h 1 1
src/test/regress/expected/password.out 1 1
From 6dc86ab02a1639044440e9c217c0ba649da932d9 Mon Sep 17 00:00:00 2001
From: Joshua Brindle <joshua.brindle@crunchydata.com>
Date: Tue, 1 Feb 2022 14:32:55 -0800
Subject: [PATCH 2/3] multiple passwords work with scram and md5

also renaming roles, dropping roles, switching between
password_encryption

Signed-off-by: Joshua Brindle <joshua.brindle@crunchydata.com>
---
 src/backend/commands/user.c            | 194 +++++++++++++++++++++----
 src/backend/libpq/auth-sasl.c          |   4 +-
 src/backend/libpq/auth-scram.c         | 184 +++++++++++++----------
 src/backend/libpq/auth.c               |  94 +++++-------
 src/backend/libpq/crypt.c              |  50 ++++---
 src/backend/parser/gram.y              |  13 +-
 src/backend/utils/cache/catcache.c     |   2 +-
 src/backend/utils/cache/syscache.c     |   6 +-
 src/include/catalog/pg_auth_password.h |   9 +-
 src/include/commands/user.h            |   2 +-
 src/include/libpq/crypt.h              |   2 +-
 src/include/libpq/sasl.h               |   4 +-
 src/include/libpq/scram.h              |   2 +-
 src/include/parser/kwlist.h            |   1 +
 src/include/utils/syscache.h           |   2 +-
 src/test/regress/expected/password.out |   2 +-
 16 files changed, 370 insertions(+), 201 deletions(-)

diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 9ff99342eef..69daabd7928 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -18,6 +18,7 @@
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
 #include "catalog/catalog.h"
+
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
@@ -31,11 +32,14 @@
 #include "commands/defrem.h"
 #include "commands/seclabel.h"
 #include "commands/user.h"
+#include "common/scram-common.h"
 #include "libpq/crypt.h"
+#include "libpq/scram.h"
 #include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/catcache.h"
 #include "utils/fmgroids.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
@@ -47,6 +51,9 @@ Oid			binary_upgrade_next_pg_authid_oid = InvalidOid;
 /* GUC parameter */
 int			Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
 
+/* default password name */
+const char* default_passname = "__def__";
+
 /* Hook to check passwords in CreateRole() and AlterRole() */
 check_password_hook_type check_password_hook = NULL;
 
@@ -69,7 +76,7 @@ have_createrole_privilege(void)
  * Is the role able to log in
 */
 bool
-is_role_valid(const char *rolename, char **logdetail)
+is_role_valid(const char *rolename, const char **logdetail)
 {
 	HeapTuple	roleTup;
 	Datum		datum;
@@ -102,6 +109,64 @@ is_role_valid(const char *rolename, char **logdetail)
 }
 
 
+static
+bool
+validate_and_get_salt(char *rolename, char **salt, const char **logdetail)
+{
+	char	  **current_secrets;
+	char	   *salt1, *salt2 = NULL;
+	int			i, num;
+	PasswordType passtype;
+
+	if (Password_encryption == PASSWORD_TYPE_MD5)
+	{
+		*salt = rolename; /* md5 always uses role name, no need to look through the passwords */
+		return true;
+	}
+	else if (Password_encryption == PASSWORD_TYPE_PLAINTEXT)
+	{
+		*salt = NULL; /* Plaintext does not have a salt */
+		return true;
+	}
+
+	current_secrets = get_role_passwords(rolename, logdetail, &num);
+	if (num == 0)
+	{
+		*salt = NULL; /* No existing passwords, allow one to be generated */
+		return true;
+	}
+	for (i = 0; i < num; i++) {
+		passtype = get_password_type(current_secrets[i]);
+		if (passtype == PASSWORD_TYPE_MD5 || passtype == PASSWORD_TYPE_PLAINTEXT)
+			continue; /* md5 uses rolename as salt so it is always the same and plaintext has no salt */
+		else if (passtype == PASSWORD_TYPE_SCRAM_SHA_256) {
+				int			iterations;
+				uint8		stored_key[SCRAM_KEY_LEN];
+				uint8		server_key[SCRAM_KEY_LEN];
+
+				parse_scram_secret(current_secrets[i], &iterations, &salt1,
+									stored_key, server_key);
+
+				if (salt2 != NULL) {
+					if (strcmp(salt1, salt2)) {
+						*logdetail = psprintf(_("inconsistent salts, clearing password"));
+						*salt = NULL;
+						return false;
+					}
+				}
+				else
+					salt2 = salt1;
+		}
+
+	}
+	for (i = 0; i < num; i++)
+		pfree(current_secrets[i]);
+	pfree(current_secrets);
+	if (salt2)
+		*salt = pstrdup(salt2);
+	return true;
+}
+
 /*
  * CREATE ROLE
  */
@@ -117,6 +182,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	ListCell   *item;
 	ListCell   *option;
 	char	   *password = NULL;	/* user password */
+	char       *passname = NULL;    /* name of the password for managing multiple passwords */
 	bool		issuper = false;	/* Make the user a superuser? */
 	bool		inherit = true; /* Auto inherit privileges? */
 	bool		createrole = false; /* Can this user create roles? */
@@ -144,6 +210,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	DefElem    *dadminmembers = NULL;
 	DefElem    *dvalidUntil = NULL;
 	DefElem    *dbypassRLS = NULL;
+	DefElem    *dpassName = NULL;
+
 
 	/* The defaults can vary depending on the original statement type */
 	switch (stmt->stmt_type)
@@ -246,6 +314,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 				errorConflictingDefElem(defel, pstate);
 			dbypassRLS = defel;
 		}
+		else if (strcmp(defel->defname, "passname") == 0)
+		{
+			if (dpassName)
+				errorConflictingDefElem(defel, pstate);
+			dpassName = defel;
+		}
+
 		else
 			elog(ERROR, "option \"%s\" not recognized",
 				 defel->defname);
@@ -283,6 +358,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 		validUntil = strVal(dvalidUntil->arg);
 	if (dbypassRLS)
 		bypassrls = boolVal(dbypassRLS->arg);
+	if (dpassName)
+		passname = strVal(dpassName->arg);
 
 	/* Check some permissions first */
 	if (issuper)
@@ -425,7 +502,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	if (password)
 	{
 		char	   *shadow_pass;
-		char	   *logdetail;
+		const char	   *logdetail;
 		Datum		new_password_record[Natts_pg_auth_password];
 		bool		new_password_record_nulls[Natts_pg_auth_password];
 		Relation	pg_auth_password_rel;
@@ -458,7 +535,12 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 
 			MemSet(new_password_record, 0, sizeof(new_password_record));
 			MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
-
+			if (passname != NULL)
+				new_password_record[Anum_pg_auth_password_name - 1] = 
+								DirectFunctionCall1(namein, CStringGetDatum(passname));
+			else
+				new_password_record[Anum_pg_auth_password_name - 1] = 
+								DirectFunctionCall1(namein, CStringGetDatum(default_passname));
 			/* open password table and insert it. */
 			pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
 			pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
@@ -560,6 +642,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 	ListCell   *option;
 	char	   *rolename;
 	char	   *password = NULL;	/* user password */
+	char       *passname = NULL;    /* password name for managing multiple passwords */
 	int			connlimit = -1; /* maximum connections allowed */
 	char	   *validUntil = NULL;	/* time the login is valid until */
 	Datum		validUntil_datum;	/* same, as timestamptz Datum */
@@ -575,6 +658,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 	DefElem    *drolemembers = NULL;
 	DefElem    *dvalidUntil = NULL;
 	DefElem    *dbypassRLS = NULL;
+	DefElem    *dpassName = NULL;
 	Oid			roleid;
 
 	check_rolespec_name(stmt->role,
@@ -652,6 +736,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 				errorConflictingDefElem(defel, pstate);
 			dbypassRLS = defel;
 		}
+		else if (strcmp(defel->defname, "passname") == 0)
+		{
+			if (dpassName)
+				errorConflictingDefElem(defel, pstate);
+			dpassName = defel;
+		}
 		else
 			elog(ERROR, "option \"%s\" not recognized",
 				 defel->defname);
@@ -669,6 +759,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 	}
 	if (dvalidUntil)
 		validUntil = strVal(dvalidUntil->arg);
+	if (dpassName)
+		passname = strVal(dpassName->arg);
 
 	/*
 	 * Scan the pg_authid relation to be certain the user exists.
@@ -805,8 +897,9 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 	/* password */
 	if (password)
 	{
-		char	   *shadow_pass;
-		const char *logdetail = NULL;
+		char	*salt = NULL;
+		const char 	*logdetail = NULL;
+		char	*shadow_pass;
 
 		/* Like in CREATE USER, don't allow an empty password. */
 		if (password[0] == '\0' ||
@@ -818,13 +911,26 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 		}
 		else
 		{
-			/* Encrypt the password to the requested format. */
-			shadow_pass = encrypt_password(Password_encryption, rolename,
-										   password);
-			new_password_record[Anum_pg_auth_password_password - 1] =
-				CStringGetTextDatum(shadow_pass);
+			if (!validate_and_get_salt(rolename, &salt, &logdetail))
+				new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
+			else
+			{
+				/* Encrypt the password to the requested format. */
+				shadow_pass = encrypt_password(Password_encryption, salt,
+											password);
+				new_password_record[Anum_pg_auth_password_password - 1] =
+					CStringGetTextDatum(shadow_pass);
+
+				new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
+
+				if (passname)
+					new_password_record[Anum_pg_auth_password_name - 1] =
+						DirectFunctionCall1(namein, CStringGetDatum(passname));
+				else
+					new_password_record[Anum_pg_auth_password_name - 1] =
+					DirectFunctionCall1(namein, CStringGetDatum(default_passname));
+			}
 		}
-		new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
 	}
 
 	/* unset password */
@@ -859,7 +965,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 
 		pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
 		pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
-		password_tuple = SearchSysCache1(AUTHPASSWORD, ObjectIdGetDatum(roleid));
+		if (dpassName)
+			password_tuple = SearchSysCache2(AUTHPASSWORDNAME, ObjectIdGetDatum(roleid), CStringGetDatum(passname));
+		else
+			password_tuple = SearchSysCache2(AUTHPASSWORDNAME, ObjectIdGetDatum(roleid), CStringGetDatum(default_passname));
 
 		if (new_password_record_nulls[Anum_pg_auth_password_password - 1] == true) /* delete existing password */
 		{
@@ -1157,14 +1266,23 @@ DropRole(DropRoleStmt *stmt)
 		DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
 
 		/*
-		 * Drop password
+		 * Drop password(s)
 		 */
-		tuple = SearchSysCache1(AUTHPASSWORD, roleid);
-		if (HeapTupleIsValid(tuple)) {
-			CatalogTupleDelete(pg_auth_password_rel, &tuple->t_self);
-			ReleaseSysCache(tuple);
+		ScanKeyInit(&scankey,
+					Anum_pg_auth_password_roleid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(roleid));
+
+		sscan = systable_beginscan(pg_auth_password_rel, AuthPasswordRoleOidIndexId,
+								   true, NULL, 1, &scankey);
+
+		while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+		{
+			CatalogTupleDelete(pg_auth_password_rel, &tmp_tuple->t_self);
 		}
 
+		systable_endscan(sscan);
+
 		/*
 		 * Remove settings for this role.
 		 */
@@ -1201,6 +1319,9 @@ RenameRole(const char *oldname, const char *newname)
 				newtuple,
 				passtuple;
 	TupleDesc	dsc;
+	ScanKeyData scankey;
+	SysScanDesc sscan;
+
 	Relation	rel;
 	Datum		datum;
 	bool		isnull = true;
@@ -1211,6 +1332,7 @@ RenameRole(const char *oldname, const char *newname)
 	Oid			roleid;
 	ObjectAddress address;
 	Form_pg_authid authform;
+	Relation	pg_auth_password_rel;
 
 	rel = table_open(AuthIdRelationId, RowExclusiveLock);
 	dsc = RelationGetDescr(rel);
@@ -1301,28 +1423,38 @@ RenameRole(const char *oldname, const char *newname)
 															   CStringGetDatum(newname));
 	repl_null[Anum_pg_authid_rolname - 1] = false;
 
-	passtuple = SearchSysCache1(AUTHPASSWORD, roleid);
+	ScanKeyInit(&scankey,
+				Anum_pg_auth_password_roleid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(roleid));
+
+	pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
 
-	if (HeapTupleIsValid(passtuple))
-		datum = SysCacheGetAttr(AUTHPASSWORD, passtuple,
-								Anum_pg_auth_password_password, &isnull);
+	sscan = systable_beginscan(pg_auth_password_rel, AuthPasswordRoleOidIndexId,
+								true, NULL, 1, &scankey);
 
-	if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
+	while (HeapTupleIsValid(passtuple = systable_getnext(sscan)))
 	{
-		Relation	pg_auth_password_rel;
+		datum = SysCacheGetAttr(AUTHPASSWORDNAME, passtuple,
+							Anum_pg_auth_password_password, &isnull);
 
-		/* MD5 uses the username as salt, so just clear it on a rename */
-		pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+		if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
+		{
 
-		if (HeapTupleIsValid(passtuple)) {
-			CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
-			ReleaseSysCache(passtuple);
+			/* MD5 uses the username as salt, so just clear it on a rename */
+
+			if (HeapTupleIsValid(passtuple)) {
+				CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
+				ereport(NOTICE,
+					(errmsg("MD5 password cleared because of role rename")));
+
+			}
 		}
-		table_close(pg_auth_password_rel, NoLock);
-		ereport(NOTICE,
-				(errmsg("MD5 password cleared because of role rename")));
 	}
 
+	systable_endscan(sscan);
+	table_close(pg_auth_password_rel, NoLock);
+
 	newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
 	CatalogTupleUpdate(rel, &oldtuple->t_self, newtuple);
 
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index 805b3695b78..652c18fa94a 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -49,7 +49,7 @@
  * should just pass NULL.
  */
 int
-CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
+CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, const char **passwords, int num,
 			  const char **logdetail)
 {
 	StringInfoData sasl_mechs;
@@ -136,7 +136,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
 			 * 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);
+			opaq = mech->init(port, selected_mech, passwords, num);
 
 			inputlen = pq_getmsgint(&buf, 4);
 			if (inputlen == -1)
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 795f1cba555..95db4aaf958 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -109,7 +109,7 @@
 
 static void scram_get_mechanisms(Port *port, StringInfo buf);
 static void *scram_init(Port *port, const char *selected_mech,
-						const char *shadow_pass);
+						const char **secrets, const int num_secrets);
 static int	scram_exchange(void *opaq, const char *input, int inputlen,
 						   char **output, int *outputlen,
 						   const char **logdetail);
@@ -132,6 +132,13 @@ typedef enum
 	SCRAM_AUTH_FINISHED
 } scram_state_enum;
 
+typedef struct
+{
+	uint8		StoredKey[SCRAM_KEY_LEN];
+	uint8		ServerKey[SCRAM_KEY_LEN];
+} scram_secret;
+
+
 typedef struct
 {
 	scram_state_enum state;
@@ -141,10 +148,16 @@ typedef struct
 	Port	   *port;
 	bool		channel_binding_in_use;
 
+	/* 
+	 * The salt and iterations must be the same for all 
+	 * secrets since they are sent as part of the initial message
+	 */
 	int			iterations;
 	char	   *salt;			/* base64-encoded */
-	uint8		StoredKey[SCRAM_KEY_LEN];
-	uint8		ServerKey[SCRAM_KEY_LEN];
+	/* Array of possible secrets */
+	scram_secret *secrets;
+	int			num_secrets;
+	int			chosen_secret; /* secret chosen during final client message */
 
 	/* Fields of the first message from client */
 	char		cbind_flag;
@@ -220,17 +233,20 @@ scram_get_mechanisms(Port *port, StringInfo buf)
  * It should be one of the mechanisms that we support, as returned by
  * scram_get_mechanisms().
  *
- * 'shadow_pass' is the role's stored secret, from pg_auth_password.password.
+ * 'passwords' are the role's stored secrets, from pg_auth_password.password.
  * The username was provided by the client in the startup message, and is
  * available in port->user_name.  If 'shadow_pass' is NULL, we still perform
  * an authentication exchange, but it will fail, as if an incorrect password
  * was given.
  */
 static void *
-scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
+scram_init(Port *port, const char *selected_mech, const char **secrets, const int num_secrets)
 {
 	scram_state *state;
-	bool		got_secret;
+	bool		got_secret = false;
+	int			i;
+	int	iterations;
+	char *salt = NULL;			/* base64-encoded */
 
 	state = (scram_state *) palloc0(sizeof(scram_state));
 	state->port = port;
@@ -260,46 +276,48 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
 	/*
 	 * Parse the stored secret.
 	 */
-	if (shadow_pass)
+	if (secrets)
 	{
-		int			password_type = get_password_type(shadow_pass);
-
-		if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
+		state->secrets = palloc0(sizeof(scram_secret) * num_secrets);
+		state->num_secrets = num_secrets;
+		for (i = 0; i < num_secrets; i++)
 		{
-			if (parse_scram_secret(shadow_pass, &state->iterations, &state->salt,
-								   state->StoredKey, state->ServerKey))
-				got_secret = true;
-			else
+			int			password_type = get_password_type(secrets[i]);
+
+			if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
 			{
-				/*
-				 * The password looked like a SCRAM secret, but could not be
-				 * parsed.
-				 */
-				ereport(LOG,
-						(errmsg("invalid SCRAM secret for user \"%s\"",
-								state->port->user_name)));
-				got_secret = false;
+			
+				if (parse_scram_secret(secrets[i], &state->iterations, &state->salt,
+									state->secrets[i].StoredKey, state->secrets[i].ServerKey))
+				{
+					if (salt) {
+						/* The stored iterations and salt must match or we cannot proceed, allow failure via mock */
+						if (strcmp(salt, state->salt) || iterations != state->iterations) {
+							ereport(WARNING, (errmsg("inconsistent salt or iterations for user \"%s\"",
+									state->port->user_name)));
+							got_secret = false; /* fail and allow mock creditials to be created */
+							break;
+						}
+					}
+					else
+					{
+						salt = state->salt;
+						iterations = state->iterations;
+						got_secret = true; /* We got at least one good SCRAM secret */
+					}
+				}
+				else
+				{
+					/*
+					* The password looked like a SCRAM secret, but could not be
+					* parsed.
+					*/
+					ereport(LOG,
+							(errmsg("invalid SCRAM secret for user \"%s\"",
+									state->port->user_name)));
+				}
 			}
 		}
-		else
-		{
-			/*
-			 * The user doesn't have SCRAM secret. (You cannot do SCRAM
-			 * authentication with an MD5 hash.)
-			 */
-			state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM secret."),
-										state->port->user_name);
-			got_secret = false;
-		}
-	}
-	else
-	{
-		/*
-		 * The caller requested us to perform a dummy authentication.  This is
-		 * considered normal, since the caller requested it, so don't set log
-		 * detail.
-		 */
-		got_secret = false;
 	}
 
 	/*
@@ -310,8 +328,10 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
 	 */
 	if (!got_secret)
 	{
+		state->secrets = palloc0(sizeof(scram_secret));
+
 		mock_scram_secret(state->port->user_name, &state->iterations,
-						  &state->salt, state->StoredKey, state->ServerKey);
+						  &state->salt, state->secrets[0].StoredKey, state->secrets[0].ServerKey);
 		state->doomed = true;
 	}
 
@@ -459,7 +479,7 @@ scram_exchange(void *opaq, const char *input, int inputlen,
  * The result is palloc'd, so caller is responsible for freeing it.
  */
 char *
-pg_be_scram_build_secret(const char *password)
+pg_be_scram_build_secret(const char *password, const char *salt)
 {
 	char	   *prep_password;
 	pg_saslprep_rc rc;
@@ -476,12 +496,14 @@ pg_be_scram_build_secret(const char *password)
 	if (rc == SASLPREP_SUCCESS)
 		password = (const char *) prep_password;
 
-	/* Generate random salt */
-	if (!pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
+	/* Use passed in salt or generate random salt */
+	if (!salt && !pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
 		ereport(ERROR,
 				(errcode(ERRCODE_INTERNAL_ERROR),
 				 errmsg("could not generate random salt")));
-
+	else if (salt)
+		pg_b64_decode(salt, strlen(salt), saltbuf, SCRAM_DEFAULT_SALT_LEN);
+	
 	result = scram_build_secret(saltbuf, SCRAM_DEFAULT_SALT_LEN,
 								SCRAM_DEFAULT_ITERATIONS, password,
 								&errstr);
@@ -1114,47 +1136,57 @@ verify_client_proof(scram_state *state)
 	uint8		ClientSignature[SCRAM_KEY_LEN];
 	uint8		ClientKey[SCRAM_KEY_LEN];
 	uint8		client_StoredKey[SCRAM_KEY_LEN];
-	pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
-	int			i;
+	pg_hmac_ctx *ctx;
+	int			i, j;
 	const char *errstr = NULL;
-
 	/*
 	 * Calculate ClientSignature.  Note that we don't log directly a failure
 	 * here even when processing the calculations as this could involve a mock
 	 * authentication.
 	 */
-	if (pg_hmac_init(ctx, state->StoredKey, SCRAM_KEY_LEN) < 0 ||
-		pg_hmac_update(ctx,
-					   (uint8 *) state->client_first_message_bare,
-					   strlen(state->client_first_message_bare)) < 0 ||
-		pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
-		pg_hmac_update(ctx,
-					   (uint8 *) state->server_first_message,
-					   strlen(state->server_first_message)) < 0 ||
-		pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
-		pg_hmac_update(ctx,
-					   (uint8 *) state->client_final_message_without_proof,
-					   strlen(state->client_final_message_without_proof)) < 0 ||
-		pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+	for (j = 0; j < state->num_secrets; j++)
 	{
-		elog(ERROR, "could not calculate client signature: %s",
-			 pg_hmac_error(ctx));
-	}
+		ctx = pg_hmac_create(PG_SHA256);
+		elog(LOG, "Trying to verify password %d", j);
+
+		if (pg_hmac_init(ctx, state->secrets[j].StoredKey, SCRAM_KEY_LEN) < 0 ||
+			pg_hmac_update(ctx,
+						(uint8 *) state->client_first_message_bare,
+						strlen(state->client_first_message_bare)) < 0 ||
+			pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
+			pg_hmac_update(ctx,
+						(uint8 *) state->server_first_message,
+						strlen(state->server_first_message)) < 0 ||
+			pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
+			pg_hmac_update(ctx,
+						(uint8 *) state->client_final_message_without_proof,
+						strlen(state->client_final_message_without_proof)) < 0 ||
+			pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+		{
+			elog(LOG, "could not calculate client signature");
+			pg_hmac_free(ctx);
+			continue;
+		}
 
-	pg_hmac_free(ctx);
+		elog(LOG, "success on %d", j);
 
-	/* Extract the ClientKey that the client calculated from the proof */
-	for (i = 0; i < SCRAM_KEY_LEN; i++)
-		ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
+		pg_hmac_free(ctx);
 
-	/* Hash it one more time, and compare with StoredKey */
-	if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0)
-		elog(ERROR, "could not hash stored key: %s", errstr);
+		for (i = 0; i < SCRAM_KEY_LEN; i++)
+			ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
 
-	if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
-		return false;
+		/* Hash it one more time, and compare with StoredKey */
+		if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0)
+			elog(ERROR, "could not hash stored key: %s", errstr);
 
-	return true;
+		if (memcmp(client_StoredKey, state->secrets[j].StoredKey, SCRAM_KEY_LEN) == 0) {
+			elog(LOG, "Moving forward with Password %d", j);
+			state->chosen_secret = j;
+			return true; 
+		}
+	}
+
+	return false;
 }
 
 /*
@@ -1380,7 +1412,7 @@ build_server_final_message(scram_state *state)
 	pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
 
 	/* calculate ServerSignature */
-	if (pg_hmac_init(ctx, state->ServerKey, SCRAM_KEY_LEN) < 0 ||
+	if (pg_hmac_init(ctx, state->secrets[state->chosen_secret].ServerKey, SCRAM_KEY_LEN) < 0 ||
 		pg_hmac_update(ctx,
 					   (uint8 *) state->client_first_message_bare,
 					   strlen(state->client_first_message_bare)) < 0 ||
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 5cfde1eaa13..5569599ab31 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -58,8 +58,7 @@ static void set_authn_id(Port *port, const char *id);
 static int	CheckPasswordAuth(Port *port, const char **logdetail);
 static int	CheckPWChallengeAuth(Port *port, const char **logdetail);
 
-static int	CheckMD5Auth(Port *port, char *shadow_pass,
-						 const char **logdetail);
+static int	CheckMD5Auth(Port *port, const char **passwords, int num, const char **logdetail);
 
 
 /*----------------------------------------------------------------
@@ -790,8 +789,9 @@ static int
 CheckPasswordAuth(Port *port, const char **logdetail)
 {
 	char	   *passwd;
-	int			result;
-	char	   *shadow_pass;
+	int			result = STATUS_ERROR;
+	int			i, num;
+	char	   **passwords;
 
 	sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 
@@ -799,17 +799,21 @@ CheckPasswordAuth(Port *port, const char **logdetail)
 	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;
+	passwords = get_role_passwords(port->user_name, logdetail, &num);
+	if (passwords != NULL) {
+		for (i = 0; i < num; i++) 
+		{
+			result = plain_crypt_verify(port->user_name, passwords[i], passwd,
+										logdetail);
+			if (result == STATUS_OK)
+				break; /* Found a matching password, no need to try any others */
+		}
+		for (i = 0; passwords[i] != NULL; i++) 
+			pfree(passwords[i]);
 
-	if (shadow_pass)
-		pfree(shadow_pass);
+		pfree(passwords);
+	}
+	
 	pfree(passwd);
 
 	if (result == STATUS_OK)
@@ -824,29 +828,15 @@ CheckPasswordAuth(Port *port, const char **logdetail)
 static int
 CheckPWChallengeAuth(Port *port, const char **logdetail)
 {
-	int			auth_result;
-	char	   *shadow_pass;
-	PasswordType pwtype;
+	int			auth_result = STATUS_ERROR;
+	int			i, num;
+	char	  **passwords;
 
 	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 or it's expired, 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-sha-256' authentication based on current
-	 * password_encryption setting.  The idea is that most genuine users
-	 * probably have a password of that type, and if we pretend that this user
-	 * had a password of that type, too, it "blends in" best.
-	 */
-	if (!shadow_pass)
-		pwtype = Password_encryption;
-	else
-		pwtype = get_password_type(shadow_pass);
+	/* First look up the user's passwords. */
+	passwords = get_role_passwords(port->user_name, logdetail, &num);
 
 	/*
 	 * If 'md5' authentication is allowed, decide whether to perform 'md5' or
@@ -858,23 +848,17 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
 	 * 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 = CheckSASLAuth(&pg_be_scram_mech, port, shadow_pass,
-									logdetail);
+	if (passwords != NULL) {
+		if (port->hba->auth_method == uaMD5)
+			auth_result = CheckMD5Auth(port, (const char **) passwords, num, logdetail);
+		else
+			auth_result = CheckSASLAuth(&pg_be_scram_mech, port, (const char **) passwords, num,
+											logdetail);
 
-	if (shadow_pass)
-		pfree(shadow_pass);
+		for (i = 0; i < num; i++) 
+			pfree(passwords[i]);
 
-	/*
-	 * If get_role_password() returned error, return error, even if the
-	 * authentication succeeded.
-	 */
-	if (!shadow_pass)
-	{
-		Assert(auth_result != STATUS_OK);
-		return STATUS_ERROR;
+		pfree(passwords);
 	}
 
 	if (auth_result == STATUS_OK)
@@ -884,11 +868,12 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
 }
 
 static int
-CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail)
+CheckMD5Auth(Port *port, const char **passwords, int num, const char **logdetail)
 {
 	char		md5Salt[4];		/* Password salt */
 	char	   *passwd;
 	int			result;
+	int			i;
 
 	if (Db_user_namespace)
 		ereport(FATAL,
@@ -909,12 +894,13 @@ CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail)
 	if (passwd == NULL)
 		return STATUS_EOF;		/* client wouldn't send password */
 
-	if (shadow_pass)
-		result = md5_crypt_verify(port->user_name, shadow_pass, passwd,
+	for (i = 0; i < num; i++)
+	{
+		result = md5_crypt_verify(port->user_name, passwords[i], passwd,
 								  md5Salt, 4, logdetail);
-	else
-		result = STATUS_ERROR;
-
+		if (result == STATUS_OK)
+			break;
+	}
 	pfree(passwd);
 
 	return result;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 745e61034c2..46b458f7d8a 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -23,6 +23,7 @@
 #include "libpq/scram.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/catcache.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
@@ -34,13 +35,15 @@
  * for the postmaster log, in *logdetail.  The error reason should *not* be
  * sent to the client, to avoid giving away user information!
  */
-char *
-get_role_password(const char *role, const char **logdetail)
+char **
+get_role_passwords(const char *role, const char **logdetail, int *num)
 {
-	HeapTuple	roleTup, passTup;
+	HeapTuple	roleTup;
 	Datum		datum;
 	bool		isnull;
-	char	   *shadow_pass;
+	char	   **passwords;
+	CatCList   *passlist;
+	int		    i;
 
 	/* Get role info from pg_authid */
 	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
@@ -52,30 +55,33 @@ get_role_password(const char *role, const char **logdetail)
 	}
 
 	datum = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_oid, &isnull);
-	
-	passTup = SearchSysCache1(AUTHPASSWORD, datum);
+	ReleaseSysCache(roleTup);
+	/* Find any existing password that is not the one being updated to get the salt */
+	passlist = SearchSysCacheList1(AUTHPASSWORDNAME, datum);
+	*num = passlist->n_members;
 
-	if (!HeapTupleIsValid(passTup))
+	if (passlist->n_members == 0)
 	{
 		*logdetail = psprintf(_("User \"%s\" has no password assigned."),
 							  role);
+		ReleaseCatCacheList(passlist);
 		return NULL;			/* user has no password */
 	}
-	datum = SysCacheGetAttr(AUTHPASSWORD, passTup,
-							Anum_pg_auth_password_password, &isnull);
 
-	ReleaseSysCache(roleTup);
-	if (isnull) /* this should not happen any more but just in case */
+	passwords = palloc0(sizeof(char *) * passlist->n_members); 
+
+	for (i = 0; i < passlist->n_members; i++)
 	{
-		ReleaseSysCache(passTup);
-		*logdetail = psprintf(_("User \"%s\" has no password assigned."),
-							  role);
-		return NULL;			/* user has no password */
+		HeapTuple	tup = &passlist->members[i]->tuple;
+
+		datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+							Anum_pg_auth_password_password, &isnull);
+		passwords[i] = TextDatumGetCString(datum);
 	}
-	shadow_pass = TextDatumGetCString(datum);
-	ReleaseSysCache(passTup);
 
-	return shadow_pass;
+	ReleaseCatCacheList(passlist);
+
+	return passwords;
 }
 
 /*
@@ -107,7 +113,7 @@ get_password_type(const char *shadow_pass)
  * hash, so it is stored as it is regardless of the requested type.
  */
 char *
-encrypt_password(PasswordType target_type, const char *role,
+encrypt_password(PasswordType target_type, const char *salt,
 				 const char *password)
 {
 	PasswordType guessed_type = get_password_type(password);
@@ -128,13 +134,13 @@ encrypt_password(PasswordType target_type, const char *role,
 		case PASSWORD_TYPE_MD5:
 			encrypted_password = palloc(MD5_PASSWD_LEN + 1);
 
-			if (!pg_md5_encrypt(password, role, strlen(role),
+			if (!pg_md5_encrypt(password, salt, strlen(salt),
 								encrypted_password, &errstr))
-				elog(ERROR, "password encryption failed: %s", errstr);
+				elog(ERROR, "password encryption failed %s", errstr);
 			return encrypted_password;
 
 		case PASSWORD_TYPE_SCRAM_SHA_256:
-			return pg_be_scram_build_secret(password);
+			return pg_be_scram_build_secret(password, salt);
 
 		case PASSWORD_TYPE_PLAINTEXT:
 			elog(ERROR, "cannot encrypt password with 'plaintext'");
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a03b33b53bd..2a288ff13f9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -716,7 +716,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSNAME PASSWORD PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -1132,6 +1132,15 @@ AlterOptRoleElem:
 							 errhint("Remove UNENCRYPTED to store the password in encrypted form instead."),
 							 parser_errposition(@1)));
 				}
+			| PASSNAME Sconst
+				{
+					$$ = makeDefElem("passname",
+									 (Node *)makeString($2), @1);
+				}
+			| VALID FOR Sconst
+				{
+					$$ = makeDefElem("validFor", (Node *)makeString($3), @1);
+				}
 			| INHERIT
 				{
 					$$ = makeDefElem("inherit", (Node *)makeBoolean(true), @1);
@@ -15855,6 +15864,7 @@ unreserved_keyword:
 			| PARTIAL
 			| PARTITION
 			| PASSING
+			| PASSNAME
 			| PASSWORD
 			| PLANS
 			| POLICY
@@ -16433,6 +16443,7 @@ bare_label_keyword:
 			| PARTIAL
 			| PARTITION
 			| PASSING
+			| PASSNAME
 			| PASSWORD
 			| PLACING
 			| PLANS
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 356e9cde6fb..c421ac0b19f 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1104,7 +1104,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
 
 		case AUTHNAME:
 		case AUTHOID:
-		case AUTHPASSWORD:
+		case AUTHPASSWORDNAME:
 		case AUTHMEMMEMROLE:
 		case DATABASEOID:
 
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 49d49f13252..2c39cfb083f 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -255,12 +255,12 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
-	{AuthPasswordRelationId,			/* AUTHPASSWORD */
+	{AuthPasswordRelationId,			/* AUTHPASSWORDNAME */
 		AuthPasswordRoleOidIndexId,
-		1,
+		2,
 		{
 			Anum_pg_auth_password_roleid,
-			0,
+			Anum_pg_auth_password_name,
 			0,
 			0
 		},
diff --git a/src/include/catalog/pg_auth_password.h b/src/include/catalog/pg_auth_password.h
index beaa2d40b90..4181caad30b 100644
--- a/src/include/catalog/pg_auth_password.h
+++ b/src/include/catalog/pg_auth_password.h
@@ -24,11 +24,13 @@
  *		typedef struct FormData_pg_auth_password
  * ----------------
  */
-CATALOG(pg_auth_password,4548,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4549,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
+CATALOG(pg_auth_password,4551,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4552,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
 {
 	Oid			roleid BKI_LOOKUP(pg_authid);	/* ID of a role */
+    NameData    name;            /* name of password for multiple password support */
+
 #ifdef CATALOG_VARLEN                /* variable-length fields start here */
-    text            password;        /* password */
+    text            password BKI_FORCE_NOT_NULL;        /* password */
 #endif
 } FormData_pg_auth_password;
 
@@ -44,7 +46,6 @@ DECLARE_TOAST(pg_auth_password, 4175, 4176);
 
 typedef FormData_pg_auth_password *Form_pg_auth_password;
 
-DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 4550, AuthPasswordRoleOidIndexId, on pg_auth_password using btree(roleid oid_ops));
-
+DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 4553, AuthPasswordRoleOidIndexId, on pg_auth_password using btree(roleid oid_ops, name name_ops));
 
 #endif							/* PG_AUTH_PASSWORD_H */
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index ce39db5a491..7223b1fdae4 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -24,7 +24,7 @@ typedef void (*check_password_hook_type) (const char *username, const char *shad
 
 extern PGDLLIMPORT check_password_hook_type check_password_hook;
 
-extern bool is_role_valid(const char *rolename, char **logdetail);
+extern bool is_role_valid(const char *rolename, const char **logdetail);
 extern Oid	CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
 extern Oid	AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
 extern Oid	AlterRoleSet(AlterRoleSetStmt *stmt);
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index b8ff8ccb417..84c9c4b4f48 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -35,7 +35,7 @@ extern PasswordType get_password_type(const char *shadow_pass);
 extern char *encrypt_password(PasswordType target_type, const char *role,
 							  const char *password);
 
-extern char *get_role_password(const char *role, const char **logdetail);
+extern char **get_role_passwords(const char *role, const char **logdetail, int *num);
 
 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/sasl.h b/src/include/libpq/sasl.h
index 71cc0dc2514..9892f9aad38 100644
--- a/src/include/libpq/sasl.h
+++ b/src/include/libpq/sasl.h
@@ -77,7 +77,7 @@ typedef struct pg_be_sasl_mech
 	 *				 disclosing valid user names.
 	 *---------
 	 */
-	void	   *(*init) (Port *port, const char *mech, const char *shadow_pass);
+	void	   *(*init) (Port *port, const char *mech, const char **secrets, const int num_secrets);
 
 	/*---------
 	 * exchange()
@@ -131,6 +131,6 @@ typedef struct pg_be_sasl_mech
 
 /* Common implementation for auth.c */
 extern int	CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port,
-						  char *shadow_pass, const char **logdetail);
+						  const char **passwords, int num, const char **logdetail);
 
 #endif							/* PG_SASL_H */
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index e60992a0d2d..8cc6f492eeb 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -21,7 +21,7 @@
 extern const pg_be_sasl_mech pg_be_scram_mech;
 
 /* Routines to handle and check SCRAM-SHA-256 secret */
-extern char *pg_be_scram_build_secret(const char *password);
+extern char *pg_be_scram_build_secret(const char *password, const char *salt);
 extern bool parse_scram_secret(const char *secret, int *iterations, char **salt,
 							   uint8 *stored_key, uint8 *server_key);
 extern bool scram_verify_plain_password(const char *username,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bcef7eed2f3..e7239b34885 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -311,6 +311,7 @@ PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("passname", PASSNAME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index ab846e8b7fa..869cc4e4e20 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,7 +43,7 @@ enum SysCacheIdentifier
 	AUTHMEMROLEMEM,
 	AUTHNAME,
 	AUTHOID,
-	AUTHPASSWORD,
+	AUTHPASSWORDNAME,
 	CASTSOURCETARGET,
 	CLAAMNAMENSP,
 	CLAOID,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 4ffc41a5455..c6a84f86a84 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -120,7 +120,7 @@ CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
 -- should not contain the original salt.
 SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
     FROM pg_authid
-    LEFT JOIN pg_auth_password p 
+    LEFT JOIN pg_auth_password p
     ON pg_authid.oid = p.roleid
     WHERE rolname LIKE 'regress_passwd_sha_len%'
     ORDER BY rolname;
-- 
2.31.1