From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri, 17 Mar 2017 09:33:27 +0000 (+0200)
Subject: Allow plaintext 'password' authentication when user has a SCRAM verifier.
X-Git-Tag: REL_10_BETA1~614
X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c6305a9c572aaf3509ea90eec5127340dab89546;p=postgresql

Allow plaintext 'password' authentication when user has a SCRAM verifier.

Oversight in the main SCRAM patch.
---

diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 9f78e57aae..db15a2fac6 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..ac10751ec2 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,21 @@ 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
+			{
+				*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 +321,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 */