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