Refactor SCRAM code to dynamically handle hash type and key length
Hi all,
(Adding Daniel and Jonathan per recent threads)
While investigating on what it would take to extend SCRAM to use new
hash methods (say like the RFC draft for SCRAM-SHA-512), I have been
quickly reminded of the limitations created by SCRAM_KEY_LEN, which is
the key length that we use in the HMAC and hash computations when
creating a SCRAM verifier or when doing a SASL exchange.
Back in v10 when SCRAM was implemented, we have kept the
implementation simple and took the decision to rely heavily on buffers
with a static size of SCRAM_KEY_LEN during the exchanges. This was a
good choice back then, because we did not really have a way to handle
errors and there were no need to worry about things like OOMs or even
SHA computations errors. This was also incorrect in its own ways,
because we failed to go through OpenSSL for the hash/HMAC computations
which would be an issue with FIPS certifications. This has led to the
introduction of the new cryptohash and HMAC APIs, and the SCRAM code
has been changed so as it is able to know and pass through its layers
any errors (OOM, hash/MAC computation, OpenSSL issue), as of 87ae969
and e6bdfd9.
Taking advantage of all the error stack and logic introduced
previously, it becomes rather straight-forward to remove the
hardcoded assumptions behind SHA-256 in the SCRAM code path. Attached
is a patch that does so:
- SCRAM_KEY_LEN is removed from all the internal SCRAM routines, this
is replaced by a logic where the hash type and the key length are
stored in fe_scram_state for the frontend and scram_state for the
backend.
- The frontend assigns the hash type and the key length depending on
its choice of SASL mechanism in scram_init()@fe-auth-scram.c.
- The backend assigns the hash type and the key length based on the
parsed SCRAM entry from pg_authid, which works nicely when we need to
handle a raw password for a SASL exchange, for example. That's
basically what we do now, but scram_state is just changed to work
through it.
We have currently on HEAD 68 references to SCRAM_KEY_LEN. This brings
down these references to 6, that cannot really be avoided because we
still need to handle SCRAM-SHA-256 one way or another:
- When parsing a SCRAM password from pg_authid (to get a password type
or fill in scram_state in the backend).
- For the mock authentication, where SHA-256 is forced. We are going
to fail anyway, so any hash would be fine as long as we just let the
user know about the failure transparently.
- When initializing the exchange in libpq based on the SASL mechanism
choice.
- scram-common.h itself.
- When building a verifier in the be-fe interfaces. These could be
changed as well but I did not see a point in doing so yet.
- SCRAM_KEY_LEN is renamed to SCRAM_SHA_256_KEY_LEN to reflect its
dependency to SHA-256.
With this patch in place, the internals of SCRAM for SASL are
basically able to handle any hash methods. There is much more to
consider like how we'd need to treat uaSCRAM (separate code for more
hash methods or use the same) or HBA entries, but this removes what I
consider to be 70%~80% of the pain in terms of extensibility with the
current code, and this is enough to be a submission on its own to move
towards more methods. I am planning to tackle more things in terms of
pluggability after what's done here, btw :)
This patch passes check-world and the CI is green. I have tested as
well the patch with SCRAM verifiers coming from a server initially on
HEAD, so it looks pretty solid seen from here, being careful of memory
leaks in the frontend, mainly.
Thoughts or comments?
--
Michael
Attachments:
0001-Remove-dependency-to-hash-type-and-key-length-in-int.patchtext/x-diff; charset=us-asciiDownload
From 378c86619933d9c712730e3d6a105a79854660cf Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 14 Dec 2022 11:35:33 +0900
Subject: [PATCH] Remove dependency to hash type and key length in internal
SCRAM code
SCRAM_KEY_LEN had a hard dependency on SHA-256, making difficult the
addition of more hash methods in SCRAM with many statically-sized
buffers, as one problem.
---
src/include/common/scram-common.h | 25 ++--
src/include/libpq/scram.h | 8 +-
src/backend/libpq/auth-scram.c | 165 ++++++++++++++---------
src/backend/libpq/crypt.c | 10 +-
src/common/scram-common.c | 189 ++++++++++++++++++---------
src/interfaces/libpq/fe-auth-scram.c | 175 +++++++++++++++++--------
6 files changed, 380 insertions(+), 192 deletions(-)
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
index 4acf2a78ad..5b647e4b81 100644
--- a/src/include/common/scram-common.h
+++ b/src/include/common/scram-common.h
@@ -21,7 +21,7 @@
#define SCRAM_SHA_256_PLUS_NAME "SCRAM-SHA-256-PLUS" /* with channel binding */
/* Length of SCRAM keys (client and server) */
-#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
+#define SCRAM_SHA_256_KEY_LEN PG_SHA256_DIGEST_LENGTH
/*
* Size of random nonce generated in the authentication exchange. This
@@ -43,17 +43,22 @@
*/
#define SCRAM_DEFAULT_ITERATIONS 4096
-extern int scram_SaltedPassword(const char *password, const char *salt,
- int saltlen, int iterations, uint8 *result,
- const char **errstr);
-extern int scram_H(const uint8 *input, int len, uint8 *result,
+extern int scram_SaltedPassword(const char *password,
+ pg_cryptohash_type hash_type, int key_length,
+ const char *salt, int saltlen, int iterations,
+ uint8 *result, const char **errstr);
+extern int scram_H(const uint8 *input, pg_cryptohash_type hash_type,
+ int key_length, uint8 *result,
const char **errstr);
-extern int scram_ClientKey(const uint8 *salted_password, uint8 *result,
- const char **errstr);
-extern int scram_ServerKey(const uint8 *salted_password, uint8 *result,
- const char **errstr);
+extern int scram_ClientKey(const uint8 *salted_password,
+ pg_cryptohash_type hash_type, int key_length,
+ uint8 *result, const char **errstr);
+extern int scram_ServerKey(const uint8 *salted_password,
+ pg_cryptohash_type hash_type, int key_length,
+ uint8 *result, const char **errstr);
-extern char *scram_build_secret(const char *salt, int saltlen, int iterations,
+extern char *scram_build_secret(pg_cryptohash_type hash_type, int key_length,
+ const char *salt, int saltlen, int iterations,
const char *password, const char **errstr);
#endif /* SCRAM_COMMON_H */
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index c51e848c24..2662c9d703 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -13,6 +13,7 @@
#ifndef PG_SCRAM_H
#define PG_SCRAM_H
+#include "common/cryptohash.h"
#include "lib/stringinfo.h"
#include "libpq/libpq-be.h"
#include "libpq/sasl.h"
@@ -22,8 +23,11 @@ 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 bool parse_scram_secret(const char *secret, int *iterations, char **salt,
- uint8 *stored_key, uint8 *server_key);
+extern bool parse_scram_secret(const char *secret,
+ int *iterations,
+ pg_cryptohash_type *hash_type,
+ int *key_length, char **salt,
+ uint8 **stored_key, uint8 **server_key);
extern bool scram_verify_plain_password(const char *username,
const char *password, const char *secret);
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index ee7f52218a..d083439c13 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -141,10 +141,14 @@ typedef struct
Port *port;
bool channel_binding_in_use;
+ /* State data depending on the hash type */
+ pg_cryptohash_type hash_type;
+ int key_length;
+
int iterations;
char *salt; /* base64-encoded */
- uint8 StoredKey[SCRAM_KEY_LEN];
- uint8 ServerKey[SCRAM_KEY_LEN];
+ uint8 *stored_key; /* size of key_length */
+ uint8 *server_key; /* size of key_length */
/* Fields of the first message from client */
char cbind_flag;
@@ -155,7 +159,7 @@ typedef struct
/* Fields from the last message from client */
char *client_final_message_without_proof;
char *client_final_nonce;
- char ClientProof[SCRAM_KEY_LEN];
+ char *client_proof; /* size of key_length */
/* Fields generated in the server */
char *server_first_message;
@@ -177,12 +181,15 @@ 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, int *iterations,
- char **salt, uint8 *stored_key, uint8 *server_key);
+static void mock_scram_secret(const char *username, 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);
static char *sanitize_char(char c);
static char *sanitize_str(const char *s);
-static char *scram_mock_salt(const char *username);
+static char *scram_mock_salt(const char *username,
+ pg_cryptohash_type hash_type,
+ int key_length);
/*
* Get a list of SASL mechanisms that this module supports.
@@ -266,8 +273,11 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
{
- if (parse_scram_secret(shadow_pass, &state->iterations, &state->salt,
- state->StoredKey, state->ServerKey))
+ if (parse_scram_secret(shadow_pass, &state->iterations,
+ &state->hash_type, &state->key_length,
+ &state->salt,
+ &state->stored_key,
+ &state->server_key))
got_secret = true;
else
{
@@ -310,8 +320,10 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
*/
if (!got_secret)
{
- mock_scram_secret(state->port->user_name, &state->iterations,
- &state->salt, state->StoredKey, state->ServerKey);
+ mock_scram_secret(state->port->user_name, &state->hash_type,
+ &state->iterations, &state->key_length,
+ &state->salt,
+ &state->stored_key, &state->server_key);
state->doomed = true;
}
@@ -482,7 +494,8 @@ pg_be_scram_build_secret(const char *password)
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not generate random salt")));
- result = scram_build_secret(saltbuf, SCRAM_DEFAULT_SALT_LEN,
+ result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN,
+ saltbuf, SCRAM_DEFAULT_SALT_LEN,
SCRAM_DEFAULT_ITERATIONS, password,
&errstr);
@@ -505,16 +518,18 @@ scram_verify_plain_password(const char *username, const char *password,
char *salt;
int saltlen;
int iterations;
- uint8 salted_password[SCRAM_KEY_LEN];
- uint8 stored_key[SCRAM_KEY_LEN];
- uint8 server_key[SCRAM_KEY_LEN];
- uint8 computed_key[SCRAM_KEY_LEN];
+ int key_length = 0;
+ pg_cryptohash_type hash_type;
+ uint8 *salted_password = NULL; /* size of key_length */
+ uint8 *stored_key = NULL; /* size of key_length */
+ uint8 *server_key = NULL; /* size of key_length */
+ uint8 *computed_key = NULL; /* size of key_length */
char *prep_password;
pg_saslprep_rc rc;
const char *errstr = NULL;
- if (!parse_scram_secret(secret, &iterations, &encoded_salt,
- stored_key, server_key))
+ if (!parse_scram_secret(secret, &iterations, &hash_type, &key_length,
+ &encoded_salt, &stored_key, &server_key))
{
/*
* The password looked like a SCRAM secret, but could not be parsed.
@@ -524,6 +539,11 @@ scram_verify_plain_password(const char *username, const char *password,
return false;
}
+ /* allocated by parse_scram_secret() */
+ Assert(stored_key && server_key);
+ salted_password = (uint8 *) palloc(key_length * sizeof(uint8));
+ computed_key = (uint8 *) palloc(key_length * sizeof(uint8));
+
saltlen = pg_b64_dec_len(strlen(encoded_salt));
salt = palloc(saltlen);
saltlen = pg_b64_decode(encoded_salt, strlen(encoded_salt), salt,
@@ -541,9 +561,11 @@ scram_verify_plain_password(const char *username, const char *password,
password = prep_password;
/* Compute Server Key based on the user-supplied plaintext password */
- if (scram_SaltedPassword(password, salt, saltlen, iterations,
+ if (scram_SaltedPassword(password, hash_type, key_length,
+ salt, saltlen, iterations,
salted_password, &errstr) < 0 ||
- scram_ServerKey(salted_password, computed_key, &errstr) < 0)
+ scram_ServerKey(salted_password, hash_type, key_length,
+ computed_key, &errstr) < 0)
{
elog(ERROR, "could not compute server key: %s", errstr);
}
@@ -555,24 +577,25 @@ scram_verify_plain_password(const char *username, const char *password,
* Compare the secret's Server Key with the one computed from the
* user-supplied password.
*/
- return memcmp(computed_key, server_key, SCRAM_KEY_LEN) == 0;
+ return memcmp(computed_key, server_key, key_length) == 0;
}
/*
* Parse and validate format of given SCRAM secret.
*
- * On success, the iteration count, salt, stored key, and server key are
- * extracted from the secret, and returned to the caller. For 'stored_key'
- * and 'server_key', the caller must pass pre-allocated buffers of size
- * SCRAM_KEY_LEN. Salt is returned as a base64-encoded, null-terminated
- * string. The buffer for the salt is palloc'd by this function.
+ * On success, the iteration count, salt, key length, stored key, and
+ * server key are extracted from the secret, and returned to the caller.
+ * 'stored_key' and 'server_key' are palloc'd with a size of 'key_length'.
+ * Salt is returned as a base64-encoded, null-terminated string. The buffer
+ * for the salt is palloc'd by this function.
*
* Returns true if the SCRAM secret has been parsed, and false otherwise.
*/
bool
-parse_scram_secret(const char *secret, int *iterations, char **salt,
- uint8 *stored_key, uint8 *server_key)
+parse_scram_secret(const char *secret, int *iterations,
+ pg_cryptohash_type *hash_type, int *key_length,
+ char **salt, uint8 **stored_key, uint8 **server_key)
{
char *v;
char *p;
@@ -606,6 +629,8 @@ parse_scram_secret(const char *secret, int *iterations, char **salt,
/* Parse the fields */
if (strcmp(scheme_str, "SCRAM-SHA-256") != 0)
goto invalid_secret;
+ *hash_type = PG_SHA256;
+ *key_length = SCRAM_SHA_256_KEY_LEN;
errno = 0;
*iterations = strtol(iterations_str, &p, 10);
@@ -631,17 +656,19 @@ parse_scram_secret(const char *secret, int *iterations, char **salt,
decoded_stored_buf = palloc(decoded_len);
decoded_len = pg_b64_decode(storedkey_str, strlen(storedkey_str),
decoded_stored_buf, decoded_len);
- if (decoded_len != SCRAM_KEY_LEN)
+ if (decoded_len != *key_length)
goto invalid_secret;
- memcpy(stored_key, decoded_stored_buf, SCRAM_KEY_LEN);
+ *stored_key = (uint8 *) palloc(*key_length * sizeof(uint8));
+ memcpy(*stored_key, decoded_stored_buf, *key_length * sizeof(uint8));
decoded_len = pg_b64_dec_len(strlen(serverkey_str));
decoded_server_buf = palloc(decoded_len);
decoded_len = pg_b64_decode(serverkey_str, strlen(serverkey_str),
decoded_server_buf, decoded_len);
- if (decoded_len != SCRAM_KEY_LEN)
+ if (decoded_len != *key_length)
goto invalid_secret;
- memcpy(server_key, decoded_server_buf, SCRAM_KEY_LEN);
+ *server_key = (uint8 *) palloc(*key_length * sizeof(uint8));
+ memcpy(*server_key, decoded_server_buf, *key_length * sizeof(uint8));
return true;
@@ -655,20 +682,25 @@ 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.
+ * realistic, for when there is no stored secret, using SCRAM-SHA-256.
*
- * Like in parse_scram_secret(), for 'stored_key' and 'server_key', the
- * caller must pass pre-allocated buffers of size SCRAM_KEY_LEN, and
- * the buffer for the salt is palloc'd by this function.
+ * 'stored_key' and 'server_key' are palloc'd by this function with
+ * an arbitrary key length guessed from the hash type, and the buffer
+ * for the salt is palloc'd by this function.
*/
static void
-mock_scram_secret(const char *username, int *iterations, char **salt,
- uint8 *stored_key, uint8 *server_key)
+mock_scram_secret(const char *username, pg_cryptohash_type *hash_type,
+ int *iterations, int *key_length, char **salt,
+ uint8 **stored_key, uint8 **server_key)
{
char *raw_salt;
char *encoded_salt;
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;
+
/*
* Generate deterministic salt.
*
@@ -677,7 +709,7 @@ mock_scram_secret(const char *username, int *iterations, char **salt,
* as the salt generated for mock authentication uses the cluster's nonce
* value.
*/
- raw_salt = scram_mock_salt(username);
+ raw_salt = scram_mock_salt(username, *hash_type, *key_length);
if (raw_salt == NULL)
elog(ERROR, "could not encode salt");
@@ -695,8 +727,8 @@ mock_scram_secret(const char *username, int *iterations, char **salt,
*iterations = SCRAM_DEFAULT_ITERATIONS;
/* StoredKey and ServerKey are not used in a doomed authentication */
- memset(stored_key, 0, SCRAM_KEY_LEN);
- memset(server_key, 0, SCRAM_KEY_LEN);
+ *stored_key = (uint8 *) palloc0(*key_length * sizeof(uint8));
+ *server_key = (uint8 *) palloc0(*key_length * sizeof(uint8));
}
/*
@@ -1111,10 +1143,13 @@ verify_final_nonce(scram_state *state)
static bool
verify_client_proof(scram_state *state)
{
- uint8 ClientSignature[SCRAM_KEY_LEN];
- uint8 ClientKey[SCRAM_KEY_LEN];
- uint8 client_StoredKey[SCRAM_KEY_LEN];
- pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
+ uint8 *ClientSignature = (uint8 *) palloc(state->key_length *
+ sizeof(uint8));
+ uint8 *ClientKey = (uint8 *) palloc(state->key_length *
+ sizeof(uint8));
+ uint8 *client_StoredKey = (uint8 *) palloc(state->key_length *
+ sizeof(uint8));
+ pg_hmac_ctx *ctx = pg_hmac_create(state->hash_type);
int i;
const char *errstr = NULL;
@@ -1123,7 +1158,7 @@ verify_client_proof(scram_state *state)
* here even when processing the calculations as this could involve a mock
* authentication.
*/
- if (pg_hmac_init(ctx, state->StoredKey, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, state->stored_key, state->key_length) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
@@ -1135,7 +1170,7 @@ verify_client_proof(scram_state *state)
pg_hmac_update(ctx,
(uint8 *) state->client_final_message_without_proof,
strlen(state->client_final_message_without_proof)) < 0 ||
- pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+ pg_hmac_final(ctx, ClientSignature, state->key_length) < 0)
{
elog(ERROR, "could not calculate client signature: %s",
pg_hmac_error(ctx));
@@ -1144,14 +1179,15 @@ verify_client_proof(scram_state *state)
pg_hmac_free(ctx);
/* Extract the ClientKey that the client calculated from the proof */
- for (i = 0; i < SCRAM_KEY_LEN; i++)
- ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
+ for (i = 0; i < state->key_length; i++)
+ ClientKey[i] = state->client_proof[i] ^ ClientSignature[i];
/* Hash it one more time, and compare with StoredKey */
- if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0)
+ if (scram_H(ClientKey, state->hash_type, state->key_length,
+ client_StoredKey, &errstr) < 0)
elog(ERROR, "could not hash stored key: %s", errstr);
- if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ if (memcmp(client_StoredKey, state->stored_key, state->key_length) != 0)
return false;
return true;
@@ -1349,12 +1385,13 @@ read_client_final_message(scram_state *state, const char *input)
client_proof_len = pg_b64_dec_len(strlen(value));
client_proof = palloc(client_proof_len);
if (pg_b64_decode(value, strlen(value), client_proof,
- client_proof_len) != SCRAM_KEY_LEN)
+ client_proof_len) != state->key_length)
ereport(ERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("malformed SCRAM message"),
errdetail("Malformed proof in client-final-message.")));
- memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+ state->client_proof = palloc(state->key_length);
+ memcpy(state->client_proof, client_proof, state->key_length);
pfree(client_proof);
if (*p != '\0')
@@ -1374,13 +1411,14 @@ read_client_final_message(scram_state *state, const char *input)
static char *
build_server_final_message(scram_state *state)
{
- uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 *ServerSignature = (uint8 *) palloc(state->key_length *
+ sizeof(uint8));
char *server_signature_base64;
int siglen;
- pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
+ pg_hmac_ctx *ctx = pg_hmac_create(state->hash_type);
/* calculate ServerSignature */
- if (pg_hmac_init(ctx, state->ServerKey, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, state->server_key, state->key_length) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
@@ -1392,7 +1430,7 @@ build_server_final_message(scram_state *state)
pg_hmac_update(ctx,
(uint8 *) state->client_final_message_without_proof,
strlen(state->client_final_message_without_proof)) < 0 ||
- pg_hmac_final(ctx, ServerSignature, sizeof(ServerSignature)) < 0)
+ pg_hmac_final(ctx, ServerSignature, state->key_length) < 0)
{
elog(ERROR, "could not calculate server signature: %s",
pg_hmac_error(ctx));
@@ -1400,11 +1438,11 @@ build_server_final_message(scram_state *state)
pg_hmac_free(ctx);
- siglen = pg_b64_enc_len(SCRAM_KEY_LEN);
+ siglen = pg_b64_enc_len(state->key_length);
/* don't forget the zero-terminator */
server_signature_base64 = palloc(siglen + 1);
siglen = pg_b64_encode((const char *) ServerSignature,
- SCRAM_KEY_LEN, server_signature_base64,
+ state->key_length, server_signature_base64,
siglen);
if (siglen < 0)
elog(ERROR, "could not encode server signature");
@@ -1431,12 +1469,15 @@ build_server_final_message(scram_state *state)
* pointer to a static buffer of size SCRAM_DEFAULT_SALT_LEN, or NULL.
*/
static char *
-scram_mock_salt(const char *username)
+scram_mock_salt(const char *username, pg_cryptohash_type hash_type,
+ int key_length)
{
pg_cryptohash_ctx *ctx;
- static uint8 sha_digest[PG_SHA256_DIGEST_LENGTH];
+ uint8 *sha_digest = (uint8 *) palloc(key_length * sizeof(uint8));
char *mock_auth_nonce = GetMockAuthenticationNonce();
+ Assert(hash_type == PG_SHA256);
+
/*
* Generate salt using a SHA256 hash of the username and the cluster's
* mock authentication nonce. (This works as long as the salt length is
@@ -1446,11 +1487,11 @@ scram_mock_salt(const char *username)
StaticAssertStmt(PG_SHA256_DIGEST_LENGTH >= SCRAM_DEFAULT_SALT_LEN,
"salt length greater than SHA256 digest length");
- ctx = pg_cryptohash_create(PG_SHA256);
+ ctx = pg_cryptohash_create(hash_type);
if (pg_cryptohash_init(ctx) < 0 ||
pg_cryptohash_update(ctx, (uint8 *) username, strlen(username)) < 0 ||
pg_cryptohash_update(ctx, (uint8 *) mock_auth_nonce, MOCK_AUTH_NONCE_LEN) < 0 ||
- pg_cryptohash_final(ctx, sha_digest, sizeof(sha_digest)) < 0)
+ pg_cryptohash_final(ctx, sha_digest, key_length) < 0)
{
pg_cryptohash_free(ctx);
return NULL;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 1ff8b0507d..4e2b7c99fe 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -90,15 +90,17 @@ get_password_type(const char *shadow_pass)
{
char *encoded_salt;
int iterations;
- uint8 stored_key[SCRAM_KEY_LEN];
- uint8 server_key[SCRAM_KEY_LEN];
+ int key_length = 0;
+ pg_cryptohash_type hash_type;
+ uint8 *stored_key; /* size of key_length */
+ uint8 *server_key; /* size of key_length */
if (strncmp(shadow_pass, "md5", 3) == 0 &&
strlen(shadow_pass) == MD5_PASSWD_LEN &&
strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
return PASSWORD_TYPE_MD5;
- if (parse_scram_secret(shadow_pass, &iterations, &encoded_salt,
- stored_key, server_key))
+ if (parse_scram_secret(shadow_pass, &iterations, &hash_type, &key_length,
+ &encoded_salt, &stored_key, &server_key))
return PASSWORD_TYPE_SCRAM_SHA_256;
return PASSWORD_TYPE_PLAINTEXT;
}
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 1268625929..d41be27ca4 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -33,6 +33,7 @@
*/
int
scram_SaltedPassword(const char *password,
+ pg_cryptohash_type hash_type, int key_length,
const char *salt, int saltlen, int iterations,
uint8 *result, const char **errstr)
{
@@ -40,9 +41,9 @@ scram_SaltedPassword(const char *password,
uint32 one = pg_hton32(1);
int i,
j;
- uint8 Ui[SCRAM_KEY_LEN];
- uint8 Ui_prev[SCRAM_KEY_LEN];
- pg_hmac_ctx *hmac_ctx = pg_hmac_create(PG_SHA256);
+ uint8 *Ui; /* size of key_length */
+ uint8 *Ui_prev; /* size of key_length */
+ pg_hmac_ctx *hmac_ctx = pg_hmac_create(hash_type);
if (hmac_ctx == NULL)
{
@@ -50,6 +51,19 @@ scram_SaltedPassword(const char *password,
return -1;
}
+#ifdef FRONTEND
+ Ui = (uint8 *) malloc(key_length * sizeof(uint8));
+ Ui_prev = (uint8 *) malloc(key_length * sizeof(uint8));
+ if (Ui == NULL || Ui_prev == NULL)
+ {
+ *errstr = _("out of memory");
+ goto error;
+ }
+#else
+ Ui = (uint8 *) palloc(key_length * sizeof(uint8));
+ Ui_prev = (uint8 *) palloc(key_length * sizeof(uint8));
+#endif
+
/*
* Iterate hash calculation of HMAC entry using given salt. This is
* essentially PBKDF2 (see RFC2898) with HMAC() as the pseudorandom
@@ -60,48 +74,70 @@ scram_SaltedPassword(const char *password,
if (pg_hmac_init(hmac_ctx, (uint8 *) password, password_len) < 0 ||
pg_hmac_update(hmac_ctx, (uint8 *) salt, saltlen) < 0 ||
pg_hmac_update(hmac_ctx, (uint8 *) &one, sizeof(uint32)) < 0 ||
- pg_hmac_final(hmac_ctx, Ui_prev, sizeof(Ui_prev)) < 0)
+ pg_hmac_final(hmac_ctx, Ui_prev, key_length) < 0)
{
*errstr = pg_hmac_error(hmac_ctx);
- pg_hmac_free(hmac_ctx);
- return -1;
+ goto error;
}
- memcpy(result, Ui_prev, SCRAM_KEY_LEN);
+ memcpy(result, Ui_prev, key_length);
/* Subsequent iterations */
for (i = 2; i <= iterations; i++)
{
if (pg_hmac_init(hmac_ctx, (uint8 *) password, password_len) < 0 ||
- pg_hmac_update(hmac_ctx, (uint8 *) Ui_prev, SCRAM_KEY_LEN) < 0 ||
- pg_hmac_final(hmac_ctx, Ui, sizeof(Ui)) < 0)
+ pg_hmac_update(hmac_ctx, (uint8 *) Ui_prev, key_length) < 0 ||
+ pg_hmac_final(hmac_ctx, Ui, key_length) < 0)
{
*errstr = pg_hmac_error(hmac_ctx);
- pg_hmac_free(hmac_ctx);
- return -1;
+ goto error;
}
- for (j = 0; j < SCRAM_KEY_LEN; j++)
+ for (j = 0; j < key_length; j++)
result[j] ^= Ui[j];
- memcpy(Ui_prev, Ui, SCRAM_KEY_LEN);
+ memcpy(Ui_prev, Ui, key_length);
}
+#ifdef FRONTEND
+ free(Ui);
+ free(Ui_prev);
+#else
+ pfree(Ui);
+ pfree(Ui_prev);
+#endif
pg_hmac_free(hmac_ctx);
return 0;
+
+error:
+#ifdef FRONTEND
+ if (Ui)
+ free(Ui);
+ if (Ui_prev)
+ free(Ui_prev);
+#else
+ if (Ui)
+ pfree(Ui);
+ if (Ui_prev)
+ pfree(Ui_prev);
+#endif
+
+ pg_hmac_free(hmac_ctx);
+ return -1;
}
/*
- * Calculate SHA-256 hash for a NULL-terminated string. (The NULL terminator is
+ * Calculate hash for a NULL-terminated string. (The NULL terminator is
* not included in the hash). Returns 0 on success, -1 on failure with *errstr
* pointing to a message about the error details.
*/
int
-scram_H(const uint8 *input, int len, uint8 *result, const char **errstr)
+scram_H(const uint8 *input, pg_cryptohash_type hash_type, int key_length,
+ uint8 *result, const char **errstr)
{
pg_cryptohash_ctx *ctx;
- ctx = pg_cryptohash_create(PG_SHA256);
+ ctx = pg_cryptohash_create(hash_type);
if (ctx == NULL)
{
*errstr = pg_cryptohash_error(NULL); /* returns OOM */
@@ -109,8 +145,8 @@ scram_H(const uint8 *input, int len, uint8 *result, const char **errstr)
}
if (pg_cryptohash_init(ctx) < 0 ||
- pg_cryptohash_update(ctx, input, len) < 0 ||
- pg_cryptohash_final(ctx, result, SCRAM_KEY_LEN) < 0)
+ pg_cryptohash_update(ctx, input, key_length) < 0 ||
+ pg_cryptohash_final(ctx, result, key_length) < 0)
{
*errstr = pg_cryptohash_error(ctx);
pg_cryptohash_free(ctx);
@@ -126,10 +162,11 @@ scram_H(const uint8 *input, int len, uint8 *result, const char **errstr)
* pointing to a message about the error details.
*/
int
-scram_ClientKey(const uint8 *salted_password, uint8 *result,
- const char **errstr)
+scram_ClientKey(const uint8 *salted_password,
+ pg_cryptohash_type hash_type, int key_length,
+ uint8 *result, const char **errstr)
{
- pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
+ pg_hmac_ctx *ctx = pg_hmac_create(hash_type);
if (ctx == NULL)
{
@@ -137,9 +174,9 @@ scram_ClientKey(const uint8 *salted_password, uint8 *result,
return -1;
}
- if (pg_hmac_init(ctx, salted_password, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, salted_password, key_length) < 0 ||
pg_hmac_update(ctx, (uint8 *) "Client Key", strlen("Client Key")) < 0 ||
- pg_hmac_final(ctx, result, SCRAM_KEY_LEN) < 0)
+ pg_hmac_final(ctx, result, key_length) < 0)
{
*errstr = pg_hmac_error(ctx);
pg_hmac_free(ctx);
@@ -155,10 +192,11 @@ scram_ClientKey(const uint8 *salted_password, uint8 *result,
* pointing to a message about the error details.
*/
int
-scram_ServerKey(const uint8 *salted_password, uint8 *result,
- const char **errstr)
+scram_ServerKey(const uint8 *salted_password,
+ pg_cryptohash_type hash_type, int key_length,
+ uint8 *result, const char **errstr)
{
- pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
+ pg_hmac_ctx *ctx = pg_hmac_create(hash_type);
if (ctx == NULL)
{
@@ -166,9 +204,9 @@ scram_ServerKey(const uint8 *salted_password, uint8 *result,
return -1;
}
- if (pg_hmac_init(ctx, salted_password, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, salted_password, key_length) < 0 ||
pg_hmac_update(ctx, (uint8 *) "Server Key", strlen("Server Key")) < 0 ||
- pg_hmac_final(ctx, result, SCRAM_KEY_LEN) < 0)
+ pg_hmac_final(ctx, result, key_length) < 0)
{
*errstr = pg_hmac_error(ctx);
pg_hmac_free(ctx);
@@ -192,13 +230,14 @@ scram_ServerKey(const uint8 *salted_password, uint8 *result,
* error details.
*/
char *
-scram_build_secret(const char *salt, int saltlen, int iterations,
+scram_build_secret(pg_cryptohash_type hash_type, int key_length,
+ const char *salt, int saltlen, int iterations,
const char *password, const char **errstr)
{
- uint8 salted_password[SCRAM_KEY_LEN];
- uint8 stored_key[SCRAM_KEY_LEN];
- uint8 server_key[SCRAM_KEY_LEN];
- char *result;
+ uint8 *salted_password = NULL;
+ uint8 *stored_key = NULL;
+ uint8 *server_key = NULL;
+ char *result = NULL;
char *p;
int maxlen;
int encoded_salt_len;
@@ -206,19 +245,42 @@ scram_build_secret(const char *salt, int saltlen, int iterations,
int encoded_server_len;
int encoded_result;
+ Assert(hash_type == PG_SHA256);
+
if (iterations <= 0)
iterations = SCRAM_DEFAULT_ITERATIONS;
+#ifdef FRONTEND
+ salted_password = (uint8 *) malloc(key_length * sizeof(uint8));
+ stored_key = (uint8 *) malloc(key_length * sizeof(uint8));
+ server_key = (uint8 *) malloc(key_length * sizeof(uint8));
+ if (salted_password == NULL ||
+ stored_key == NULL ||
+ server_key == NULL)
+ {
+ *errstr = _("out of memory");
+ goto error;
+ }
+#else
+ salted_password = (uint8 *) palloc(key_length * sizeof(uint8));
+ stored_key = (uint8 *) palloc(key_length * sizeof(uint8));
+ server_key = (uint8 *) palloc(key_length * sizeof(uint8));
+#endif
+
/* Calculate StoredKey and ServerKey */
- if (scram_SaltedPassword(password, salt, saltlen, iterations,
+ if (scram_SaltedPassword(password, hash_type, key_length,
+ salt, saltlen, iterations,
salted_password, errstr) < 0 ||
- scram_ClientKey(salted_password, stored_key, errstr) < 0 ||
- scram_H(stored_key, SCRAM_KEY_LEN, stored_key, errstr) < 0 ||
- scram_ServerKey(salted_password, server_key, errstr) < 0)
+ scram_ClientKey(salted_password, hash_type, key_length,
+ stored_key, errstr) < 0 ||
+ scram_H(stored_key, hash_type, key_length,
+ stored_key, errstr) < 0 ||
+ scram_ServerKey(salted_password, hash_type, key_length,
+ server_key, errstr) < 0)
{
/* errstr is filled already here */
#ifdef FRONTEND
- return NULL;
+ goto error;
#else
elog(ERROR, "could not calculate stored key and server key: %s",
*errstr);
@@ -231,8 +293,8 @@ scram_build_secret(const char *salt, int saltlen, int iterations,
*----------
*/
encoded_salt_len = pg_b64_enc_len(saltlen);
- encoded_stored_len = pg_b64_enc_len(SCRAM_KEY_LEN);
- encoded_server_len = pg_b64_enc_len(SCRAM_KEY_LEN);
+ encoded_stored_len = pg_b64_enc_len(key_length);
+ encoded_server_len = pg_b64_enc_len(key_length);
maxlen = strlen("SCRAM-SHA-256") + 1
+ 10 + 1 /* iteration count */
@@ -245,7 +307,7 @@ scram_build_secret(const char *salt, int saltlen, int iterations,
if (!result)
{
*errstr = _("out of memory");
- return NULL;
+ goto error;
}
#else
result = palloc(maxlen);
@@ -258,45 +320,30 @@ scram_build_secret(const char *salt, int saltlen, int iterations,
if (encoded_result < 0)
{
*errstr = _("could not encode salt");
-#ifdef FRONTEND
- free(result);
- return NULL;
-#else
- elog(ERROR, "%s", *errstr);
-#endif
+ goto error;
}
p += encoded_result;
*(p++) = '$';
/* stored key */
- encoded_result = pg_b64_encode((char *) stored_key, SCRAM_KEY_LEN, p,
+ encoded_result = pg_b64_encode((char *) stored_key, key_length, p,
encoded_stored_len);
if (encoded_result < 0)
{
*errstr = _("could not encode stored key");
-#ifdef FRONTEND
- free(result);
- return NULL;
-#else
- elog(ERROR, "%s", *errstr);
-#endif
+ goto error;
}
p += encoded_result;
*(p++) = ':';
/* server key */
- encoded_result = pg_b64_encode((char *) server_key, SCRAM_KEY_LEN, p,
+ encoded_result = pg_b64_encode((char *) server_key, key_length, p,
encoded_server_len);
if (encoded_result < 0)
{
*errstr = _("could not encode server key");
-#ifdef FRONTEND
- free(result);
- return NULL;
-#else
- elog(ERROR, "%s", *errstr);
-#endif
+ goto error;
}
p += encoded_result;
@@ -304,5 +351,25 @@ scram_build_secret(const char *salt, int saltlen, int iterations,
Assert(p - result <= maxlen);
+#ifdef FRONTEND
+ free(salted_password);
+ free(stored_key);
+ free(server_key);
+#endif
return result;
+
+error:
+#ifdef FRONTEND
+ if (salted_password)
+ free(salted_password);
+ if (stored_key)
+ free(stored_key);
+ if (server_key)
+ free(server_key);
+ if (result)
+ free(result);
+#else
+ elog(ERROR, "%s", *errstr);
+#endif
+ return NULL;
}
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index c500bea9e7..3b20062484 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -58,8 +58,12 @@ typedef struct
char *password;
char *sasl_mechanism;
+ /* State data depending on the hash type */
+ pg_cryptohash_type hash_type;
+ int key_length;
+
/* We construct these */
- uint8 SaltedPassword[SCRAM_KEY_LEN];
+ uint8 *salted_password; /* size of key_length */
char *client_nonce;
char *client_first_message_bare;
char *client_final_message_without_proof;
@@ -73,7 +77,7 @@ typedef struct
/* These come from the server-final message */
char *server_final_message;
- char ServerSignature[SCRAM_KEY_LEN];
+ char *server_signature; /* size of key_length */
} fe_scram_state;
static bool read_server_first_message(fe_scram_state *state, char *input);
@@ -106,35 +110,47 @@ scram_init(PGconn *conn,
memset(state, 0, sizeof(fe_scram_state));
state->conn = conn;
state->state = FE_SCRAM_INIT;
- state->sasl_mechanism = strdup(sasl_mechanism);
+ state->key_length = SCRAM_SHA_256_KEY_LEN;
+ state->hash_type = PG_SHA256;
+ state->sasl_mechanism = strdup(sasl_mechanism);
if (!state->sasl_mechanism)
- {
- free(state);
- return NULL;
- }
+ goto oom_error;
+
+ state->salted_password = (uint8 *) malloc(state->key_length * sizeof(uint8));
+ if (state->salted_password == NULL)
+ goto oom_error;
+ state->server_signature = (char *) malloc(state->key_length * sizeof(char));
+ if (state->server_signature == NULL)
+ goto oom_error;
/* Normalize the password with SASLprep, if possible */
rc = pg_saslprep(password, &prep_password);
if (rc == SASLPREP_OOM)
- {
- free(state->sasl_mechanism);
- free(state);
- return NULL;
- }
+ goto oom_error;
+
if (rc != SASLPREP_SUCCESS)
{
prep_password = strdup(password);
if (!prep_password)
- {
- free(state->sasl_mechanism);
- free(state);
- return NULL;
- }
+ goto oom_error;
}
state->password = prep_password;
return state;
+
+oom_error:
+ if (state->salted_password)
+ free(state->salted_password);
+ if (state->server_signature)
+ free(state->server_signature);
+ if (state->password)
+ free(state->password);
+ if (state->sasl_mechanism)
+ free(state->sasl_mechanism);
+ if (state)
+ free(state);
+ return NULL;
}
/*
@@ -178,6 +194,7 @@ scram_free(void *opaq)
free(state->sasl_mechanism);
/* client messages */
+ free(state->salted_password);
free(state->client_nonce);
free(state->client_first_message_bare);
free(state->client_final_message_without_proof);
@@ -189,6 +206,7 @@ scram_free(void *opaq)
/* final message from server */
free(state->server_final_message);
+ free(state->server_signature);
free(state);
}
@@ -450,13 +468,17 @@ build_client_final_message(fe_scram_state *state)
{
PQExpBufferData buf;
PGconn *conn = state->conn;
- uint8 client_proof[SCRAM_KEY_LEN];
+ uint8 *client_proof; /* size of key_length */
char *result;
int encoded_len;
const char *errstr = NULL;
initPQExpBuffer(&buf);
+ client_proof = (uint8 *) malloc(state->key_length * sizeof(uint8));
+ if (client_proof == NULL)
+ goto oom_error;
+
/*
* Construct client-final-message-without-proof. We need to remember it
* for verifying the server proof in the final step of authentication.
@@ -565,11 +587,11 @@ build_client_final_message(fe_scram_state *state)
}
appendPQExpBufferStr(&buf, ",p=");
- encoded_len = pg_b64_enc_len(SCRAM_KEY_LEN);
+ encoded_len = pg_b64_enc_len(state->key_length);
if (!enlargePQExpBuffer(&buf, encoded_len))
goto oom_error;
encoded_len = pg_b64_encode((char *) client_proof,
- SCRAM_KEY_LEN,
+ state->key_length,
buf.data + buf.len,
encoded_len);
if (encoded_len < 0)
@@ -590,6 +612,7 @@ build_client_final_message(fe_scram_state *state)
oom_error:
termPQExpBuffer(&buf);
+ free(client_proof);
libpq_append_conn_error(conn, "out of memory");
return NULL;
}
@@ -738,13 +761,14 @@ read_server_final_message(fe_scram_state *state, char *input)
strlen(encoded_server_signature),
decoded_server_signature,
server_signature_len);
- if (server_signature_len != SCRAM_KEY_LEN)
+ if (server_signature_len != state->key_length)
{
free(decoded_server_signature);
libpq_append_conn_error(conn, "malformed SCRAM message (invalid server signature)");
return false;
}
- memcpy(state->ServerSignature, decoded_server_signature, SCRAM_KEY_LEN);
+ memcpy(state->server_signature, decoded_server_signature,
+ state->key_length);
free(decoded_server_signature);
return true;
@@ -760,35 +784,48 @@ calculate_client_proof(fe_scram_state *state,
const char *client_final_message_without_proof,
uint8 *result, const char **errstr)
{
- uint8 StoredKey[SCRAM_KEY_LEN];
- uint8 ClientKey[SCRAM_KEY_LEN];
- uint8 ClientSignature[SCRAM_KEY_LEN];
+ uint8 *StoredKey = NULL;
+ uint8 *ClientKey = NULL;
+ uint8 *ClientSignature = NULL;
int i;
- pg_hmac_ctx *ctx;
+ pg_hmac_ctx *ctx = NULL;
- ctx = pg_hmac_create(PG_SHA256);
+ StoredKey = malloc(state->key_length * sizeof(uint8));
+ ClientKey = malloc(state->key_length * sizeof(uint8));
+ ClientSignature = malloc(state->key_length * sizeof(uint8));
+ if (StoredKey == NULL ||
+ ClientKey == NULL ||
+ ClientSignature == NULL)
+ {
+ *errstr = libpq_gettext("out of memory");
+ goto error;
+ }
+
+ ctx = pg_hmac_create(state->hash_type);
if (ctx == NULL)
{
*errstr = pg_hmac_error(NULL); /* returns OOM */
- return false;
+ goto error;
}
/*
* Calculate SaltedPassword, and store it in 'state' so that we can reuse
* it later in verify_server_signature.
*/
- if (scram_SaltedPassword(state->password, state->salt, state->saltlen,
- state->iterations, state->SaltedPassword,
+ if (scram_SaltedPassword(state->password, state->hash_type,
+ state->key_length, state->salt, state->saltlen,
+ state->iterations, state->salted_password,
errstr) < 0 ||
- scram_ClientKey(state->SaltedPassword, ClientKey, errstr) < 0 ||
- scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey, errstr) < 0)
+ scram_ClientKey(state->salted_password, state->hash_type,
+ state->key_length, ClientKey, errstr) < 0 ||
+ scram_H(ClientKey, state->hash_type, state->key_length,
+ StoredKey, errstr) < 0)
{
/* errstr is already filled here */
- pg_hmac_free(ctx);
- return false;
+ goto error;
}
- if (pg_hmac_init(ctx, StoredKey, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, StoredKey, state->key_length) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
@@ -800,18 +837,30 @@ calculate_client_proof(fe_scram_state *state,
pg_hmac_update(ctx,
(uint8 *) client_final_message_without_proof,
strlen(client_final_message_without_proof)) < 0 ||
- pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+ pg_hmac_final(ctx, ClientSignature, state->key_length) < 0)
{
*errstr = pg_hmac_error(ctx);
- pg_hmac_free(ctx);
- return false;
+ goto error;
}
- for (i = 0; i < SCRAM_KEY_LEN; i++)
+ for (i = 0; i < state->key_length; i++)
result[i] = ClientKey[i] ^ ClientSignature[i];
+ free(StoredKey);
+ free(ClientKey);
+ free(ClientSignature);
pg_hmac_free(ctx);
return true;
+
+error:
+ if (StoredKey)
+ free(StoredKey);
+ if (ClientKey)
+ free(ClientKey);
+ if (ClientSignature)
+ free(ClientSignature);
+ pg_hmac_free(ctx);
+ return false;
}
/*
@@ -825,26 +874,35 @@ static bool
verify_server_signature(fe_scram_state *state, bool *match,
const char **errstr)
{
- uint8 expected_ServerSignature[SCRAM_KEY_LEN];
- uint8 ServerKey[SCRAM_KEY_LEN];
- pg_hmac_ctx *ctx;
+ uint8 *expected_ServerSignature = NULL;
+ uint8 *ServerKey = NULL;
+ pg_hmac_ctx *ctx = NULL;
- ctx = pg_hmac_create(PG_SHA256);
+ ServerKey = (uint8 *) malloc(state->key_length * sizeof(uint8));
+ expected_ServerSignature = (uint8 *) malloc(state->key_length * sizeof(uint8));
+
+ if (ServerKey == NULL || expected_ServerSignature == NULL)
+ {
+ *errstr = libpq_gettext("out of memory");
+ goto error;
+ }
+
+ ctx = pg_hmac_create(state->hash_type);
if (ctx == NULL)
{
*errstr = pg_hmac_error(NULL); /* returns OOM */
- return false;
+ goto error;
}
- if (scram_ServerKey(state->SaltedPassword, ServerKey, errstr) < 0)
+ if (scram_ServerKey(state->salted_password, state->hash_type,
+ state->key_length, ServerKey, errstr) < 0)
{
/* errstr is filled already */
- pg_hmac_free(ctx);
- return false;
+ goto error;
}
/* calculate ServerSignature */
- if (pg_hmac_init(ctx, ServerKey, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, ServerKey, state->key_length) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
@@ -857,22 +915,32 @@ verify_server_signature(fe_scram_state *state, bool *match,
(uint8 *) state->client_final_message_without_proof,
strlen(state->client_final_message_without_proof)) < 0 ||
pg_hmac_final(ctx, expected_ServerSignature,
- sizeof(expected_ServerSignature)) < 0)
+ state->key_length) < 0)
{
*errstr = pg_hmac_error(ctx);
- pg_hmac_free(ctx);
- return false;
+ goto error;
}
pg_hmac_free(ctx);
/* signature processed, so now check after it */
- if (memcmp(expected_ServerSignature, state->ServerSignature, SCRAM_KEY_LEN) != 0)
+ if (memcmp(expected_ServerSignature, state->server_signature,
+ state->key_length) != 0)
*match = false;
else
*match = true;
+ free(ServerKey);
+ free(expected_ServerSignature);
return true;
+
+error:
+ if (ServerKey)
+ free(ServerKey);
+ if (expected_ServerSignature)
+ free(expected_ServerSignature);
+ pg_hmac_free(ctx);
+ return false;
}
/*
@@ -912,7 +980,8 @@ pg_fe_scram_build_secret(const char *password, const char **errstr)
return NULL;
}
- result = scram_build_secret(saltbuf, SCRAM_DEFAULT_SALT_LEN,
+ result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN, saltbuf,
+ SCRAM_DEFAULT_SALT_LEN,
SCRAM_DEFAULT_ITERATIONS, password,
errstr);
--
2.38.1
On 14.12.22 03:38, Michael Paquier wrote:
This patch passes check-world and the CI is green. I have tested as
well the patch with SCRAM verifiers coming from a server initially on
HEAD, so it looks pretty solid seen from here, being careful of memory
leaks in the frontend, mainly.
The changes from local arrays to dynamic allocation appear to introduce
significant complexity. I would reconsider that. If we consider your
reasoning
While investigating on what it would take to extend SCRAM to use new
hash methods (say like the RFC draft for SCRAM-SHA-512), I have been
quickly reminded of the limitations created by SCRAM_KEY_LEN, which is
the key length that we use in the HMAC and hash computations when
creating a SCRAM verifier or when doing a SASL exchange.
then the obvious fix there is to change the definition of SCRAM_KEY_LEN
to PG_SHA512_DIGEST_LENGTH, which would be a much smaller and simpler
change. We don't have to support arbitrary key sizes, so a fixed-size
array seems appropriate.
On Wed, Dec 14, 2022 at 02:39:43PM +0100, Peter Eisentraut wrote:
On 14.12.22 03:38, Michael Paquier wrote:
While investigating on what it would take to extend SCRAM to use new
hash methods (say like the RFC draft for SCRAM-SHA-512), I have been
quickly reminded of the limitations created by SCRAM_KEY_LEN, which is
the key length that we use in the HMAC and hash computations when
creating a SCRAM verifier or when doing a SASL exchange.then the obvious fix there is to change the definition of SCRAM_KEY_LEN to
PG_SHA512_DIGEST_LENGTH, which would be a much smaller and simpler change.
We don't have to support arbitrary key sizes, so a fixed-size array seems
appropriate.
Yeah, I was considering doing that as well for the static arrays, with
something like a Max() to combine but perhaps that's not necessary for
the digest lengths anyway. Perhaps I just over-engineered the
approach.
However, that's only half of the picture. The key length and the hash
type (or just the hash type to know what's the digest/key length to
use but that's more invasive) still need to be sent across the
internal routines of SCRAM and attached to the state data of the
frontend and the backend or we won't be able to do the hash and HMAC
computations dependent on that.
--
Michael
On Thu, Dec 15, 2022 at 04:59:52AM +0900, Michael Paquier wrote:
However, that's only half of the picture. The key length and the hash
type (or just the hash type to know what's the digest/key length to
use but that's more invasive) still need to be sent across the
internal routines of SCRAM and attached to the state data of the
frontend and the backend or we won't be able to do the hash and HMAC
computations dependent on that.
Attached is a patch to do exactly that, and as a result v2 is half the
size of v1:
- SCRAM_KEY_LEN is now named SCRAM_MAX_KEY_LEN, adding a note that
this should be kept in sync as the maximum digest size of the
supported hash methods. This is used as the method to size all the
internal buffers of the SCRAM routines.
- SCRAM_SHA_256_KEY_LEN is used to track the key length for
SCRAM-SHA-256, the one initialized with the state data.
- No changes in the internal, the buffers are just resized based on
the max defined.
I'd like to move on with that in the next couple of days (still need
to study more the other areas of the code to see what else could be
made more pluggable), so let me know if there are any objections..
--
Michael
Attachments:
v2-0001-Remove-dependency-to-hash-type-and-key-length-in-.patchtext/x-diff; charset=us-asciiDownload
From 0e01ec72ebfdf71bafd7434ea19c2dcb17164f1d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Sat, 17 Dec 2022 12:06:37 +0900
Subject: [PATCH v2] Remove dependency to hash type and key length in internal
SCRAM code
SCRAM_KEY_LEN had a hard dependency on SHA-256, making difficult the
addition of more hash methods in SCRAM with many internal buffers sized
depending on that. A second issue is that SHA-256 is assumed as the
computation method to use all the time.
This commit renames SCRAM_KEY_LEN to a more generic SCRAM_KEY_MAX_LEN,
which is used as the size of the buffers used by the internal routines
of SCRAM, which is aimed at tracking centrally the maximum size
necessary for all the hash methods supported. A second change is that
the key length (SHA digest length) and hash types are now tracked by the
state data in the backend and the frontend, the common portions being
extended to handle these as arguments by the internal routines of
SCRAM.
---
src/include/common/scram-common.h | 31 ++++--
src/include/libpq/scram.h | 6 +-
src/backend/libpq/auth-scram.c | 137 ++++++++++++++++-----------
src/backend/libpq/crypt.c | 10 +-
src/common/scram-common.c | 83 +++++++++-------
src/interfaces/libpq/fe-auth-scram.c | 65 ++++++++-----
6 files changed, 201 insertions(+), 131 deletions(-)
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
index 4acf2a78ad..953d30ac54 100644
--- a/src/include/common/scram-common.h
+++ b/src/include/common/scram-common.h
@@ -21,7 +21,13 @@
#define SCRAM_SHA_256_PLUS_NAME "SCRAM-SHA-256-PLUS" /* with channel binding */
/* Length of SCRAM keys (client and server) */
-#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
+#define SCRAM_SHA_256_KEY_LEN PG_SHA256_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
/*
* Size of random nonce generated in the authentication exchange. This
@@ -43,17 +49,22 @@
*/
#define SCRAM_DEFAULT_ITERATIONS 4096
-extern int scram_SaltedPassword(const char *password, const char *salt,
- int saltlen, int iterations, uint8 *result,
- const char **errstr);
-extern int scram_H(const uint8 *input, int len, uint8 *result,
+extern int scram_SaltedPassword(const char *password,
+ pg_cryptohash_type hash_type, int key_length,
+ const char *salt, int saltlen, int iterations,
+ uint8 *result, const char **errstr);
+extern int scram_H(const uint8 *input, pg_cryptohash_type hash_type,
+ int key_length, uint8 *result,
const char **errstr);
-extern int scram_ClientKey(const uint8 *salted_password, uint8 *result,
- const char **errstr);
-extern int scram_ServerKey(const uint8 *salted_password, uint8 *result,
- const char **errstr);
+extern int scram_ClientKey(const uint8 *salted_password,
+ pg_cryptohash_type hash_type, int key_length,
+ uint8 *result, const char **errstr);
+extern int scram_ServerKey(const uint8 *salted_password,
+ pg_cryptohash_type hash_type, int key_length,
+ uint8 *result, const char **errstr);
-extern char *scram_build_secret(const char *salt, int saltlen, int iterations,
+extern char *scram_build_secret(pg_cryptohash_type hash_type, int key_length,
+ const char *salt, int saltlen, int iterations,
const char *password, const char **errstr);
#endif /* SCRAM_COMMON_H */
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index c51e848c24..b29501ef96 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -13,6 +13,7 @@
#ifndef PG_SCRAM_H
#define PG_SCRAM_H
+#include "common/cryptohash.h"
#include "lib/stringinfo.h"
#include "libpq/libpq-be.h"
#include "libpq/sasl.h"
@@ -22,7 +23,10 @@ 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 bool parse_scram_secret(const char *secret, int *iterations, char **salt,
+extern bool parse_scram_secret(const char *secret,
+ int *iterations,
+ pg_cryptohash_type *hash_type,
+ int *key_length, char **salt,
uint8 *stored_key, uint8 *server_key);
extern bool scram_verify_plain_password(const char *username,
const char *password, const char *secret);
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index c9bab85e82..0e4bbfc4f1 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -141,10 +141,14 @@ typedef struct
Port *port;
bool channel_binding_in_use;
+ /* State data depending on the hash type */
+ pg_cryptohash_type hash_type;
+ int key_length;
+
int iterations;
char *salt; /* base64-encoded */
- uint8 StoredKey[SCRAM_KEY_LEN];
- uint8 ServerKey[SCRAM_KEY_LEN];
+ uint8 StoredKey[SCRAM_MAX_KEY_LEN];
+ uint8 ServerKey[SCRAM_MAX_KEY_LEN];
/* Fields of the first message from client */
char cbind_flag;
@@ -155,7 +159,7 @@ typedef struct
/* Fields from the last message from client */
char *client_final_message_without_proof;
char *client_final_nonce;
- char ClientProof[SCRAM_KEY_LEN];
+ char ClientProof[SCRAM_MAX_KEY_LEN];
/* Fields generated in the server */
char *server_first_message;
@@ -177,12 +181,15 @@ 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, int *iterations,
- char **salt, uint8 *stored_key, uint8 *server_key);
+static void mock_scram_secret(const char *username, 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);
static char *sanitize_char(char c);
static char *sanitize_str(const char *s);
-static char *scram_mock_salt(const char *username);
+static char *scram_mock_salt(const char *username,
+ pg_cryptohash_type hash_type,
+ int key_length);
/*
* Get a list of SASL mechanisms that this module supports.
@@ -266,8 +273,11 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
{
- if (parse_scram_secret(shadow_pass, &state->iterations, &state->salt,
- state->StoredKey, state->ServerKey))
+ if (parse_scram_secret(shadow_pass, &state->iterations,
+ &state->hash_type, &state->key_length,
+ &state->salt,
+ state->StoredKey,
+ state->ServerKey))
got_secret = true;
else
{
@@ -310,8 +320,10 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
*/
if (!got_secret)
{
- mock_scram_secret(state->port->user_name, &state->iterations,
- &state->salt, state->StoredKey, state->ServerKey);
+ mock_scram_secret(state->port->user_name, &state->hash_type,
+ &state->iterations, &state->key_length,
+ &state->salt,
+ state->StoredKey, state->ServerKey);
state->doomed = true;
}
@@ -482,7 +494,8 @@ pg_be_scram_build_secret(const char *password)
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not generate random salt")));
- result = scram_build_secret(saltbuf, SCRAM_DEFAULT_SALT_LEN,
+ result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN,
+ saltbuf, SCRAM_DEFAULT_SALT_LEN,
SCRAM_DEFAULT_ITERATIONS, password,
&errstr);
@@ -505,16 +518,18 @@ scram_verify_plain_password(const char *username, const char *password,
char *salt;
int saltlen;
int iterations;
- uint8 salted_password[SCRAM_KEY_LEN];
- uint8 stored_key[SCRAM_KEY_LEN];
- uint8 server_key[SCRAM_KEY_LEN];
- uint8 computed_key[SCRAM_KEY_LEN];
+ int key_length = 0;
+ pg_cryptohash_type hash_type;
+ uint8 salted_password[SCRAM_MAX_KEY_LEN];
+ uint8 stored_key[SCRAM_MAX_KEY_LEN];
+ uint8 server_key[SCRAM_MAX_KEY_LEN];
+ uint8 computed_key[SCRAM_MAX_KEY_LEN];
char *prep_password;
pg_saslprep_rc rc;
const char *errstr = NULL;
- if (!parse_scram_secret(secret, &iterations, &encoded_salt,
- stored_key, server_key))
+ if (!parse_scram_secret(secret, &iterations, &hash_type, &key_length,
+ &encoded_salt, stored_key, server_key))
{
/*
* The password looked like a SCRAM secret, but could not be parsed.
@@ -541,9 +556,11 @@ scram_verify_plain_password(const char *username, const char *password,
password = prep_password;
/* Compute Server Key based on the user-supplied plaintext password */
- if (scram_SaltedPassword(password, salt, saltlen, iterations,
+ if (scram_SaltedPassword(password, hash_type, key_length,
+ salt, saltlen, iterations,
salted_password, &errstr) < 0 ||
- scram_ServerKey(salted_password, computed_key, &errstr) < 0)
+ scram_ServerKey(salted_password, hash_type, key_length,
+ computed_key, &errstr) < 0)
{
elog(ERROR, "could not compute server key: %s", errstr);
}
@@ -555,7 +572,7 @@ scram_verify_plain_password(const char *username, const char *password,
* Compare the secret's Server Key with the one computed from the
* user-supplied password.
*/
- return memcmp(computed_key, server_key, SCRAM_KEY_LEN) == 0;
+ return memcmp(computed_key, server_key, key_length) == 0;
}
@@ -565,14 +582,15 @@ scram_verify_plain_password(const char *username, const char *password,
* On success, the iteration count, salt, stored key, and server key are
* extracted from the secret, and returned to the caller. For 'stored_key'
* and 'server_key', the caller must pass pre-allocated buffers of size
- * SCRAM_KEY_LEN. Salt is returned as a base64-encoded, null-terminated
+ * SCRAM_MAX_KEY_LEN. Salt is returned as a base64-encoded, null-terminated
* string. The buffer for the salt is palloc'd by this function.
*
* Returns true if the SCRAM secret has been parsed, and false otherwise.
*/
bool
-parse_scram_secret(const char *secret, int *iterations, char **salt,
- uint8 *stored_key, uint8 *server_key)
+parse_scram_secret(const char *secret, int *iterations,
+ pg_cryptohash_type *hash_type, int *key_length,
+ char **salt, uint8 *stored_key, uint8 *server_key)
{
char *v;
char *p;
@@ -606,6 +624,8 @@ parse_scram_secret(const char *secret, int *iterations, char **salt,
/* Parse the fields */
if (strcmp(scheme_str, "SCRAM-SHA-256") != 0)
goto invalid_secret;
+ *hash_type = PG_SHA256;
+ *key_length = SCRAM_SHA_256_KEY_LEN;
errno = 0;
*iterations = strtol(iterations_str, &p, 10);
@@ -631,17 +651,17 @@ parse_scram_secret(const char *secret, int *iterations, char **salt,
decoded_stored_buf = palloc(decoded_len);
decoded_len = pg_b64_decode(storedkey_str, strlen(storedkey_str),
decoded_stored_buf, decoded_len);
- if (decoded_len != SCRAM_KEY_LEN)
+ if (decoded_len != *key_length)
goto invalid_secret;
- memcpy(stored_key, decoded_stored_buf, SCRAM_KEY_LEN);
+ memcpy(stored_key, decoded_stored_buf, *key_length);
decoded_len = pg_b64_dec_len(strlen(serverkey_str));
decoded_server_buf = palloc(decoded_len);
decoded_len = pg_b64_decode(serverkey_str, strlen(serverkey_str),
decoded_server_buf, decoded_len);
- if (decoded_len != SCRAM_KEY_LEN)
+ if (decoded_len != *key_length)
goto invalid_secret;
- memcpy(server_key, decoded_server_buf, SCRAM_KEY_LEN);
+ memcpy(server_key, decoded_server_buf, *key_length);
return true;
@@ -655,20 +675,25 @@ 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.
+ * realistic, for when there is no stored secret, using SCRAM-SHA-256.
*
* Like in parse_scram_secret(), for 'stored_key' and 'server_key', the
- * caller must pass pre-allocated buffers of size SCRAM_KEY_LEN, and
+ * 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, int *iterations, char **salt,
+mock_scram_secret(const char *username, pg_cryptohash_type *hash_type,
+ int *iterations, int *key_length, char **salt,
uint8 *stored_key, uint8 *server_key)
{
char *raw_salt;
char *encoded_salt;
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;
+
/*
* Generate deterministic salt.
*
@@ -677,7 +702,7 @@ mock_scram_secret(const char *username, int *iterations, char **salt,
* as the salt generated for mock authentication uses the cluster's nonce
* value.
*/
- raw_salt = scram_mock_salt(username);
+ raw_salt = scram_mock_salt(username, *hash_type, *key_length);
if (raw_salt == NULL)
elog(ERROR, "could not encode salt");
@@ -695,8 +720,8 @@ mock_scram_secret(const char *username, int *iterations, char **salt,
*iterations = SCRAM_DEFAULT_ITERATIONS;
/* StoredKey and ServerKey are not used in a doomed authentication */
- memset(stored_key, 0, SCRAM_KEY_LEN);
- memset(server_key, 0, SCRAM_KEY_LEN);
+ memset(stored_key, 0, SCRAM_MAX_KEY_LEN);
+ memset(server_key, 0, SCRAM_MAX_KEY_LEN);
}
/*
@@ -1111,10 +1136,10 @@ verify_final_nonce(scram_state *state)
static bool
verify_client_proof(scram_state *state)
{
- uint8 ClientSignature[SCRAM_KEY_LEN];
- uint8 ClientKey[SCRAM_KEY_LEN];
- uint8 client_StoredKey[SCRAM_KEY_LEN];
- pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
+ uint8 ClientSignature[SCRAM_MAX_KEY_LEN];
+ uint8 ClientKey[SCRAM_MAX_KEY_LEN];
+ uint8 client_StoredKey[SCRAM_MAX_KEY_LEN];
+ pg_hmac_ctx *ctx = pg_hmac_create(state->hash_type);
int i;
const char *errstr = NULL;
@@ -1123,7 +1148,7 @@ verify_client_proof(scram_state *state)
* here even when processing the calculations as this could involve a mock
* authentication.
*/
- if (pg_hmac_init(ctx, state->StoredKey, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, state->StoredKey, state->key_length) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
@@ -1135,7 +1160,7 @@ verify_client_proof(scram_state *state)
pg_hmac_update(ctx,
(uint8 *) state->client_final_message_without_proof,
strlen(state->client_final_message_without_proof)) < 0 ||
- pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+ pg_hmac_final(ctx, ClientSignature, state->key_length) < 0)
{
elog(ERROR, "could not calculate client signature: %s",
pg_hmac_error(ctx));
@@ -1144,14 +1169,15 @@ verify_client_proof(scram_state *state)
pg_hmac_free(ctx);
/* Extract the ClientKey that the client calculated from the proof */
- for (i = 0; i < SCRAM_KEY_LEN; i++)
+ for (i = 0; i < state->key_length; i++)
ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
/* Hash it one more time, and compare with StoredKey */
- if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0)
+ if (scram_H(ClientKey, state->hash_type, state->key_length,
+ client_StoredKey, &errstr) < 0)
elog(ERROR, "could not hash stored key: %s", errstr);
- if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ if (memcmp(client_StoredKey, state->StoredKey, state->key_length) != 0)
return false;
return true;
@@ -1349,12 +1375,12 @@ read_client_final_message(scram_state *state, const char *input)
client_proof_len = pg_b64_dec_len(strlen(value));
client_proof = palloc(client_proof_len);
if (pg_b64_decode(value, strlen(value), client_proof,
- client_proof_len) != SCRAM_KEY_LEN)
+ client_proof_len) != state->key_length)
ereport(ERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("malformed SCRAM message"),
errdetail("Malformed proof in client-final-message.")));
- memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+ memcpy(state->ClientProof, client_proof, state->key_length);
pfree(client_proof);
if (*p != '\0')
@@ -1374,13 +1400,13 @@ read_client_final_message(scram_state *state, const char *input)
static char *
build_server_final_message(scram_state *state)
{
- uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerSignature[SCRAM_MAX_KEY_LEN];
char *server_signature_base64;
int siglen;
- pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
+ pg_hmac_ctx *ctx = pg_hmac_create(state->hash_type);
/* calculate ServerSignature */
- if (pg_hmac_init(ctx, state->ServerKey, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, state->ServerKey, state->key_length) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
@@ -1392,7 +1418,7 @@ build_server_final_message(scram_state *state)
pg_hmac_update(ctx,
(uint8 *) state->client_final_message_without_proof,
strlen(state->client_final_message_without_proof)) < 0 ||
- pg_hmac_final(ctx, ServerSignature, sizeof(ServerSignature)) < 0)
+ pg_hmac_final(ctx, ServerSignature, state->key_length) < 0)
{
elog(ERROR, "could not calculate server signature: %s",
pg_hmac_error(ctx));
@@ -1400,11 +1426,11 @@ build_server_final_message(scram_state *state)
pg_hmac_free(ctx);
- siglen = pg_b64_enc_len(SCRAM_KEY_LEN);
+ siglen = pg_b64_enc_len(state->key_length);
/* don't forget the zero-terminator */
server_signature_base64 = palloc(siglen + 1);
siglen = pg_b64_encode((const char *) ServerSignature,
- SCRAM_KEY_LEN, server_signature_base64,
+ state->key_length, server_signature_base64,
siglen);
if (siglen < 0)
elog(ERROR, "could not encode server signature");
@@ -1431,10 +1457,11 @@ build_server_final_message(scram_state *state)
* pointer to a static buffer of size SCRAM_DEFAULT_SALT_LEN, or NULL.
*/
static char *
-scram_mock_salt(const char *username)
+scram_mock_salt(const char *username, pg_cryptohash_type hash_type,
+ int key_length)
{
pg_cryptohash_ctx *ctx;
- static uint8 sha_digest[PG_SHA256_DIGEST_LENGTH];
+ static uint8 sha_digest[SCRAM_MAX_KEY_LEN];
char *mock_auth_nonce = GetMockAuthenticationNonce();
/*
@@ -1446,11 +1473,13 @@ scram_mock_salt(const char *username)
StaticAssertDecl(PG_SHA256_DIGEST_LENGTH >= SCRAM_DEFAULT_SALT_LEN,
"salt length greater than SHA256 digest length");
- ctx = pg_cryptohash_create(PG_SHA256);
+ Assert(hash_type == PG_SHA256);
+
+ ctx = pg_cryptohash_create(hash_type);
if (pg_cryptohash_init(ctx) < 0 ||
pg_cryptohash_update(ctx, (uint8 *) username, strlen(username)) < 0 ||
pg_cryptohash_update(ctx, (uint8 *) mock_auth_nonce, MOCK_AUTH_NONCE_LEN) < 0 ||
- pg_cryptohash_final(ctx, sha_digest, sizeof(sha_digest)) < 0)
+ pg_cryptohash_final(ctx, sha_digest, key_length) < 0)
{
pg_cryptohash_free(ctx);
return NULL;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 1ff8b0507d..a81af0749a 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -90,15 +90,17 @@ get_password_type(const char *shadow_pass)
{
char *encoded_salt;
int iterations;
- uint8 stored_key[SCRAM_KEY_LEN];
- uint8 server_key[SCRAM_KEY_LEN];
+ int key_length = 0;
+ pg_cryptohash_type hash_type;
+ uint8 stored_key[SCRAM_MAX_KEY_LEN];
+ uint8 server_key[SCRAM_MAX_KEY_LEN];
if (strncmp(shadow_pass, "md5", 3) == 0 &&
strlen(shadow_pass) == MD5_PASSWD_LEN &&
strspn(shadow_pass + 3, MD5_PASSWD_CHARSET) == MD5_PASSWD_LEN - 3)
return PASSWORD_TYPE_MD5;
- if (parse_scram_secret(shadow_pass, &iterations, &encoded_salt,
- stored_key, server_key))
+ if (parse_scram_secret(shadow_pass, &iterations, &hash_type, &key_length,
+ &encoded_salt, stored_key, server_key))
return PASSWORD_TYPE_SCRAM_SHA_256;
return PASSWORD_TYPE_PLAINTEXT;
}
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 1268625929..4f59910dea 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -33,6 +33,7 @@
*/
int
scram_SaltedPassword(const char *password,
+ pg_cryptohash_type hash_type, int key_length,
const char *salt, int saltlen, int iterations,
uint8 *result, const char **errstr)
{
@@ -40,9 +41,9 @@ scram_SaltedPassword(const char *password,
uint32 one = pg_hton32(1);
int i,
j;
- uint8 Ui[SCRAM_KEY_LEN];
- uint8 Ui_prev[SCRAM_KEY_LEN];
- pg_hmac_ctx *hmac_ctx = pg_hmac_create(PG_SHA256);
+ uint8 Ui[SCRAM_MAX_KEY_LEN];
+ uint8 Ui_prev[SCRAM_MAX_KEY_LEN];
+ pg_hmac_ctx *hmac_ctx = pg_hmac_create(hash_type);
if (hmac_ctx == NULL)
{
@@ -60,30 +61,30 @@ scram_SaltedPassword(const char *password,
if (pg_hmac_init(hmac_ctx, (uint8 *) password, password_len) < 0 ||
pg_hmac_update(hmac_ctx, (uint8 *) salt, saltlen) < 0 ||
pg_hmac_update(hmac_ctx, (uint8 *) &one, sizeof(uint32)) < 0 ||
- pg_hmac_final(hmac_ctx, Ui_prev, sizeof(Ui_prev)) < 0)
+ pg_hmac_final(hmac_ctx, Ui_prev, key_length) < 0)
{
*errstr = pg_hmac_error(hmac_ctx);
pg_hmac_free(hmac_ctx);
return -1;
}
- memcpy(result, Ui_prev, SCRAM_KEY_LEN);
+ memcpy(result, Ui_prev, key_length);
/* Subsequent iterations */
for (i = 2; i <= iterations; i++)
{
if (pg_hmac_init(hmac_ctx, (uint8 *) password, password_len) < 0 ||
- pg_hmac_update(hmac_ctx, (uint8 *) Ui_prev, SCRAM_KEY_LEN) < 0 ||
- pg_hmac_final(hmac_ctx, Ui, sizeof(Ui)) < 0)
+ pg_hmac_update(hmac_ctx, (uint8 *) Ui_prev, key_length) < 0 ||
+ pg_hmac_final(hmac_ctx, Ui, key_length) < 0)
{
*errstr = pg_hmac_error(hmac_ctx);
pg_hmac_free(hmac_ctx);
return -1;
}
- for (j = 0; j < SCRAM_KEY_LEN; j++)
+ for (j = 0; j < key_length; j++)
result[j] ^= Ui[j];
- memcpy(Ui_prev, Ui, SCRAM_KEY_LEN);
+ memcpy(Ui_prev, Ui, key_length);
}
pg_hmac_free(hmac_ctx);
@@ -92,16 +93,17 @@ scram_SaltedPassword(const char *password,
/*
- * Calculate SHA-256 hash for a NULL-terminated string. (The NULL terminator is
+ * Calculate hash for a NULL-terminated string. (The NULL terminator is
* not included in the hash). Returns 0 on success, -1 on failure with *errstr
* pointing to a message about the error details.
*/
int
-scram_H(const uint8 *input, int len, uint8 *result, const char **errstr)
+scram_H(const uint8 *input, pg_cryptohash_type hash_type, int key_length,
+ uint8 *result, const char **errstr)
{
pg_cryptohash_ctx *ctx;
- ctx = pg_cryptohash_create(PG_SHA256);
+ ctx = pg_cryptohash_create(hash_type);
if (ctx == NULL)
{
*errstr = pg_cryptohash_error(NULL); /* returns OOM */
@@ -109,8 +111,8 @@ scram_H(const uint8 *input, int len, uint8 *result, const char **errstr)
}
if (pg_cryptohash_init(ctx) < 0 ||
- pg_cryptohash_update(ctx, input, len) < 0 ||
- pg_cryptohash_final(ctx, result, SCRAM_KEY_LEN) < 0)
+ pg_cryptohash_update(ctx, input, key_length) < 0 ||
+ pg_cryptohash_final(ctx, result, key_length) < 0)
{
*errstr = pg_cryptohash_error(ctx);
pg_cryptohash_free(ctx);
@@ -126,10 +128,11 @@ scram_H(const uint8 *input, int len, uint8 *result, const char **errstr)
* pointing to a message about the error details.
*/
int
-scram_ClientKey(const uint8 *salted_password, uint8 *result,
- const char **errstr)
+scram_ClientKey(const uint8 *salted_password,
+ pg_cryptohash_type hash_type, int key_length,
+ uint8 *result, const char **errstr)
{
- pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
+ pg_hmac_ctx *ctx = pg_hmac_create(hash_type);
if (ctx == NULL)
{
@@ -137,9 +140,9 @@ scram_ClientKey(const uint8 *salted_password, uint8 *result,
return -1;
}
- if (pg_hmac_init(ctx, salted_password, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, salted_password, key_length) < 0 ||
pg_hmac_update(ctx, (uint8 *) "Client Key", strlen("Client Key")) < 0 ||
- pg_hmac_final(ctx, result, SCRAM_KEY_LEN) < 0)
+ pg_hmac_final(ctx, result, key_length) < 0)
{
*errstr = pg_hmac_error(ctx);
pg_hmac_free(ctx);
@@ -155,10 +158,11 @@ scram_ClientKey(const uint8 *salted_password, uint8 *result,
* pointing to a message about the error details.
*/
int
-scram_ServerKey(const uint8 *salted_password, uint8 *result,
- const char **errstr)
+scram_ServerKey(const uint8 *salted_password,
+ pg_cryptohash_type hash_type, int key_length,
+ uint8 *result, const char **errstr)
{
- pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
+ pg_hmac_ctx *ctx = pg_hmac_create(hash_type);
if (ctx == NULL)
{
@@ -166,9 +170,9 @@ scram_ServerKey(const uint8 *salted_password, uint8 *result,
return -1;
}
- if (pg_hmac_init(ctx, salted_password, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, salted_password, key_length) < 0 ||
pg_hmac_update(ctx, (uint8 *) "Server Key", strlen("Server Key")) < 0 ||
- pg_hmac_final(ctx, result, SCRAM_KEY_LEN) < 0)
+ pg_hmac_final(ctx, result, key_length) < 0)
{
*errstr = pg_hmac_error(ctx);
pg_hmac_free(ctx);
@@ -192,12 +196,13 @@ scram_ServerKey(const uint8 *salted_password, uint8 *result,
* error details.
*/
char *
-scram_build_secret(const char *salt, int saltlen, int iterations,
+scram_build_secret(pg_cryptohash_type hash_type, int key_length,
+ const char *salt, int saltlen, int iterations,
const char *password, const char **errstr)
{
- uint8 salted_password[SCRAM_KEY_LEN];
- uint8 stored_key[SCRAM_KEY_LEN];
- uint8 server_key[SCRAM_KEY_LEN];
+ uint8 salted_password[SCRAM_MAX_KEY_LEN];
+ uint8 stored_key[SCRAM_MAX_KEY_LEN];
+ uint8 server_key[SCRAM_MAX_KEY_LEN];
char *result;
char *p;
int maxlen;
@@ -206,15 +211,21 @@ scram_build_secret(const char *salt, int saltlen, int iterations,
int encoded_server_len;
int encoded_result;
+ Assert(hash_type == PG_SHA256);
+
if (iterations <= 0)
iterations = SCRAM_DEFAULT_ITERATIONS;
/* Calculate StoredKey and ServerKey */
- if (scram_SaltedPassword(password, salt, saltlen, iterations,
+ if (scram_SaltedPassword(password, hash_type, key_length,
+ salt, saltlen, iterations,
salted_password, errstr) < 0 ||
- scram_ClientKey(salted_password, stored_key, errstr) < 0 ||
- scram_H(stored_key, SCRAM_KEY_LEN, stored_key, errstr) < 0 ||
- scram_ServerKey(salted_password, server_key, errstr) < 0)
+ scram_ClientKey(salted_password, hash_type, key_length,
+ stored_key, errstr) < 0 ||
+ scram_H(stored_key, hash_type, key_length,
+ stored_key, errstr) < 0 ||
+ scram_ServerKey(salted_password, hash_type, key_length,
+ server_key, errstr) < 0)
{
/* errstr is filled already here */
#ifdef FRONTEND
@@ -231,8 +242,8 @@ scram_build_secret(const char *salt, int saltlen, int iterations,
*----------
*/
encoded_salt_len = pg_b64_enc_len(saltlen);
- encoded_stored_len = pg_b64_enc_len(SCRAM_KEY_LEN);
- encoded_server_len = pg_b64_enc_len(SCRAM_KEY_LEN);
+ encoded_stored_len = pg_b64_enc_len(key_length);
+ encoded_server_len = pg_b64_enc_len(key_length);
maxlen = strlen("SCRAM-SHA-256") + 1
+ 10 + 1 /* iteration count */
@@ -269,7 +280,7 @@ scram_build_secret(const char *salt, int saltlen, int iterations,
*(p++) = '$';
/* stored key */
- encoded_result = pg_b64_encode((char *) stored_key, SCRAM_KEY_LEN, p,
+ encoded_result = pg_b64_encode((char *) stored_key, key_length, p,
encoded_stored_len);
if (encoded_result < 0)
{
@@ -286,7 +297,7 @@ scram_build_secret(const char *salt, int saltlen, int iterations,
*(p++) = ':';
/* server key */
- encoded_result = pg_b64_encode((char *) server_key, SCRAM_KEY_LEN, p,
+ encoded_result = pg_b64_encode((char *) server_key, key_length, p,
encoded_server_len);
if (encoded_result < 0)
{
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index c500bea9e7..7410d5ba52 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -58,8 +58,12 @@ typedef struct
char *password;
char *sasl_mechanism;
+ /* State data depending on the hash type */
+ pg_cryptohash_type hash_type;
+ int key_length;
+
/* We construct these */
- uint8 SaltedPassword[SCRAM_KEY_LEN];
+ uint8 SaltedPassword[SCRAM_MAX_KEY_LEN];
char *client_nonce;
char *client_first_message_bare;
char *client_final_message_without_proof;
@@ -73,7 +77,7 @@ typedef struct
/* These come from the server-final message */
char *server_final_message;
- char ServerSignature[SCRAM_KEY_LEN];
+ char ServerSignature[SCRAM_MAX_KEY_LEN];
} fe_scram_state;
static bool read_server_first_message(fe_scram_state *state, char *input);
@@ -106,8 +110,10 @@ scram_init(PGconn *conn,
memset(state, 0, sizeof(fe_scram_state));
state->conn = conn;
state->state = FE_SCRAM_INIT;
- state->sasl_mechanism = strdup(sasl_mechanism);
+ state->key_length = SCRAM_SHA_256_KEY_LEN;
+ state->hash_type = PG_SHA256;
+ state->sasl_mechanism = strdup(sasl_mechanism);
if (!state->sasl_mechanism)
{
free(state);
@@ -450,7 +456,7 @@ build_client_final_message(fe_scram_state *state)
{
PQExpBufferData buf;
PGconn *conn = state->conn;
- uint8 client_proof[SCRAM_KEY_LEN];
+ uint8 client_proof[SCRAM_MAX_KEY_LEN];
char *result;
int encoded_len;
const char *errstr = NULL;
@@ -565,11 +571,11 @@ build_client_final_message(fe_scram_state *state)
}
appendPQExpBufferStr(&buf, ",p=");
- encoded_len = pg_b64_enc_len(SCRAM_KEY_LEN);
+ encoded_len = pg_b64_enc_len(state->key_length);
if (!enlargePQExpBuffer(&buf, encoded_len))
goto oom_error;
encoded_len = pg_b64_encode((char *) client_proof,
- SCRAM_KEY_LEN,
+ state->key_length,
buf.data + buf.len,
encoded_len);
if (encoded_len < 0)
@@ -738,13 +744,14 @@ read_server_final_message(fe_scram_state *state, char *input)
strlen(encoded_server_signature),
decoded_server_signature,
server_signature_len);
- if (server_signature_len != SCRAM_KEY_LEN)
+ if (server_signature_len != state->key_length)
{
free(decoded_server_signature);
libpq_append_conn_error(conn, "malformed SCRAM message (invalid server signature)");
return false;
}
- memcpy(state->ServerSignature, decoded_server_signature, SCRAM_KEY_LEN);
+ memcpy(state->ServerSignature, decoded_server_signature,
+ state->key_length);
free(decoded_server_signature);
return true;
@@ -760,13 +767,13 @@ calculate_client_proof(fe_scram_state *state,
const char *client_final_message_without_proof,
uint8 *result, const char **errstr)
{
- uint8 StoredKey[SCRAM_KEY_LEN];
- uint8 ClientKey[SCRAM_KEY_LEN];
- uint8 ClientSignature[SCRAM_KEY_LEN];
+ uint8 StoredKey[SCRAM_MAX_KEY_LEN];
+ uint8 ClientKey[SCRAM_MAX_KEY_LEN];
+ uint8 ClientSignature[SCRAM_MAX_KEY_LEN];
int i;
pg_hmac_ctx *ctx;
- ctx = pg_hmac_create(PG_SHA256);
+ ctx = pg_hmac_create(state->hash_type);
if (ctx == NULL)
{
*errstr = pg_hmac_error(NULL); /* returns OOM */
@@ -777,18 +784,21 @@ calculate_client_proof(fe_scram_state *state,
* Calculate SaltedPassword, and store it in 'state' so that we can reuse
* it later in verify_server_signature.
*/
- if (scram_SaltedPassword(state->password, state->salt, state->saltlen,
+ if (scram_SaltedPassword(state->password, state->hash_type,
+ state->key_length, state->salt, state->saltlen,
state->iterations, state->SaltedPassword,
errstr) < 0 ||
- scram_ClientKey(state->SaltedPassword, ClientKey, errstr) < 0 ||
- scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey, errstr) < 0)
+ scram_ClientKey(state->SaltedPassword, state->hash_type,
+ state->key_length, ClientKey, errstr) < 0 ||
+ scram_H(ClientKey, state->hash_type, state->key_length,
+ StoredKey, errstr) < 0)
{
/* errstr is already filled here */
pg_hmac_free(ctx);
return false;
}
- if (pg_hmac_init(ctx, StoredKey, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, StoredKey, state->key_length) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
@@ -800,14 +810,14 @@ calculate_client_proof(fe_scram_state *state,
pg_hmac_update(ctx,
(uint8 *) client_final_message_without_proof,
strlen(client_final_message_without_proof)) < 0 ||
- pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+ pg_hmac_final(ctx, ClientSignature, state->key_length) < 0)
{
*errstr = pg_hmac_error(ctx);
pg_hmac_free(ctx);
return false;
}
- for (i = 0; i < SCRAM_KEY_LEN; i++)
+ for (i = 0; i < state->key_length; i++)
result[i] = ClientKey[i] ^ ClientSignature[i];
pg_hmac_free(ctx);
@@ -825,18 +835,19 @@ static bool
verify_server_signature(fe_scram_state *state, bool *match,
const char **errstr)
{
- uint8 expected_ServerSignature[SCRAM_KEY_LEN];
- uint8 ServerKey[SCRAM_KEY_LEN];
+ uint8 expected_ServerSignature[SCRAM_MAX_KEY_LEN];
+ uint8 ServerKey[SCRAM_MAX_KEY_LEN];
pg_hmac_ctx *ctx;
- ctx = pg_hmac_create(PG_SHA256);
+ ctx = pg_hmac_create(state->hash_type);
if (ctx == NULL)
{
*errstr = pg_hmac_error(NULL); /* returns OOM */
return false;
}
- if (scram_ServerKey(state->SaltedPassword, ServerKey, errstr) < 0)
+ if (scram_ServerKey(state->SaltedPassword, state->hash_type,
+ state->key_length, ServerKey, errstr) < 0)
{
/* errstr is filled already */
pg_hmac_free(ctx);
@@ -844,7 +855,7 @@ verify_server_signature(fe_scram_state *state, bool *match,
}
/* calculate ServerSignature */
- if (pg_hmac_init(ctx, ServerKey, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, ServerKey, state->key_length) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
@@ -857,7 +868,7 @@ verify_server_signature(fe_scram_state *state, bool *match,
(uint8 *) state->client_final_message_without_proof,
strlen(state->client_final_message_without_proof)) < 0 ||
pg_hmac_final(ctx, expected_ServerSignature,
- sizeof(expected_ServerSignature)) < 0)
+ state->key_length) < 0)
{
*errstr = pg_hmac_error(ctx);
pg_hmac_free(ctx);
@@ -867,7 +878,8 @@ verify_server_signature(fe_scram_state *state, bool *match,
pg_hmac_free(ctx);
/* signature processed, so now check after it */
- if (memcmp(expected_ServerSignature, state->ServerSignature, SCRAM_KEY_LEN) != 0)
+ if (memcmp(expected_ServerSignature, state->ServerSignature,
+ state->key_length) != 0)
*match = false;
else
*match = true;
@@ -912,7 +924,8 @@ pg_fe_scram_build_secret(const char *password, const char **errstr)
return NULL;
}
- result = scram_build_secret(saltbuf, SCRAM_DEFAULT_SALT_LEN,
+ result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN, saltbuf,
+ SCRAM_DEFAULT_SALT_LEN,
SCRAM_DEFAULT_ITERATIONS, password,
errstr);
--
2.38.1
On 12/16/22 10:08 PM, Michael Paquier wrote:
On Thu, Dec 15, 2022 at 04:59:52AM +0900, Michael Paquier wrote:
However, that's only half of the picture. The key length and the hash
type (or just the hash type to know what's the digest/key length to
use but that's more invasive) still need to be sent across the
internal routines of SCRAM and attached to the state data of the
frontend and the backend or we won't be able to do the hash and HMAC
computations dependent on that.Attached is a patch to do exactly that, and as a result v2 is half the
size of v1:
- SCRAM_KEY_LEN is now named SCRAM_MAX_KEY_LEN, adding a note that
this should be kept in sync as the maximum digest size of the
supported hash methods. This is used as the method to size all the
internal buffers of the SCRAM routines.
- SCRAM_SHA_256_KEY_LEN is used to track the key length for
SCRAM-SHA-256, the one initialized with the state data.
- No changes in the internal, the buffers are just resized based on
the max defined.
Thanks! I looked through this and ran tests. I like the approach overall
and I think this sets us up pretty well for expanding our SCRAM support.
Only a couple of minor comments:
- I noticed a couple of these in "scram_build_secret" and "scram_mock_salt":
Assert(hash_type == PG_SHA256);
Presumably there to ensure 1/ We're setting a hash_type and 2/ as
possibly a reminder to update the assertions if/when we support more
digests.
With the assertion in "scram_build_secret", that value is set from the
"PG_SHA256" constant anyway, so I don't know if it actually gives us
anything other than a reminder? With "scram_mock"salt" the value
ultimately comes from state (which is currently set from the constant),
so perhaps there is a guard there.
At a minimum, I'd suggest a comment around it, especially if it's set up
to be removed at a future date.
- I do like the "SCRAM_MAX_KEY_LEN" change, and I see we're now passing
"key_length" around to ensure we're only using the desired number of
bytes. I am a little queasy that once we expand "SCRAM_MAX_KEY_LEN" we
run the risk of having the smaller hashes accidentally use the extra
bytes in their calculations. However, I think that's more a fear than
not, an we can mitigate the risk with testing.
I'd like to move on with that in the next couple of days (still need
to study more the other areas of the code to see what else could be
made more pluggable), so let me know if there are any objections..
No objections. I think this decreases the lift to supporting more
variations of SCRAM.
Once committed, I'll rebase the server-side SCRAM functions patch with
this. I may want to rethink the interface for that to allow the digest
to be "selectable" (vs. from the function) but I'll discuss on that
thread[1]/messages/by-id/fce7228e-d0d6-64a1-3dcb-bba85c2fac85@postgresql.org.
Thanks,
Jonathan
[1]: /messages/by-id/fce7228e-d0d6-64a1-3dcb-bba85c2fac85@postgresql.org
/messages/by-id/fce7228e-d0d6-64a1-3dcb-bba85c2fac85@postgresql.org
On Mon, Dec 19, 2022 at 02:58:24PM -0500, Jonathan S. Katz wrote:
With the assertion in "scram_build_secret", that value is set from the
"PG_SHA256" constant anyway, so I don't know if it actually gives us
anything other than a reminder? With "scram_mock"salt" the value ultimately
comes from state (which is currently set from the constant), so perhaps
there is a guard there.
Yes, these mostly act as reminders to anybody touching this code, so
I'd like to keep both. For the mock part, we may also want to use
something different than SHA-256.
At a minimum, I'd suggest a comment around it, especially if it's set up to
be removed at a future date.
Okay, sure.
- I do like the "SCRAM_MAX_KEY_LEN" change, and I see we're now passing
"key_length" around to ensure we're only using the desired number of bytes.
I am a little queasy that once we expand "SCRAM_MAX_KEY_LEN" we run the risk
of having the smaller hashes accidentally use the extra bytes in their
calculations. However, I think that's more a fear than not, an we can
mitigate the risk with testing.
A few code paths relied on the size of these local buffers, now they
just use the passed-in key length from the state.
No objections. I think this decreases the lift to supporting more variations
of SCRAM.Once committed, I'll rebase the server-side SCRAM functions patch with this.
I may want to rethink the interface for that to allow the digest to be
"selectable" (vs. from the function) but I'll discuss on that thread[1].
Thanks! I have applied for I have here.. There are other pieces to
think about in this area.
--
Michael
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
Attachments:
0001-WIP-SCRAM-SHA-512.patchtext/x-diff; charset=us-asciiDownload
From 49a3c993a1df722da01f16839eb1d6185c205092 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@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
On 12/20/22 2:25 AM, Michael Paquier wrote:
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.
Great! Thanks for doing a quick "stress test" on this.
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.
This does seem like a challenge, particularly if we have to support
multiple different SCRAM hashes.
Perhaps this can be done with an interface change in HBA. For example,
we could rename the auth method from "scram-sha-256" to "scram" and
support an option list of hashes (e.g. "hash=sha-512,sha-256"). We can
then advertise the user-selected hashes as part of the handshake.
For backwards compatibility, we can take an auth method of
"scram-sha-256" to mean "scram" + using a sha-256 hash. Similarly, if no
hash map is defined, we can default to "scram-sha-256".
Anyway, I understand this point would require more discussion, but
perhaps it is a way to simplify the amount of code we would need to
write to support more hashes.
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.
*nods*
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?
Is this the case where we specify "md5" as the auth method but the
user-password is stored in SCRAM?
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.
Yeah, I think a user may want to select which ones they want to use
(e.g. they may not want to advertise SHA-256).
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.
Thanks for starting this.
Jonathan
On Tue, Dec 20, 2022 at 08:45:29PM -0500, Jonathan S. Katz wrote:
On 12/20/22 2:25 AM, Michael Paquier wrote:
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?Is this the case where we specify "md5" as the auth method but the
user-password is stored in SCRAM?
Yes. A port storing uaMD5 with a SCRAM password makes the backend use
SASL for the whole exchange. At quick glance, we could fallback to
look at the password of the user sent by the startup packet and
advertise the mechanisms based on that because we know that one user
=> one password currently. I'd need to double-check on the RFCs to
see if there is anything specific here to worry about. The recent
ones being worked on may tell more.
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.Yeah, I think a user may want to select which ones they want to use (e.g.
they may not want to advertise SHA-256).
Yep, they should be able to do so. libpq should select the strongest
one if the server sends all of them, but things like
https://commitfest.postgresql.org/41/3716/ should be able to enforce
one over the other.
--
Michael