On Tue, Dec 20, 2022 at 08:58:38AM +0900, Michael Paquier wrote:
> Thanks!  I have applied for I have here..  There are other pieces to
> think about in this area.

FYI, I have spent a few hours looking at the remaining parts of the
SCRAM code that could be simplified if a new hash method is added, and
this b3bb7d1 has really made things easier.  There are a few things
that will need more thoughts.  Here are my notes, assuming that
SHA-512 is done:
1) HBA entries had better use a new keyword for scram-sha-512, implying
a new uaSCRAM512 to combine with the existing uaSCRAM.  One reason
behind that it to advertise the mechanisms supported back to the
client depending on the matching HBA entry.
2) If a role has a SCRAM-SHA-256 password and the HBA entry matches
scram-sha-512, the SASL exchange needs to go through the mock process
with SHA-512 and fail.
3) If a role has a SCRAM-SHA-512 password and the HBA entry matches
scram-sha-256, the SASL exchange needs to go through the mock process
with SHA-256 and fail.
4) The case of MD5 is something that looks a bit tricky at quick
glance.  We know that if the role has a MD5 password stored, we will
fail anyway.  So we could just advertise the SHA-256 mechanisms in
this case and map the mock to that?
5) The mechanism choice in libpq needs to be reworked a bit based on
what the backend sends.  There may be no point in advertising all the
SHA-256 and SHA-512 mechanisms at the same time, I guess.

Attached is a WIP patch that I have played with.  This shows the parts
of the code that would need more thoughts if implementing such
things.  This works for the cases 1~3 (see the TAP tests).  I've given
up on the MD5 case 4 for now, but perhaps I just missed a simple trick.
5 in libpq uses dirty tricks.  I have marked this CF entry as
committed, and I'll come back to each relevant part on new separate
threads.
--
Michael
From 49a3c993a1df722da01f16839eb1d6185c205092 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@paquier.xyz>
Date: Tue, 20 Dec 2022 16:14:41 +0900
Subject: [PATCH] WIP SCRAM-SHA-512

---
 src/include/common/scram-common.h         |   5 +-
 src/include/libpq/crypt.h                 |   3 +-
 src/include/libpq/hba.h                   |   3 +-
 src/include/libpq/scram.h                 |   3 +-
 src/backend/libpq/auth-scram.c            | 143 ++++++++++++++++++----
 src/backend/libpq/auth.c                  |  15 ++-
 src/backend/libpq/crypt.c                 |  13 +-
 src/backend/libpq/hba.c                   |   5 +-
 src/backend/utils/misc/guc_tables.c       |   1 +
 src/common/scram-common.c                 |  13 +-
 src/interfaces/libpq/fe-auth-scram.c      |  24 +++-
 src/interfaces/libpq/fe-auth.c            |  52 +++++---
 src/interfaces/libpq/fe-auth.h            |   2 +
 src/test/authentication/t/001_password.pl |  21 +++-
 14 files changed, 236 insertions(+), 67 deletions(-)

diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
index 953d30ac54..10996fa735 100644
--- a/src/include/common/scram-common.h
+++ b/src/include/common/scram-common.h
@@ -19,15 +19,18 @@
 /* Name of SCRAM mechanisms per IANA */
 #define SCRAM_SHA_256_NAME "SCRAM-SHA-256"
 #define SCRAM_SHA_256_PLUS_NAME "SCRAM-SHA-256-PLUS"	/* with channel binding */
+#define SCRAM_SHA_512_NAME "SCRAM-SHA-512"
+#define SCRAM_SHA_512_PLUS_NAME "SCRAM-SHA-512-PLUS"	/* with channel binding */
 
 /* Length of SCRAM keys (client and server) */
 #define SCRAM_SHA_256_KEY_LEN				PG_SHA256_DIGEST_LENGTH
+#define SCRAM_SHA_512_KEY_LEN				PG_SHA512_DIGEST_LENGTH
 
 /*
  * Size of buffers used internally by SCRAM routines, that should be the
  * maximum of SCRAM_SHA_*_KEY_LEN among the hash methods supported.
  */
-#define SCRAM_MAX_KEY_LEN					SCRAM_SHA_256_KEY_LEN
+#define SCRAM_MAX_KEY_LEN					SCRAM_SHA_512_KEY_LEN
 
 /*
  * Size of random nonce generated in the authentication exchange.  This
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 3238cf66d3..992d4922fc 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -28,7 +28,8 @@ typedef enum PasswordType
 {
 	PASSWORD_TYPE_PLAINTEXT = 0,
 	PASSWORD_TYPE_MD5,
-	PASSWORD_TYPE_SCRAM_SHA_256
+	PASSWORD_TYPE_SCRAM_SHA_256,
+	PASSWORD_TYPE_SCRAM_SHA_512
 } PasswordType;
 
 extern PasswordType get_password_type(const char *shadow_pass);
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 90c51ad6fa..dc536225ef 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -30,7 +30,8 @@ typedef enum UserAuth
 	uaIdent,
 	uaPassword,
 	uaMD5,
-	uaSCRAM,
+	uaSCRAM256,
+	uaSCRAM512,
 	uaGSS,
 	uaSSPI,
 	uaPAM,
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index b29501ef96..f2aa5c82c2 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -22,7 +22,8 @@
 extern PGDLLIMPORT 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,
+									  pg_cryptohash_type hash_type);
 extern bool parse_scram_secret(const char *secret,
 							   int *iterations,
 							   pg_cryptohash_type *hash_type,
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 126eb70974..01cf5da754 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -181,7 +181,9 @@ static char *build_server_first_message(scram_state *state);
 static char *build_server_final_message(scram_state *state);
 static bool verify_client_proof(scram_state *state);
 static bool verify_final_nonce(scram_state *state);
-static void mock_scram_secret(const char *username, pg_cryptohash_type *hash_type,
+static void mock_scram_secret(const char *username,
+							  pg_cryptohash_type mock_hash,
+							  pg_cryptohash_type *hash_type,
 							  int *iterations, int *key_length, char **salt,
 							  uint8 *stored_key, uint8 *server_key);
 static bool is_scram_printable(char *p);
@@ -210,12 +212,28 @@ scram_get_mechanisms(Port *port, StringInfo buf)
 #ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH
 	if (port->ssl_in_use)
 	{
-		appendStringInfoString(buf, SCRAM_SHA_256_PLUS_NAME);
-		appendStringInfoChar(buf, '\0');
+		if (port->hba->auth_method == uaSCRAM512)
+		{
+			appendStringInfoString(buf, SCRAM_SHA_512_PLUS_NAME);
+			appendStringInfoChar(buf, '\0');
+		}
+		else
+		{
+			appendStringInfoString(buf, SCRAM_SHA_256_PLUS_NAME);
+			appendStringInfoChar(buf, '\0');
+		}
 	}
 #endif
-	appendStringInfoString(buf, SCRAM_SHA_256_NAME);
-	appendStringInfoChar(buf, '\0');
+	if (port->hba->auth_method == uaSCRAM512)
+	{
+		appendStringInfoString(buf, SCRAM_SHA_512_NAME);
+		appendStringInfoChar(buf, '\0');
+	}
+	else
+	{
+		appendStringInfoString(buf, SCRAM_SHA_256_NAME);
+		appendStringInfoChar(buf, '\0');
+	}
 }
 
 /*
@@ -238,11 +256,14 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
 {
 	scram_state *state;
 	bool		got_secret;
+	pg_cryptohash_type mock_hash;
 
 	state = (scram_state *) palloc0(sizeof(scram_state));
 	state->port = port;
 	state->state = SCRAM_AUTH_INIT;
 
+	mock_hash = PG_SHA256;
+
 	/*
 	 * Parse the selected mechanism.
 	 *
@@ -253,17 +274,42 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
 	 * selecting any other SASL mechanism we don't support.
 	 */
 #ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH
-	if (strcmp(selected_mech, SCRAM_SHA_256_PLUS_NAME) == 0 && port->ssl_in_use)
+	if ((strcmp(selected_mech, SCRAM_SHA_256_PLUS_NAME) == 0 ||
+		 strcmp(selected_mech, SCRAM_SHA_512_PLUS_NAME) == 0) &&
+		port->ssl_in_use)
 		state->channel_binding_in_use = true;
 	else
 #endif
-	if (strcmp(selected_mech, SCRAM_SHA_256_NAME) == 0)
+	if (strcmp(selected_mech, SCRAM_SHA_256_NAME) == 0 ||
+		strcmp(selected_mech, SCRAM_SHA_512_NAME) == 0)
 		state->channel_binding_in_use = false;
 	else
 		ereport(ERROR,
 				(errcode(ERRCODE_PROTOCOL_VIOLATION),
 				 errmsg("client selected an invalid SASL authentication mechanism")));
 
+	/*
+	 * If the HBA entry does not match with the selected mechanism, the
+	 * authentication should fail, so go through the mock process in this
+	 * case.
+	 */
+	if (port->hba->auth_method == uaSCRAM256 &&
+		strcmp(selected_mech, SCRAM_SHA_256_PLUS_NAME) != 0 &&
+		strcmp(selected_mech, SCRAM_SHA_256_NAME) != 0)
+	{
+		/* the client has selected one of the SHA-512 mechanisms */
+		shadow_pass = NULL;
+		mock_hash = PG_SHA512;
+	}
+	if (port->hba->auth_method == uaSCRAM512 &&
+		strcmp(selected_mech, SCRAM_SHA_512_PLUS_NAME) != 0 &&
+		strcmp(selected_mech, SCRAM_SHA_512_NAME) != 0)
+	{
+		/* the client has selected one of the SHA-256 mechanisms */
+		shadow_pass = NULL;
+		mock_hash = PG_SHA256;
+	}
+
 	/*
 	 * Parse the stored secret.
 	 */
@@ -271,15 +317,37 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
 	{
 		int			password_type = get_password_type(shadow_pass);
 
-		if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
+		if (password_type == PASSWORD_TYPE_SCRAM_SHA_256 ||
+			password_type == PASSWORD_TYPE_SCRAM_SHA_512)
 		{
-			if (parse_scram_secret(shadow_pass, &state->iterations,
-								   &state->hash_type, &state->key_length,
-								   &state->salt,
-								   state->StoredKey,
-								   state->ServerKey))
-				got_secret = true;
-			else
+			got_secret = parse_scram_secret(shadow_pass,
+											&state->iterations,
+											&state->hash_type,
+											&state->key_length,
+											&state->salt,
+											state->StoredKey,
+											state->ServerKey);
+
+			/*
+			 * At this point, we already know that the HBA entry maps
+			 * with a matching mechanism.  If the password type does not
+			 * not match any of that, do a mock processing using the
+			 * hash selected by the client's mechanism.
+			 */
+			if (port->hba->auth_method == uaSCRAM256 &&
+				password_type != PASSWORD_TYPE_SCRAM_SHA_256)
+			{
+				mock_hash = PG_SHA256;
+				got_secret = false;
+			}
+			else if (port->hba->auth_method == uaSCRAM512 &&
+					 password_type != PASSWORD_TYPE_SCRAM_SHA_512)
+			{
+				mock_hash = PG_SHA512;
+				got_secret = false;
+			}
+
+			if (!got_secret)
 			{
 				/*
 				 * The password looked like a SCRAM secret, but could not be
@@ -288,7 +356,6 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
 				ereport(LOG,
 						(errmsg("invalid SCRAM secret for user \"%s\"",
 								state->port->user_name)));
-				got_secret = false;
 			}
 		}
 		else
@@ -309,6 +376,7 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
 		 * considered normal, since the caller requested it, so don't set log
 		 * detail.
 		 */
+		mock_hash = PG_SHA256;
 		got_secret = false;
 	}
 
@@ -320,7 +388,8 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
 	 */
 	if (!got_secret)
 	{
-		mock_scram_secret(state->port->user_name, &state->hash_type,
+		mock_scram_secret(state->port->user_name, mock_hash,
+						  &state->hash_type,
 						  &state->iterations, &state->key_length,
 						  &state->salt,
 						  state->StoredKey, state->ServerKey);
@@ -471,7 +540,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, pg_cryptohash_type hash_type)
 {
 	char	   *prep_password;
 	pg_saslprep_rc rc;
@@ -494,7 +563,9 @@ pg_be_scram_build_secret(const char *password)
 				(errcode(ERRCODE_INTERNAL_ERROR),
 				 errmsg("could not generate random salt")));
 
-	result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN,
+	result = scram_build_secret(hash_type,
+								(hash_type == PG_SHA512) ?
+								SCRAM_SHA_512_KEY_LEN : SCRAM_SHA_256_KEY_LEN,
 								saltbuf, SCRAM_DEFAULT_SALT_LEN,
 								SCRAM_DEFAULT_ITERATIONS, password,
 								&errstr);
@@ -622,10 +693,18 @@ parse_scram_secret(const char *secret, int *iterations,
 		goto invalid_secret;
 
 	/* Parse the fields */
-	if (strcmp(scheme_str, "SCRAM-SHA-256") != 0)
+	if (strcmp(scheme_str, "SCRAM-SHA-256") == 0)
+	{
+		*hash_type = PG_SHA256;
+		*key_length = SCRAM_SHA_256_KEY_LEN;
+	}
+	else if (strcmp(scheme_str, "SCRAM-SHA-512") == 0)
+	{
+		*hash_type = PG_SHA512;
+		*key_length = SCRAM_SHA_512_KEY_LEN;
+	}
+	else
 		goto invalid_secret;
-	*hash_type = PG_SHA256;
-	*key_length = SCRAM_SHA_256_KEY_LEN;
 
 	errno = 0;
 	*iterations = strtol(iterations_str, &p, 10);
@@ -675,14 +754,16 @@ invalid_secret:
  *
  * In a normal authentication, these are extracted from the secret
  * stored in the server.  This function generates values that look
- * realistic, for when there is no stored secret, using SCRAM-SHA-256.
+ * realistic, for when there is no stored secret, using the hash method
+ * specified by mock_hash.
  *
  * Like in parse_scram_secret(), for 'stored_key' and 'server_key', the
  * caller must pass pre-allocated buffers of size SCRAM_MAX_KEY_LEN, and
  * the buffer for the salt is palloc'd by this function.
  */
 static void
-mock_scram_secret(const char *username, pg_cryptohash_type *hash_type,
+mock_scram_secret(const char *username, pg_cryptohash_type mock_hash,
+				  pg_cryptohash_type *hash_type,
 				  int *iterations, int *key_length, char **salt,
 				  uint8 *stored_key, uint8 *server_key)
 {
@@ -691,8 +772,16 @@ mock_scram_secret(const char *username, pg_cryptohash_type *hash_type,
 	int			encoded_len;
 
 	/* Enforce the use of SHA-256, which would be realistic enough */
-	*hash_type = PG_SHA256;
-	*key_length = SCRAM_SHA_256_KEY_LEN;
+	if (mock_hash == PG_SHA256)
+	{
+		*hash_type = PG_SHA256;
+		*key_length = SCRAM_SHA_256_KEY_LEN;
+	}
+	else
+	{
+		*hash_type = PG_SHA512;
+		*key_length = SCRAM_SHA_512_KEY_LEN;
+	}
 
 	/*
 	 * Generate deterministic salt.
@@ -1477,7 +1566,7 @@ scram_mock_salt(const char *username, pg_cryptohash_type hash_type,
 	 * This may be worth refreshing if support for more hash methods is\
 	 * added.
 	 */
-	Assert(hash_type == PG_SHA256);
+	Assert(hash_type == PG_SHA256 || hash_type == PG_SHA512);
 
 	ctx = pg_cryptohash_create(hash_type);
 	if (pg_cryptohash_init(ctx) < 0 ||
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index e2f723d188..6109953e8b 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -276,7 +276,8 @@ auth_failed(Port *port, int status, const char *logdetail)
 			break;
 		case uaPassword:
 		case uaMD5:
-		case uaSCRAM:
+		case uaSCRAM256:
+		case uaSCRAM512:
 			errstr = gettext_noop("password authentication failed for user \"%s\"");
 			/* We use it to indicate if a .pgpass password failed. */
 			errcode_return = ERRCODE_INVALID_PASSWORD;
@@ -585,7 +586,8 @@ ClientAuthentication(Port *port)
 			break;
 
 		case uaMD5:
-		case uaSCRAM:
+		case uaSCRAM256:
+		case uaSCRAM512:
 			status = CheckPWChallengeAuth(port, &logdetail);
 			break;
 
@@ -806,7 +808,8 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
 	char	   *shadow_pass;
 	PasswordType pwtype;
 
-	Assert(port->hba->auth_method == uaSCRAM ||
+	Assert(port->hba->auth_method == uaSCRAM256 ||
+		   port->hba->auth_method == uaSCRAM512 ||
 		   port->hba->auth_method == uaMD5);
 
 	/* First look up the user's password. */
@@ -828,9 +831,9 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
 
 	/*
 	 * If 'md5' authentication is allowed, decide whether to perform 'md5' or
-	 * 'scram-sha-256' 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 secret, we must do SCRAM authentication.
+	 * 'scram-sha-{256,512}' 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 secret, we must do SCRAM authentication.
 	 *
 	 * If MD5 authentication is not allowed, always use SCRAM.  If the user
 	 * had an MD5 password, CheckSASLAuth() with the SCRAM mechanism will
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index a81af0749a..3c0f49cb05 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -101,7 +101,12 @@ get_password_type(const char *shadow_pass)
 		return PASSWORD_TYPE_MD5;
 	if (parse_scram_secret(shadow_pass, &iterations, &hash_type, &key_length,
 						   &encoded_salt, stored_key, server_key))
-		return PASSWORD_TYPE_SCRAM_SHA_256;
+	{
+		if (hash_type == PG_SHA512)
+			return PASSWORD_TYPE_SCRAM_SHA_512;
+		else
+			return PASSWORD_TYPE_SCRAM_SHA_256;
+	}
 	return PASSWORD_TYPE_PLAINTEXT;
 }
 
@@ -140,7 +145,10 @@ encrypt_password(PasswordType target_type, const char *role,
 			return encrypted_password;
 
 		case PASSWORD_TYPE_SCRAM_SHA_256:
-			return pg_be_scram_build_secret(password);
+			return pg_be_scram_build_secret(password, PG_SHA256);
+
+		case PASSWORD_TYPE_SCRAM_SHA_512:
+			return pg_be_scram_build_secret(password, PG_SHA512);
 
 		case PASSWORD_TYPE_PLAINTEXT:
 			elog(ERROR, "cannot encrypt password with 'plaintext'");
@@ -235,6 +243,7 @@ plain_crypt_verify(const char *role, const char *shadow_pass,
 	switch (get_password_type(shadow_pass))
 	{
 		case PASSWORD_TYPE_SCRAM_SHA_256:
+		case PASSWORD_TYPE_SCRAM_SHA_512:
 			if (scram_verify_plain_password(role,
 											client_pass,
 											shadow_pass))
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 870b907697..53fd31de72 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -117,6 +117,7 @@ static const char *const UserAuthName[] =
 	"password",
 	"md5",
 	"scram-sha-256",
+	"scram-sha-512",
 	"gss",
 	"sspi",
 	"pam",
@@ -1776,7 +1777,9 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
 		parsedline->auth_method = uaMD5;
 	}
 	else if (strcmp(token->string, "scram-sha-256") == 0)
-		parsedline->auth_method = uaSCRAM;
+		parsedline->auth_method = uaSCRAM256;
+	else if (strcmp(token->string, "scram-sha-512") == 0)
+		parsedline->auth_method = uaSCRAM512;
 	else if (strcmp(token->string, "pam") == 0)
 #ifdef USE_PAM
 		parsedline->auth_method = uaPAM;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1bf14eec66..564173407a 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -383,6 +383,7 @@ static const struct config_enum_entry plan_cache_mode_options[] = {
 static const struct config_enum_entry password_encryption_options[] = {
 	{"md5", PASSWORD_TYPE_MD5, false},
 	{"scram-sha-256", PASSWORD_TYPE_SCRAM_SHA_256, false},
+	{"scram-sha-512", PASSWORD_TYPE_SCRAM_SHA_512, false},
 	{NULL, 0, false}
 };
 
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index bffbbb4317..d06669f2ee 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -203,6 +203,7 @@ scram_build_secret(pg_cryptohash_type hash_type, int key_length,
 	uint8		salted_password[SCRAM_MAX_KEY_LEN];
 	uint8		stored_key[SCRAM_MAX_KEY_LEN];
 	uint8		server_key[SCRAM_MAX_KEY_LEN];
+	char	   *header;
 	char	   *result;
 	char	   *p;
 	int			maxlen;
@@ -212,7 +213,8 @@ scram_build_secret(pg_cryptohash_type hash_type, int key_length,
 	int			encoded_result;
 
 	/* Only this hash method is supported currently */
-	Assert(hash_type == PG_SHA256);
+	Assert(hash_type == PG_SHA256 ||
+		   hash_type == PG_SHA512);
 
 	if (iterations <= 0)
 		iterations = SCRAM_DEFAULT_ITERATIONS;
@@ -246,7 +248,12 @@ scram_build_secret(pg_cryptohash_type hash_type, int key_length,
 	encoded_stored_len = pg_b64_enc_len(key_length);
 	encoded_server_len = pg_b64_enc_len(key_length);
 
-	maxlen = strlen("SCRAM-SHA-256") + 1
+	if (hash_type == PG_SHA256)
+		header = "SCRAM-SHA-256";
+	else
+		header = "SCRAM-SHA-512";
+
+	maxlen = strlen(header) + 1
 		+ 10 + 1				/* iteration count */
 		+ encoded_salt_len + 1	/* Base64-encoded salt */
 		+ encoded_stored_len + 1	/* Base64-encoded StoredKey */
@@ -263,7 +270,7 @@ scram_build_secret(pg_cryptohash_type hash_type, int key_length,
 	result = palloc(maxlen);
 #endif
 
-	p = result + sprintf(result, "SCRAM-SHA-256$%d:", iterations);
+	p = result + sprintf(result, "%s$%d:", header, iterations);
 
 	/* salt */
 	encoded_result = pg_b64_encode(salt, saltlen, p, encoded_salt_len);
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index 7410d5ba52..1f6c47a942 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -110,8 +110,17 @@ scram_init(PGconn *conn,
 	memset(state, 0, sizeof(fe_scram_state));
 	state->conn = conn;
 	state->state = FE_SCRAM_INIT;
-	state->key_length = SCRAM_SHA_256_KEY_LEN;
-	state->hash_type = PG_SHA256;
+	if (strcmp(sasl_mechanism, SCRAM_SHA_512_NAME) == 0 ||
+		strcmp(sasl_mechanism, SCRAM_SHA_512_PLUS_NAME) == 0)
+	{
+		state->key_length = SCRAM_SHA_512_KEY_LEN;
+		state->hash_type = PG_SHA512;
+	}
+	else
+	{
+		state->key_length = SCRAM_SHA_256_KEY_LEN;
+		state->hash_type = PG_SHA256;
+	}
 
 	state->sasl_mechanism = strdup(sasl_mechanism);
 	if (!state->sasl_mechanism)
@@ -165,7 +174,8 @@ scram_channel_bound(void *opaq)
 		return false;
 
 	/* channel binding mechanism not used */
-	if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0)
+	if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0 &&
+		strcmp(state->sasl_mechanism, SCRAM_SHA_512_PLUS_NAME) != 0)
 		return false;
 
 	/* all clear! */
@@ -894,7 +904,8 @@ verify_server_signature(fe_scram_state *state, bool *match,
  * error details.
  */
 char *
-pg_fe_scram_build_secret(const char *password, const char **errstr)
+pg_fe_scram_build_secret(const char *password, pg_cryptohash_type hash_type,
+						 const char **errstr)
 {
 	char	   *prep_password;
 	pg_saslprep_rc rc;
@@ -924,7 +935,10 @@ pg_fe_scram_build_secret(const char *password, const char **errstr)
 		return NULL;
 	}
 
-	result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN, saltbuf,
+	result = scram_build_secret(hash_type,
+								(hash_type == PG_SHA512) ?
+								SCRAM_SHA_512_KEY_LEN : SCRAM_SHA_256_KEY_LEN,
+								saltbuf,
 								SCRAM_DEFAULT_SALT_LEN,
 								SCRAM_DEFAULT_ITERATIONS, password,
 								errstr);
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 4a6c358bb6..6b17ebcf9d 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -431,8 +431,8 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 	/*
 	 * Parse the list of SASL authentication mechanisms in the
 	 * AuthenticationSASL message, and select the best mechanism that we
-	 * support.  SCRAM-SHA-256-PLUS and SCRAM-SHA-256 are the only ones
-	 * supported at the moment, listed by order of decreasing importance.
+	 * support.  SCRAM-SHA-256[-PLUS] and SCRAM-SHA-512[-PLUS] are the only
+	 * ones supported at the moment, listed by order of decreasing importance.
 	 */
 	selected_mechanism = NULL;
 	for (;;)
@@ -451,17 +451,19 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 			break;
 
 		/*
-		 * Select the mechanism to use.  Pick SCRAM-SHA-256-PLUS over anything
-		 * else if a channel binding type is set and if the client supports it
-		 * (and did not set channel_binding=disable). Pick SCRAM-SHA-256 if
-		 * nothing else has already been picked.  If we add more mechanisms, a
-		 * more refined priority mechanism might become necessary.
+		 * Select the mechanism to use.  Pick SCRAM-SHA-{256,512}-PLUS over
+		 * anything else if a channel binding type is set and if the client
+		 * supports it (and did not set channel_binding=disable). Pick
+		 * SCRAM-SHA-{256,512} if nothing else has already been picked.  If
+		 * we add more mechanisms, a more refined priority mechanism might
+		 * become necessary.
 		 */
-		if (strcmp(mechanism_buf.data, SCRAM_SHA_256_PLUS_NAME) == 0)
+		if (strcmp(mechanism_buf.data, SCRAM_SHA_512_PLUS_NAME) == 0 ||
+			strcmp(mechanism_buf.data, SCRAM_SHA_256_PLUS_NAME) == 0)
 		{
 			if (conn->ssl_in_use)
 			{
-				/* The server has offered SCRAM-SHA-256-PLUS. */
+				/* The server has offered SCRAM-SHA-{256,512}-PLUS. */
 
 #ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
 				/*
@@ -470,7 +472,10 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 				 */
 				if (conn->channel_binding[0] != 'd')	/* disable */
 				{
-					selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
+					if (strcmp(mechanism_buf.data, SCRAM_SHA_512_PLUS_NAME) == 0)
+						selected_mechanism = SCRAM_SHA_512_PLUS_NAME;
+					else
+						selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
 					conn->sasl = &pg_scram_mech;
 				}
 #else
@@ -478,7 +483,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 				 * The client does not support channel binding.  If it is
 				 * required, complain immediately instead of the error below
 				 * which would be confusing as the server is publishing
-				 * SCRAM-SHA-256-PLUS.
+				 * SCRAM-SHA-{256,512}-PLUS.
 				 */
 				if (conn->channel_binding[0] == 'r')	/* require */
 				{
@@ -490,18 +495,25 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 			else
 			{
 				/*
-				 * The server offered SCRAM-SHA-256-PLUS, but the connection
-				 * is not SSL-encrypted. That's not sane. Perhaps SSL was
-				 * stripped by a proxy? There's no point in continuing,
+				 * The server offered SCRAM-SHA-{256,512}-PLUS, but the
+				 * connection is not SSL-encrypted. That's not sane. Perhaps
+				 * SSL was stripped by a proxy? There's no point in continuing,
 				 * because the server will reject the connection anyway if we
 				 * try authenticate without channel binding even though both
 				 * the client and server supported it. The SCRAM exchange
 				 * checks for that, to prevent downgrade attacks.
 				 */
-				libpq_append_conn_error(conn, "server offered SCRAM-SHA-256-PLUS authentication over a non-SSL connection");
+				libpq_append_conn_error(conn, "server offered %s authentication over a non-SSL connection",
+										mechanism_buf.data);
 				goto error;
 			}
 		}
+		else if (strcmp(mechanism_buf.data, SCRAM_SHA_512_NAME) == 0 &&
+				 !selected_mechanism)
+		{
+			selected_mechanism = SCRAM_SHA_512_NAME;
+			conn->sasl = &pg_scram_mech;
+		}
 		else if (strcmp(mechanism_buf.data, SCRAM_SHA_256_NAME) == 0 &&
 				 !selected_mechanism)
 		{
@@ -517,7 +529,8 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 	}
 
 	if (conn->channel_binding[0] == 'r' &&	/* require */
-		strcmp(selected_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0)
+		strcmp(selected_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0 &&
+		strcmp(selected_mechanism, SCRAM_SHA_512_PLUS_NAME) != 0)
 	{
 		libpq_append_conn_error(conn, "channel binding is required, but server did not offer an authentication method that supports channel binding");
 		goto error;
@@ -1249,11 +1262,14 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
 	/*
 	 * Ok, now we know what algorithm to use
 	 */
-	if (strcmp(algorithm, "scram-sha-256") == 0)
+	if (strcmp(algorithm, "scram-sha-256") == 0 ||
+		strcmp(algorithm, "scram-sha-512") == 0)
 	{
 		const char *errstr = NULL;
+		pg_cryptohash_type hash_type =
+			(strcmp(algorithm, "scram-sha-512") == 0) ? PG_SHA512 : PG_SHA256;
 
-		crypt_pwd = pg_fe_scram_build_secret(passwd, &errstr);
+		crypt_pwd = pg_fe_scram_build_secret(passwd, hash_type, &errstr);
 		if (!crypt_pwd)
 			libpq_append_conn_error(conn, "could not encrypt password: %s", errstr);
 	}
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 049a8bb1a1..1f6dd52987 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -17,6 +17,7 @@
 #include "libpq-fe.h"
 #include "libpq-int.h"
 
+#include "common/cryptohash.h"
 
 /* Prototypes for functions in fe-auth.c */
 extern int	pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn);
@@ -26,6 +27,7 @@ extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
 /* Mechanisms in fe-auth-scram.c */
 extern const pg_fe_sasl_mech pg_scram_mech;
 extern char *pg_fe_scram_build_secret(const char *password,
+									  pg_cryptohash_type hash_type,
 									  const char **errstr);
 
 #endif							/* FE_AUTH_H */
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 42d3d4c79b..12026dd222 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -66,11 +66,14 @@ $node->init;
 $node->append_conf('postgresql.conf', "log_connections = on\n");
 $node->start;
 
-# Create 3 roles with different password methods for each one. The same
+# Create 4 roles with different password methods for each one. The same
 # password is used for all of them.
 $node->safe_psql('postgres',
 	"SET password_encryption='scram-sha-256'; CREATE ROLE scram_role LOGIN PASSWORD 'pass';"
 );
+$node->safe_psql('postgres',
+	"SET password_encryption='scram-sha-512'; CREATE ROLE scram_role_512 LOGIN PASSWORD 'pass';"
+);
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
@@ -134,6 +137,8 @@ test_conn(
 	log_like => [
 		qr/connection authenticated: identity="scram_role" method=scram-sha-256/
 	]);
+test_conn($node, 'user=scram_role_512', 'scram-sha-256', 2,
+	log_unlike => [qr/connection authenticated:/]);
 test_conn($node, 'user=md5_role', 'scram-sha-256', 2,
 	log_unlike => [qr/connection authenticated:/]);
 
@@ -143,6 +148,20 @@ test_conn($node, 'user=scram_role', 'scram-sha-256', 2,
 	log_unlike => [qr/connection authenticated:/]);
 $ENV{"PGPASSWORD"} = 'pass';
 
+# For "scram-sha-512" method, user "scram_role_512" should be able to connect.
+reset_pg_hba($node, 'all', 'all', 'scram-sha-512');
+test_conn(
+	$node,
+	'user=scram_role_512',
+	'scram-sha-512',
+	0,
+	log_like => [
+		qr/connection authenticated: identity="scram_role_512" method=scram-sha-512/
+	]);
+test_conn($node, 'user=scram_role_256', 'scram-sha-256', 2,
+	log_unlike => [qr/connection authenticated:/]);
+
+
 # For "md5" method, all users should be able to connect (SCRAM
 # authentication will be performed for the user with a SCRAM secret.)
 reset_pg_hba($node, 'all', 'all', 'md5');
-- 
2.39.0

Attachment: signature.asc
Description: PGP signature

Reply via email to