mirror of
https://github.com/postgres/postgres.git
synced 2025-05-05 09:19:17 +03:00
Setting the iteration count for SCRAM secret generation to INT_MAX will cause an infinite loop in scram_SaltedPassword() due to integer overflow, as the loop uses the "i <= iterations" comparison. To fix, use "i < iterations" instead. Back-patch to v16 where the user-settable GUC scram_iterations has been added. Author: Kevin K Biju <kevinkbiju@gmail.com> Reviewed-by: Richard Guo <guofenglinux@gmail.com> Reviewed-by: Michael Paquier <michael@paquier.xyz> Discussion: https://postgr.es/m/CAM45KeEMm8hnxdTOxA98qhfZ9CzGDdgy3mxgJmy0c+2WwjA6Zg@mail.gmail.com
330 lines
7.8 KiB
C
330 lines
7.8 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-2024, 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"
|
|
#ifndef FRONTEND
|
|
#include "miscadmin.h"
|
|
#endif
|
|
#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 = 1; i < iterations; i++)
|
|
{
|
|
#ifndef FRONTEND
|
|
/*
|
|
* Make sure that this is interruptible as scram_iterations could be
|
|
* set to a large value.
|
|
*/
|
|
CHECK_FOR_INTERRUPTS();
|
|
#endif
|
|
|
|
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!
|
|
*
|
|
* 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;
|
|
}
|