From fd200d76b40736af8ee98b09ddaf62ddc5ed9d72 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 14 Mar 2017 14:26:50 +0200
Subject: [PATCH 1/2] Allow plaintext 'password' authentication when user has a
 SCRAM verifier.

---
 src/backend/libpq/auth-scram.c | 46 +++++++++++++++++++++++++++++++
 src/backend/libpq/crypt.c      | 62 +++++++++++++++++++++++++++---------------
 src/include/libpq/scram.h      |  2 ++
 3 files changed, 88 insertions(+), 22 deletions(-)

diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 9f78e57aae..0833759afb 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -364,6 +364,52 @@ scram_build_verifier(const char *username, const char *password,
 	return psprintf("scram-sha-256:%s:%d:%s:%s", encoded_salt, iterations, storedkey_hex, serverkey_hex);
 }
 
+/*
+ * Verify a plaintext password against a SCRAM verifier. This is used when
+ * performing plaintext password authentication for a user that has a SCRAM
+ * verifier stored in pg_authid.
+ */
+bool
+scram_verify_plain_password(const char *username, const char *password,
+							const char *verifier)
+{
+	char	   *encoded_salt;
+	char	   *salt;
+	int			saltlen;
+	int			iterations;
+	uint8		stored_key[SCRAM_KEY_LEN];
+	uint8		server_key[SCRAM_KEY_LEN];
+	uint8		computed_key[SCRAM_KEY_LEN];
+
+	if (!parse_scram_verifier(verifier, &encoded_salt, &iterations,
+							  stored_key, server_key))
+	{
+		/*
+		 * The password looked like a SCRAM verifier, but could not be
+		 * parsed.
+		 */
+		elog(LOG, "invalid SCRAM verifier for user \"%s\"", username);
+		return false;
+	}
+
+	salt = palloc(pg_b64_dec_len(strlen(encoded_salt)));
+	saltlen = pg_b64_decode(encoded_salt, strlen(encoded_salt), salt);
+	if (saltlen == -1)
+	{
+		elog(LOG, "invalid SCRAM verifier for user \"%s\"", username);
+		return false;
+	}
+
+	/* Compute Server key based on the user-supplied plaintext password */
+	scram_ClientOrServerKey(password, salt, saltlen, iterations,
+							SCRAM_SERVER_KEY_NAME, computed_key);
+
+	/*
+	 * Compare the verifier's Server Key with the one computed from the
+	 * user-supplied password.
+	 */
+	return memcmp(computed_key, server_key, SCRAM_KEY_LEN) == 0;
+}
 
 /*
  * Check if given verifier can be used for SCRAM authentication.
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 9f0ae15b00..3ec0fd7b92 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -283,7 +283,6 @@ plain_crypt_verify(const char *role, const char *shadow_pass,
 				   const char *client_pass,
 				   char **logdetail)
 {
-	int			retval;
 	char		crypt_client_pass[MD5_PASSWD_LEN + 1];
 
 	/*
@@ -293,6 +292,22 @@ plain_crypt_verify(const char *role, const char *shadow_pass,
 	 */
 	switch (get_password_type(shadow_pass))
 	{
+		case PASSWORD_TYPE_SCRAM:
+			if (scram_verify_plain_password(role,
+											client_pass,
+											shadow_pass))
+			{
+				return STATUS_OK;
+			}
+			else
+			{
+				if (!*logdetail)
+					*logdetail = psprintf(_("Password does not match for user \"%s\"."),
+										  role);
+				return STATUS_ERROR;
+			}
+			break;
+
 		case PASSWORD_TYPE_MD5:
 			if (!pg_md5_encrypt(client_pass,
 								role,
@@ -307,30 +322,33 @@ plain_crypt_verify(const char *role, const char *shadow_pass,
 				 */
 				return STATUS_ERROR;
 			}
-			client_pass = crypt_client_pass;
+			if (strcmp(crypt_client_pass, shadow_pass) == 0)
+				return STATUS_OK;
+			else
+			{
+				*logdetail = psprintf(_("Password does not match for user \"%s\"."),
+									  role);
+				return STATUS_ERROR;
+			}
 			break;
+
 		case PASSWORD_TYPE_PLAINTEXT:
+			if (strcmp(client_pass, shadow_pass) == 0)
+				return STATUS_OK;
+			else
+			{
+				*logdetail = psprintf(_("Password does not match for user \"%s\"."),
+									  role);
+				return STATUS_ERROR;
+			}
 			break;
-
-		default:
-
-			/*
-			 * This shouldn't happen. Plain "password" authentication should
-			 * be possible with any kind of stored password hash.
-			 */
-			*logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
-								  role);
-			return STATUS_ERROR;
 	}
 
-	if (strcmp(client_pass, shadow_pass) == 0)
-		retval = STATUS_OK;
-	else
-	{
-		*logdetail = psprintf(_("Password does not match for user \"%s\"."),
-							  role);
-		retval = STATUS_ERROR;
-	}
-
-	return retval;
+	/*
+	 * This shouldn't happen. Plain "password" authentication is possible
+	 * with any kind of stored password hash.
+	 */
+	*logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
+						  role);
+	return STATUS_ERROR;
 }
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index 78a52db684..fb21e056c8 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -31,5 +31,7 @@ extern char *scram_build_verifier(const char *username,
 					 const char *password,
 					 int iterations);
 extern bool is_scram_verifier(const char *verifier);
+extern bool scram_verify_plain_password(const char *username,
+							const char *password, const char *verifier);
 
 #endif   /* PG_SCRAM_H */
-- 
2.11.0

