mirror of
https://github.com/postgres/postgres.git
synced 2025-05-08 07:21:33 +03:00
Replace the hardcoded value with a GUC such that the iteration count can be raised in order to increase protection against brute-force attacks. The hardcoded value for SCRAM iteration count was defined to be 4096, which is taken from RFC 7677, so set the default for the GUC to 4096 to match. In RFC 7677 the recommendation is at least 15000 iterations but 4096 is listed as a SHOULD requirement given that it's estimated to yield a 0.5s processing time on a mobile handset of the time of RFC writing (late 2015). Raising the iteration count of SCRAM will make stored passwords more resilient to brute-force attacks at a higher computational cost during connection establishment. Lowering the count will reduce computational overhead during connections at the tradeoff of reducing strength against brute-force attacks. There are however platforms where even a modest iteration count yields a too high computational overhead, with weaker password encryption schemes chosen as a result. In these situations, SCRAM with a very low iteration count still gives benefits over weaker schemes like md5, so we allow the iteration count to be set to one at the low end. The new GUC is intentionally generically named such that it can be made to support future SCRAM standards should they emerge. At that point the value can be made into key:value pairs with an undefined key as a default which will be backwards compatible with this. Reviewed-by: Michael Paquier <michael@paquier.xyz> Reviewed-by: Jonathan S. Katz <jkatz@postgresql.org> Discussion: https://postgr.es/m/F72E7BC7-189F-4B17-BF47-9735EB72C364@yesql.se
320 lines
7.7 KiB
C
320 lines
7.7 KiB
C
/*-------------------------------------------------------------------------
|
|
* scram-common.c
|
|
* Shared frontend/backend code for SCRAM authentication
|
|
*
|
|
* This contains the common low-level functions needed in both frontend and
|
|
* backend, for implement the Salted Challenge Response Authentication
|
|
* Mechanism (SCRAM), per IETF's RFC 5802.
|
|
*
|
|
* Portions Copyright (c) 2017-2023, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* src/common/scram-common.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#ifndef FRONTEND
|
|
#include "postgres.h"
|
|
#else
|
|
#include "postgres_fe.h"
|
|
#endif
|
|
|
|
#include "common/base64.h"
|
|
#include "common/hmac.h"
|
|
#include "common/scram-common.h"
|
|
#include "port/pg_bswap.h"
|
|
|
|
/*
|
|
* Calculate SaltedPassword.
|
|
*
|
|
* The password should already be normalized by SASLprep. Returns 0 on
|
|
* success, -1 on failure with *errstr pointing to a message about the
|
|
* error details.
|
|
*/
|
|
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)
|
|
{
|
|
int password_len = strlen(password);
|
|
uint32 one = pg_hton32(1);
|
|
int i,
|
|
j;
|
|
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)
|
|
{
|
|
*errstr = pg_hmac_error(NULL); /* returns OOM */
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Iterate hash calculation of HMAC entry using given salt. This is
|
|
* essentially PBKDF2 (see RFC2898) with HMAC() as the pseudorandom
|
|
* function.
|
|
*/
|
|
|
|
/* First iteration */
|
|
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, key_length) < 0)
|
|
{
|
|
*errstr = pg_hmac_error(hmac_ctx);
|
|
pg_hmac_free(hmac_ctx);
|
|
return -1;
|
|
}
|
|
|
|
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, 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 < key_length; j++)
|
|
result[j] ^= Ui[j];
|
|
memcpy(Ui_prev, Ui, key_length);
|
|
}
|
|
|
|
pg_hmac_free(hmac_ctx);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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, pg_cryptohash_type hash_type, int key_length,
|
|
uint8 *result, const char **errstr)
|
|
{
|
|
pg_cryptohash_ctx *ctx;
|
|
|
|
ctx = pg_cryptohash_create(hash_type);
|
|
if (ctx == NULL)
|
|
{
|
|
*errstr = pg_cryptohash_error(NULL); /* returns OOM */
|
|
return -1;
|
|
}
|
|
|
|
if (pg_cryptohash_init(ctx) < 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);
|
|
return -1;
|
|
}
|
|
|
|
pg_cryptohash_free(ctx);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Calculate ClientKey. Returns 0 on success, -1 on failure with *errstr
|
|
* pointing to a message about the error details.
|
|
*/
|
|
int
|
|
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(hash_type);
|
|
|
|
if (ctx == NULL)
|
|
{
|
|
*errstr = pg_hmac_error(NULL); /* returns OOM */
|
|
return -1;
|
|
}
|
|
|
|
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, key_length) < 0)
|
|
{
|
|
*errstr = pg_hmac_error(ctx);
|
|
pg_hmac_free(ctx);
|
|
return -1;
|
|
}
|
|
|
|
pg_hmac_free(ctx);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Calculate ServerKey. Returns 0 on success, -1 on failure with *errstr
|
|
* pointing to a message about the error details.
|
|
*/
|
|
int
|
|
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(hash_type);
|
|
|
|
if (ctx == NULL)
|
|
{
|
|
*errstr = pg_hmac_error(NULL); /* returns OOM */
|
|
return -1;
|
|
}
|
|
|
|
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, key_length) < 0)
|
|
{
|
|
*errstr = pg_hmac_error(ctx);
|
|
pg_hmac_free(ctx);
|
|
return -1;
|
|
}
|
|
|
|
pg_hmac_free(ctx);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Construct a SCRAM secret, for storing in pg_authid.rolpassword.
|
|
*
|
|
* The password should already have been processed with SASLprep, if necessary!
|
|
*
|
|
* If iterations is 0, default number of iterations is used. The result is
|
|
* palloc'd or malloc'd, so caller is responsible for freeing it.
|
|
*
|
|
* On error, returns NULL and sets *errstr to point to a message about the
|
|
* error details.
|
|
*/
|
|
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)
|
|
{
|
|
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;
|
|
int encoded_salt_len;
|
|
int encoded_stored_len;
|
|
int encoded_server_len;
|
|
int encoded_result;
|
|
|
|
/* Only this hash method is supported currently */
|
|
Assert(hash_type == PG_SHA256);
|
|
|
|
Assert(iterations > 0);
|
|
|
|
/* Calculate StoredKey and ServerKey */
|
|
if (scram_SaltedPassword(password, hash_type, key_length,
|
|
salt, saltlen, iterations,
|
|
salted_password, 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;
|
|
#else
|
|
elog(ERROR, "could not calculate stored key and server key: %s",
|
|
*errstr);
|
|
#endif
|
|
}
|
|
|
|
/*----------
|
|
* The format is:
|
|
* SCRAM-SHA-256$<iteration count>:<salt>$<StoredKey>:<ServerKey>
|
|
*----------
|
|
*/
|
|
encoded_salt_len = pg_b64_enc_len(saltlen);
|
|
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 */
|
|
+ encoded_salt_len + 1 /* Base64-encoded salt */
|
|
+ encoded_stored_len + 1 /* Base64-encoded StoredKey */
|
|
+ encoded_server_len + 1; /* Base64-encoded ServerKey */
|
|
|
|
#ifdef FRONTEND
|
|
result = malloc(maxlen);
|
|
if (!result)
|
|
{
|
|
*errstr = _("out of memory");
|
|
return NULL;
|
|
}
|
|
#else
|
|
result = palloc(maxlen);
|
|
#endif
|
|
|
|
p = result + sprintf(result, "SCRAM-SHA-256$%d:", iterations);
|
|
|
|
/* salt */
|
|
encoded_result = pg_b64_encode(salt, saltlen, p, encoded_salt_len);
|
|
if (encoded_result < 0)
|
|
{
|
|
*errstr = _("could not encode salt");
|
|
#ifdef FRONTEND
|
|
free(result);
|
|
return NULL;
|
|
#else
|
|
elog(ERROR, "%s", *errstr);
|
|
#endif
|
|
}
|
|
p += encoded_result;
|
|
*(p++) = '$';
|
|
|
|
/* stored key */
|
|
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
|
|
}
|
|
|
|
p += encoded_result;
|
|
*(p++) = ':';
|
|
|
|
/* server key */
|
|
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
|
|
}
|
|
|
|
p += encoded_result;
|
|
*(p++) = '\0';
|
|
|
|
Assert(p - result <= maxlen);
|
|
|
|
return result;
|
|
}
|