*
* See the following RFCs for more details:
* - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 5803: https://tools.ietf.org/html/rfc5803
* - RFC 7677: https://tools.ietf.org/html/rfc7677
*
* Here are some differences:
* - Channel binding is not supported yet.
*
*
- * The password stored in pg_authid consists of the salt, iteration count,
+ * The password stored in pg_authid consists of the iteration count, salt,
* StoredKey and ServerKey.
*
* SASLprep usage
const char *username; /* username from startup packet */
- char *salt; /* base64-encoded */
int iterations;
+ char *salt; /* base64-encoded */
uint8 StoredKey[SCRAM_KEY_LEN];
uint8 ServerKey[SCRAM_KEY_LEN];
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 bool parse_scram_verifier(const char *verifier, char **salt,
- int *iterations, uint8 *stored_key, uint8 *server_key);
-static void mock_scram_verifier(const char *username, char **salt, int *iterations,
- uint8 *stored_key, uint8 *server_key);
+static bool parse_scram_verifier(const char *verifier, int *iterations,
+ char **salt, uint8 *stored_key, uint8 *server_key);
+static void mock_scram_verifier(const char *username, int *iterations,
+ char **salt, uint8 *stored_key, uint8 *server_key);
static bool is_scram_printable(char *p);
static char *sanitize_char(char c);
static char *scram_MockSalt(const char *username);
if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
{
- if (parse_scram_verifier(shadow_pass, &state->salt, &state->iterations,
+ if (parse_scram_verifier(shadow_pass, &state->iterations, &state->salt,
state->StoredKey, state->ServerKey))
got_verifier = true;
else
verifier = scram_build_verifier(username, shadow_pass, 0);
- (void) parse_scram_verifier(verifier, &state->salt, &state->iterations,
+ (void) parse_scram_verifier(verifier, &state->iterations, &state->salt,
state->StoredKey, state->ServerKey);
pfree(verifier);
*/
if (!got_verifier)
{
- mock_scram_verifier(username, &state->salt, &state->iterations,
+ mock_scram_verifier(username, &state->iterations, &state->salt,
state->StoredKey, state->ServerKey);
state->doomed = true;
}
scram_build_verifier(const char *username, const char *password,
int iterations)
{
- uint8 keybuf[SCRAM_KEY_LEN + 1];
- char storedkey_hex[SCRAM_KEY_LEN * 2 + 1];
- char serverkey_hex[SCRAM_KEY_LEN * 2 + 1];
- char salt[SCRAM_SALT_LEN];
- char *encoded_salt;
- int encoded_len;
char *prep_password = NULL;
pg_saslprep_rc rc;
+ char saltbuf[SCRAM_SALT_LEN];
+ uint8 keybuf[SCRAM_KEY_LEN];
+ char *encoded_salt;
+ char *encoded_storedkey;
+ char *encoded_serverkey;
+ int encoded_len;
+ char *result;
/*
* Normalize the password with SASLprep. If that doesn't work, because
if (iterations <= 0)
iterations = SCRAM_ITERATIONS_DEFAULT;
- if (!pg_backend_random(salt, SCRAM_SALT_LEN))
+ /* Generate salt, and encode it in base64 */
+ if (!pg_backend_random(saltbuf, SCRAM_SALT_LEN))
{
ereport(LOG,
(errcode(ERRCODE_INTERNAL_ERROR),
}
encoded_salt = palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1);
- encoded_len = pg_b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_len = pg_b64_encode(saltbuf, SCRAM_SALT_LEN, encoded_salt);
encoded_salt[encoded_len] = '\0';
- /* Calculate StoredKey, and encode it in hex */
- scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ /* Calculate StoredKey, and encode it in base64 */
+ scram_ClientOrServerKey(password, saltbuf, SCRAM_SALT_LEN,
iterations, SCRAM_CLIENT_KEY_NAME, keybuf);
scram_H(keybuf, SCRAM_KEY_LEN, keybuf); /* StoredKey */
- (void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, storedkey_hex);
- storedkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+ encoded_storedkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
+ encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN,
+ encoded_storedkey);
+ encoded_storedkey[encoded_len] = '\0';
/* And same for ServerKey */
- scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN, iterations,
+ scram_ClientOrServerKey(password, saltbuf, SCRAM_SALT_LEN, iterations,
SCRAM_SERVER_KEY_NAME, keybuf);
- (void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, serverkey_hex);
- serverkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+ encoded_serverkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
+ encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN,
+ encoded_serverkey);
+ encoded_serverkey[encoded_len] = '\0';
+
+ result = psprintf("SCRAM-SHA-256$%d:%s$%s:%s", iterations, encoded_salt,
+ encoded_storedkey, encoded_serverkey);
if (prep_password)
pfree(prep_password);
+ pfree(encoded_salt);
+ pfree(encoded_storedkey);
+ pfree(encoded_serverkey);
- return psprintf("scram-sha-256:%s:%d:%s:%s", encoded_salt, iterations, storedkey_hex, serverkey_hex);
+ return result;
}
/*
char *prep_password = NULL;
pg_saslprep_rc rc;
- if (!parse_scram_verifier(verifier, &encoded_salt, &iterations,
+ if (!parse_scram_verifier(verifier, &iterations, &encoded_salt,
stored_key, server_key))
{
/*
bool
is_scram_verifier(const char *verifier)
{
- char *salt = NULL;
int iterations;
+ char *salt = NULL;
uint8 stored_key[SCRAM_KEY_LEN];
uint8 server_key[SCRAM_KEY_LEN];
bool result;
- result = parse_scram_verifier(verifier, &salt, &iterations, stored_key, server_key);
+ result = parse_scram_verifier(verifier, &iterations, &salt,
+ stored_key, server_key);
if (salt)
pfree(salt);
* Returns true if the SCRAM verifier has been parsed, and false otherwise.
*/
static bool
-parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+parse_scram_verifier(const char *verifier, int *iterations, char **salt,
uint8 *stored_key, uint8 *server_key)
{
char *v;
char *p;
+ char *scheme_str;
+ char *salt_str;
+ char *iterations_str;
+ char *storedkey_str;
+ char *serverkey_str;
+ int decoded_len;
+ char *decoded_salt_buf;
/*
* The verifier is of form:
*
- * scram-sha-256:<salt>:<iterations>:<storedkey>:<serverkey>
+ * SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>
*/
- if (strncmp(verifier, "scram-sha-256:", strlen("scram-sha-256:")) != 0)
- return false;
-
- v = pstrdup(verifier + strlen("scram-sha-256:"));
-
- /* salt */
- if ((p = strtok(v, ":")) == NULL)
+ v = pstrdup(verifier);
+ if ((scheme_str = strtok(v, "$")) == NULL)
+ goto invalid_verifier;
+ if ((iterations_str = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if ((salt_str = strtok(NULL, "$")) == NULL)
+ goto invalid_verifier;
+ if ((storedkey_str = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if ((serverkey_str = strtok(NULL, "")) == NULL)
goto invalid_verifier;
- *salt = pstrdup(p);
- /* iterations */
- if ((p = strtok(NULL, ":")) == NULL)
+ /* Parse the fields */
+ if (strcmp(scheme_str, "SCRAM-SHA-256") != 0)
goto invalid_verifier;
+
errno = 0;
- *iterations = strtol(p, &p, 10);
+ *iterations = strtol(iterations_str, &p, 10);
if (*p || errno != 0)
goto invalid_verifier;
- /* storedkey */
- if ((p = strtok(NULL, ":")) == NULL)
- goto invalid_verifier;
- if (strlen(p) != SCRAM_KEY_LEN * 2)
+ /*
+ * Verify that the salt is in Base64-encoded format, by decoding it,
+ * although we return the encoded version to the caller.
+ */
+ decoded_salt_buf = palloc(pg_b64_dec_len(strlen(salt_str)));
+ decoded_len = pg_b64_decode(salt_str, strlen(salt_str), decoded_salt_buf);
+ if (decoded_len < 0)
goto invalid_verifier;
+ *salt = pstrdup(salt_str);
- hex_decode(p, SCRAM_KEY_LEN * 2, (char *) stored_key);
+ /*
+ * Decode StoredKey and ServerKey.
+ */
+ if (pg_b64_dec_len(strlen(storedkey_str) != SCRAM_KEY_LEN))
+ goto invalid_verifier;
+ decoded_len = pg_b64_decode(storedkey_str, strlen(storedkey_str),
+ (char *) stored_key);
+ if (decoded_len != SCRAM_KEY_LEN)
+ goto invalid_verifier;
- /* serverkey */
- if ((p = strtok(NULL, ":")) == NULL)
+ if (pg_b64_dec_len(strlen(serverkey_str) != SCRAM_KEY_LEN))
goto invalid_verifier;
- if (strlen(p) != SCRAM_KEY_LEN * 2)
+ decoded_len = pg_b64_decode(serverkey_str, strlen(serverkey_str),
+ (char *) server_key);
+ if (decoded_len != SCRAM_KEY_LEN)
goto invalid_verifier;
- hex_decode(p, SCRAM_KEY_LEN * 2, (char *) server_key);
- pfree(v);
return true;
invalid_verifier:
pfree(v);
+ *salt = NULL;
return false;
}
static void
-mock_scram_verifier(const char *username, char **salt, int *iterations,
+mock_scram_verifier(const char *username, int *iterations, char **salt,
uint8 *stored_key, uint8 *server_key)
{
char *raw_salt;
-- check list of created entries
--
-- The scram verifier will look something like:
--- scram-sha-256:E4HxLGtnRzsYwg==:4096:5ebc825510cb7862efd87dfa638d8337179e6913a724441dc9e888a856fbc10c:e966b1c72fad89d69aaebb156eae04edc9581286f92207c044711e79cd461bee
+-- SCRAM-SHA-256$4096:E4HxLGtnRzsYwg==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo=
--
-- Since the salt is random, the exact value stored will be different on every test
-- run. Use a regular expression to mask the changing parts.
-SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):(\d+):(\w+):(\w+)', '\1:<salt>:\3:<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/]+==)\$([a-zA-Z0-9+/]+=):([a-zA-Z0-9+/]+=)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
WHERE rolname LIKE 'regress_passwd%'
ORDER BY rolname, rolpassword;
regress_passwd1 | role_pwd1
regress_passwd2 | md54044304ba511dd062133eb5b4b84a2a3
regress_passwd3 | md50e5699b6911d87f17a08b8d76a21e8b8
- regress_passwd4 | scram-sha-256:<salt>:4096:<storedkey>:<serverkey>
+ regress_passwd4 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
regress_passwd5 |
(5 rows)
ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5dfa155cadd5f4ad57860162f3fab9cdb'; -- encrypted with MD5
SET password_encryption = 'md5';
ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
-ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'scram-sha-256:VLK4RMaQLCvNtQ==:4096:3ded2376f7aafa93b1bdbd71bcc18b7d6ee50ed018029cc583d152ef3fc7d430:a6dd36dfc94c181956a6ae95f05e01b1864f0a22a2657d1de4ba84d2a24dc438'; -- client-supplied SCRAM verifier, use as it is
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'SCRAM-SHA-256$4096:VLK4RMaQLCvNtQ==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo='; -- client-supplied SCRAM verifier, use as it is
SET password_encryption = 'scram-sha-256';
ALTER ROLE regress_passwd5 ENCRYPTED PASSWORD 'foo'; -- create SCRAM verifier
CREATE ROLE regress_passwd6 ENCRYPTED PASSWORD 'md53725413363ab045e20521bf36b8d8d7f'; -- encrypted with MD5, use as it is
-SELECT rolname, regexp_replace(rolpassword, '(scram-sha-256):([a-zA-Z0-9+/]+==):(\d+):(\w+):(\w+)', '\1:<salt>:\3:<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/]+==)\$([a-zA-Z0-9+/]+=):([a-zA-Z0-9+/]+=)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
WHERE rolname LIKE 'regress_passwd%'
ORDER BY rolname, rolpassword;
regress_passwd1 | foo
regress_passwd2 | md5dfa155cadd5f4ad57860162f3fab9cdb
regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
- regress_passwd4 | scram-sha-256:<salt>:4096:<storedkey>:<serverkey>
- regress_passwd5 | scram-sha-256:<salt>:4096:<storedkey>:<serverkey>
+ regress_passwd4 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
+ regress_passwd5 | SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
regress_passwd6 | md53725413363ab045e20521bf36b8d8d7f
(6 rows)