From 8e1a53287a75e0000a4823b6aac569f08dcf1599 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 15 Mar 2017 23:45:50 +0900
Subject: [PATCH 3/4] 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.
---
 src/backend/libpq/auth-scram.c    | 57 ++----------------------------
 src/backend/libpq/crypt.c         |  7 +++-
 src/common/scram-common.c         | 74 +++++++++++++++++++++++++++++++++++++++
 src/include/common/scram-common.h | 20 +++++++++++
 src/include/libpq/scram.h         |  5 +--
 5 files changed, 103 insertions(+), 60 deletions(-)

diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 29e2965883..4ed0e968d7 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -171,14 +171,14 @@ pg_be_scram_init(const char *username, const char *shadow_pass, bool doomed)
 	}
 	else if (password_type == PASSWORD_TYPE_PLAINTEXT)
 	{
-		char	   *verifier;
+		char	   *verifier = palloc(SCRAM_VERIFIER_LEN + 1);
 
 		/*
 		 * The password provided is in plain format, in which case a fresh
 		 * SCRAM verifier can be generated and used for the rest of the
 		 * processing.
 		 */
-		verifier = scram_build_verifier(username, shadow_pass, 0);
+		(void) scram_build_verifier(username, shadow_pass, 0, verifier);
 
 		(void) parse_scram_verifier(verifier, &state->salt, &state->iterations,
 									state->StoredKey, state->ServerKey);
@@ -316,59 +316,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;
-
-	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';
-
-	return psprintf("scram-sha-256:%s:%d:%s:%s", encoded_salt, iterations,
-					encoded_storedkey, encoded_serverkey);
-}
-
 
 /*
  * 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..9a448fad11 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -20,6 +20,7 @@
 
 #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"
@@ -168,7 +169,11 @@ encrypt_password(PasswordType target_type, const char *role,
 			switch (guessed_type)
 			{
 				case PASSWORD_TYPE_PLAINTEXT:
-					return scram_build_verifier(role, password, 0);
+					encrypted_password = palloc(SCRAM_VERIFIER_LEN + 1);
+					if (!scram_build_verifier(role, password, 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 e44f38f652..00e45a800a 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -24,6 +24,11 @@
 #include <arpa/inet.h>
 
 #include "common/scram-common.h"
+#ifndef FRONTEND
+#include "utils/backend_random.h"
+#else
+#include "common/frontend_random.h"
+#endif
 
 #define HMAC_IPAD 0x36
 #define HMAC_OPAD 0x5C
@@ -184,3 +189,72 @@ 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.
+ */
+bool
+scram_build_verifier(const char *username, const char *password,
+					 int iterations, char *verifier)
+{
+	uint8		keybuf[SCRAM_KEY_LEN + 1];
+	char		salt[SCRAM_SALT_LEN];
+	char		intbuf[SCRAM_ITERATION_LEN];
+	char	   *p;
+
+	if (iterations <= 0)
+		iterations = SCRAM_ITERATIONS_DEFAULT;
+
+#ifdef FRONTEND
+	if (!pg_frontend_random(salt, SCRAM_SALT_LEN))
+		return false;
+#else
+	if (!pg_backend_random(salt, SCRAM_SALT_LEN))
+	{
+		ereport(LOG,
+				(errcode(ERRCODE_INTERNAL_ERROR),
+				 errmsg("could not generate random salt")));
+		return false;
+	}
+#endif
+
+	/* 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';
+
+	return true;
+}
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
index 7c98cc74d6..3fdfca44d6 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) */
@@ -41,6 +42,23 @@
 #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 10 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 + \
+							SCRAM_ITERATION_LEN + \
+							pg_b64_enc_len(SCRAM_SALT_LEN) + \
+							pg_b64_enc_len(SCRAM_KEY_LEN) * 2)
+
 /*
  * Context data for HMAC used in SCRAM authentication.
  */
@@ -58,5 +76,7 @@ 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,
+								 int iterations, char *verifier);
 
 #endif   /* SCRAM_COMMON_H */
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index 78a52db684..3aaa9ef18a 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, boo
 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);
 
 #endif   /* PG_SCRAM_H */
-- 
2.12.0

