From b6ed8cfed14fa3eb272ec514f5e5a7d46944cf99 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 10 Apr 2017 14:33:51 +0900
Subject: [PATCH 2/5] Move routine to build SCRAM verifier into src/common/

This is aimed at being used by libpq to allow frontend-side creation
of SCRAM verifiers. The result is not anymore allocated by the routine
itself, the caller is responsible for that, similarly to md5.
---
 src/backend/libpq/auth-scram.c    | 79 ++++++---------------------------------
 src/backend/libpq/crypt.c         | 21 ++++++++++-
 src/common/scram-common.c         | 78 ++++++++++++++++++++++++++++++++++++++
 src/include/common/scram-common.h | 20 ++++++++++
 src/include/libpq/scram.h         |  5 +--
 5 files changed, 129 insertions(+), 74 deletions(-)

diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 75a261bd36..aa05662af2 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -204,9 +204,18 @@ pg_be_scram_init(const char *username, const char *shadow_pass)
 			 * The stored password is in plain format.  Generate a fresh SCRAM
 			 * verifier from it, and proceed with that.
 			 */
-			char	   *verifier;
+			char	   *verifier = palloc(SCRAM_VERIFIER_LEN + 1);
+			char		salt[SCRAM_SALT_LEN];
 
-			verifier = scram_build_verifier(username, shadow_pass, 0);
+			if (!pg_backend_random(salt, SCRAM_SALT_LEN))
+			{
+				ereport(LOG,
+						(errcode(ERRCODE_INTERNAL_ERROR),
+						 errmsg("could not generate random salt")));
+				return NULL;
+			}
+
+			(void) scram_build_verifier(username, shadow_pass, salt, 0, verifier);
 
 			(void) parse_scram_verifier(verifier, &state->salt, &state->iterations,
 										state->StoredKey, state->ServerKey);
@@ -360,72 +369,6 @@ pg_be_scram_exchange(void *opaq, char *input, int inputlen,
 	return result;
 }
 
-/*
- * Construct a verifier string for SCRAM, stored in pg_authid.rolpassword.
- *
- * If iterations is 0, default number of iterations is used.  The result is
- * palloc'd, so caller is responsible for freeing it.
- */
-char *
-scram_build_verifier(const char *username, const char *password,
-					 int iterations)
-{
-	uint8		keybuf[SCRAM_KEY_LEN + 1];
-	char	   *encoded_storedkey;
-	char	   *encoded_serverkey;
-	char		salt[SCRAM_SALT_LEN];
-	char	   *encoded_salt;
-	int			encoded_len;
-	char	   *prep_password = NULL;
-	pg_saslprep_rc rc;
-
-	/*
-	 * Normalize the password with SASLprep.  If that doesn't work, because
-	 * the password isn't valid UTF-8 or contains prohibited characters, just
-	 * proceed with the original password.  (See comments at top of file.)
-	 */
-	rc = pg_saslprep(password, &prep_password);
-	if (rc == SASLPREP_SUCCESS)
-		password = (const char *) prep_password;
-
-	if (iterations <= 0)
-		iterations = SCRAM_ITERATIONS_DEFAULT;
-
-	if (!pg_backend_random(salt, SCRAM_SALT_LEN))
-	{
-		ereport(LOG,
-				(errcode(ERRCODE_INTERNAL_ERROR),
-				 errmsg("could not generate random salt")));
-		return NULL;
-	}
-
-	encoded_salt = palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1);
-	encoded_len = pg_b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
-	encoded_salt[encoded_len] = '\0';
-
-	/* Calculate StoredKey, and encode it in base64 */
-	encoded_storedkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
-	scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
-							iterations, SCRAM_CLIENT_KEY_NAME, keybuf);
-	scram_H(keybuf, SCRAM_KEY_LEN, keybuf);		/* StoredKey */
-	encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN,
-								encoded_storedkey);
-	encoded_storedkey[encoded_len] = '\0';
-
-	/* And same for ServerKey */
-	encoded_serverkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
-	scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN, iterations,
-							SCRAM_SERVER_KEY_NAME, keybuf);
-	encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN,
-								encoded_serverkey);
-	encoded_serverkey[encoded_len] = '\0';
-
-	if (prep_password)
-		pfree(prep_password);
-
-	return psprintf("scram-sha-256:%s:%d:%s:%s", encoded_salt, iterations,
-					encoded_storedkey, encoded_serverkey);
-}
 
 /*
  * Verify a plaintext password against a SCRAM verifier.  This is used when
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 34beab5334..a0426f5450 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -20,9 +20,11 @@
 
 #include "catalog/pg_authid.h"
 #include "common/md5.h"
+#include "common/scram-common.h"
 #include "libpq/crypt.h"
 #include "libpq/scram.h"
 #include "miscadmin.h"
+#include "utils/backend_random.h"
 #include "utils/builtins.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
@@ -156,8 +158,23 @@ encrypt_password(PasswordType target_type, const char *role,
 			switch (guessed_type)
 			{
 				case PASSWORD_TYPE_PLAINTEXT:
-					return scram_build_verifier(role, password, 0);
-
+				{
+					char salt[SCRAM_SALT_LEN];
+
+					if (!pg_backend_random(salt, SCRAM_SALT_LEN))
+					{
+						ereport(ERROR,
+								(errcode(ERRCODE_INTERNAL_ERROR),
+								 errmsg("could not generate random salt")));
+						return NULL;
+					}
+
+					encrypted_password = palloc(SCRAM_VERIFIER_LEN + 1);
+					if (!scram_build_verifier(role, password, salt, 0,
+											  encrypted_password))
+						elog(ERROR, "password encryption failed");
+					return encrypted_password;
+				}
 				case PASSWORD_TYPE_MD5:
 
 					/*
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index df9f0eaa90..2e5ec0c6c0 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -23,6 +23,7 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
+#include "common/saslprep.h"
 #include "common/scram-common.h"
 
 #define HMAC_IPAD 0x36
@@ -165,3 +166,80 @@ scram_ClientOrServerKey(const char *password,
 	scram_HMAC_update(&ctx, keystr, strlen(keystr));
 	scram_HMAC_final(result, &ctx);
 }
+
+/*
+ * Construct a verifier string for SCRAM, stored in pg_authid.rolpassword.
+ *
+ * If iterations is 0, default number of iterations is used.  The result is
+ * stored in "verifier" that caller is responsible to allocate a buffer of
+ * size SCRAM_VERIFIER_LEN.  Returns true if the verifier has been generated,
+ * false otherwise.  It is important for this routine to do no memory
+ * allocations (SASLprep is an exception to that as the prepared password's
+ * length is estimated locally). The salt defined by the caller needs to be a
+ * buffer pre-filled with random data of length SCRAM_SALT_LEN.
+ */
+bool
+scram_build_verifier(const char *username, const char *password,
+					 const char *salt, int iterations, char *verifier)
+{
+	uint8		keybuf[SCRAM_KEY_LEN + 1];
+	char		intbuf[12];
+	char	   *p;
+	char	   *prep_password = NULL;
+	pg_saslprep_rc rc;
+
+	/*
+	 * Normalize the password with SASLprep.  If that doesn't work, because
+	 * the password isn't valid UTF-8 or contains prohibited characters, just
+	 * proceed with the original password.  (See comments at top of
+	 * auth-scram.c.)
+	 */
+	rc = pg_saslprep(password, &prep_password);
+	if (rc == SASLPREP_SUCCESS)
+		password = (const char *) prep_password;
+
+	if (iterations <= 0)
+		iterations = SCRAM_ITERATIONS_DEFAULT;
+
+	/* Fill in the data of the verifier */
+	p = verifier;
+	memcpy(p, SCRAM_VERIFIER_PREFIX, strlen(SCRAM_VERIFIER_PREFIX));
+	p += strlen(SCRAM_VERIFIER_PREFIX);
+	*p++ = ':';
+
+	/* salt */
+	(void) pg_b64_encode(salt, SCRAM_SALT_LEN, p);
+	p += pg_b64_enc_len(SCRAM_SALT_LEN);
+	*p++ = ':';
+
+	/* iterations */
+	sprintf(intbuf, "%d", iterations);
+	memcpy(p, intbuf, strlen(intbuf));
+	p += strlen(intbuf);
+	*p++ = ':';
+
+	/* Calculate StoredKey, and encode it in base64 */
+	scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+							iterations, SCRAM_CLIENT_KEY_NAME, keybuf);
+	scram_H(keybuf, SCRAM_KEY_LEN, keybuf);		/* StoredKey */
+	(void) pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN, p);
+	p += pg_b64_enc_len(SCRAM_KEY_LEN) - 1;
+	*p++ = ':';
+
+	/* And same for ServerKey */
+	scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN, iterations,
+							SCRAM_SERVER_KEY_NAME, keybuf);
+	(void) pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN, p);
+	p += pg_b64_enc_len(SCRAM_KEY_LEN) - 1;
+	*p++ = '\0';
+
+#ifndef FRONTEND
+	if (prep_password)
+		pfree(prep_password);
+#else
+	if (prep_password)
+		free(prep_password);
+#endif
+
+	return true;
+}
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
index 6740069eee..fcf3b98ba6 100644
--- a/src/include/common/scram-common.h
+++ b/src/include/common/scram-common.h
@@ -13,6 +13,7 @@
 #ifndef SCRAM_COMMON_H
 #define SCRAM_COMMON_H
 
+#include "common/base64.h"
 #include "common/sha2.h"
 
 /* Length of SCRAM keys (client and server) */
@@ -38,6 +39,22 @@
 #define SCRAM_SERVER_KEY_NAME "Server Key"
 #define SCRAM_CLIENT_KEY_NAME "Client Key"
 
+#define SCRAM_VERIFIER_PREFIX "scram-sha-256"
+
+/*
+ * Length of a SCRAM verifier, which is made of the following five fields
+ * separated by a colon:
+ * - prefix "scram-sha-256", made of 13 characters.
+ * - 4 colon separators.
+ * - 32-bit number of interations, up to 11 characters.
+ * - base64-encoded salt of length SCRAM_SALT_LEN
+ * - base64-encoded stored key of length SCRAM_KEY_LEN
+ * - base64-encoded server key of length SCRAM_KEY_LEN
+ */
+#define SCRAM_VERIFIER_LEN (strlen("scram-sha-256") + 4 + 11 + \
+							pg_b64_enc_len(SCRAM_SALT_LEN) + \
+							pg_b64_enc_len(SCRAM_KEY_LEN) * 2)
+
 /*
  * Context data for HMAC used in SCRAM authentication.
  */
@@ -55,5 +72,8 @@ extern void scram_H(const uint8 *str, int len, uint8 *result);
 extern void scram_ClientOrServerKey(const char *password, const char *salt,
 						int saltlen, int iterations,
 						const char *keystr, uint8 *result);
+extern bool scram_build_verifier(const char *username, const char *password,
+								 const char *salt, int iterations,
+								 char *verifier);
 
 #endif   /* SCRAM_COMMON_H */
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index e373f0c07e..803fc4ef7a 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -26,10 +26,7 @@ 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);
 
-/* Routines to handle and check SCRAM-SHA-256 verifier */
-extern char *scram_build_verifier(const char *username,
-					 const char *password,
-					 int iterations);
+/* Routines to check SCRAM-SHA-256 verifier */
 extern bool is_scram_verifier(const char *verifier);
 extern bool scram_verify_plain_password(const char *username,
 							const char *password, const char *verifier);
-- 
2.12.2

