mirror of
https://github.com/postgres/postgres.git
synced 2025-09-06 13:46:51 +03:00
Similarly to the cryptohash implementations, this refactors the existing HMAC code into a single set of APIs that can be plugged with any crypto libraries PostgreSQL is built with (only OpenSSL currently). If there is no such libraries, a fallback implementation is available. Those new APIs are designed similarly to the existing cryptohash layer, so there is no real new design here, with the same logic around buffer bound checks and memory handling. HMAC has a dependency on cryptohashes, so all the cryptohash types supported by cryptohash{_openssl}.c can be used with HMAC. This refactoring is an advantage mainly for SCRAM, that included its own implementation of HMAC with SHA256 without relying on the existing crypto libraries even if PostgreSQL was built with their support. This code has been tested on Windows and Linux, with and without OpenSSL, across all the versions supported on HEAD from 1.1.1 down to 1.0.1. I have also checked that the implementations are working fine using some sample results, a custom extension of my own, and doing cross-checks across different major versions with SCRAM with the client and the backend. Author: Michael Paquier Reviewed-by: Bruce Momjian Discussion: https://postgr.es/m/X9m0nkEJEzIPXjeZ@paquier.xyz
275 lines
6.3 KiB
C
275 lines
6.3 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-2021, 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.
|
|
*/
|
|
int
|
|
scram_SaltedPassword(const char *password,
|
|
const char *salt, int saltlen, int iterations,
|
|
uint8 *result)
|
|
{
|
|
int password_len = strlen(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);
|
|
|
|
if (hmac_ctx == NULL)
|
|
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, sizeof(Ui_prev)) < 0)
|
|
{
|
|
pg_hmac_free(hmac_ctx);
|
|
return -1;
|
|
}
|
|
|
|
memcpy(result, Ui_prev, SCRAM_KEY_LEN);
|
|
|
|
/* 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_free(hmac_ctx);
|
|
return -1;
|
|
}
|
|
|
|
for (j = 0; j < SCRAM_KEY_LEN; j++)
|
|
result[j] ^= Ui[j];
|
|
memcpy(Ui_prev, Ui, SCRAM_KEY_LEN);
|
|
}
|
|
|
|
pg_hmac_free(hmac_ctx);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Calculate SHA-256 hash for a NULL-terminated string. (The NULL terminator is
|
|
* not included in the hash). Returns 0 on success, -1 on failure.
|
|
*/
|
|
int
|
|
scram_H(const uint8 *input, int len, uint8 *result)
|
|
{
|
|
pg_cryptohash_ctx *ctx;
|
|
|
|
ctx = pg_cryptohash_create(PG_SHA256);
|
|
if (ctx == NULL)
|
|
return -1;
|
|
|
|
if (pg_cryptohash_init(ctx) < 0 ||
|
|
pg_cryptohash_update(ctx, input, len) < 0 ||
|
|
pg_cryptohash_final(ctx, result, SCRAM_KEY_LEN) < 0)
|
|
{
|
|
pg_cryptohash_free(ctx);
|
|
return -1;
|
|
}
|
|
|
|
pg_cryptohash_free(ctx);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Calculate ClientKey. Returns 0 on success, -1 on failure.
|
|
*/
|
|
int
|
|
scram_ClientKey(const uint8 *salted_password, uint8 *result)
|
|
{
|
|
pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
|
|
|
|
if (ctx == NULL)
|
|
return -1;
|
|
|
|
if (pg_hmac_init(ctx, salted_password, SCRAM_KEY_LEN) < 0 ||
|
|
pg_hmac_update(ctx, (uint8 *) "Client Key", strlen("Client Key")) < 0 ||
|
|
pg_hmac_final(ctx, result, SCRAM_KEY_LEN) < 0)
|
|
{
|
|
pg_hmac_free(ctx);
|
|
return -1;
|
|
}
|
|
|
|
pg_hmac_free(ctx);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Calculate ServerKey. Returns 0 on success, -1 on failure.
|
|
*/
|
|
int
|
|
scram_ServerKey(const uint8 *salted_password, uint8 *result)
|
|
{
|
|
pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
|
|
|
|
if (ctx == NULL)
|
|
return -1;
|
|
|
|
if (pg_hmac_init(ctx, salted_password, SCRAM_KEY_LEN) < 0 ||
|
|
pg_hmac_update(ctx, (uint8 *) "Server Key", strlen("Server Key")) < 0 ||
|
|
pg_hmac_final(ctx, result, SCRAM_KEY_LEN) < 0)
|
|
{
|
|
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.
|
|
*/
|
|
char *
|
|
scram_build_secret(const char *salt, int saltlen, int iterations,
|
|
const char *password)
|
|
{
|
|
uint8 salted_password[SCRAM_KEY_LEN];
|
|
uint8 stored_key[SCRAM_KEY_LEN];
|
|
uint8 server_key[SCRAM_KEY_LEN];
|
|
char *result;
|
|
char *p;
|
|
int maxlen;
|
|
int encoded_salt_len;
|
|
int encoded_stored_len;
|
|
int encoded_server_len;
|
|
int encoded_result;
|
|
|
|
if (iterations <= 0)
|
|
iterations = SCRAM_DEFAULT_ITERATIONS;
|
|
|
|
/* Calculate StoredKey and ServerKey */
|
|
if (scram_SaltedPassword(password, salt, saltlen, iterations,
|
|
salted_password) < 0 ||
|
|
scram_ClientKey(salted_password, stored_key) < 0 ||
|
|
scram_H(stored_key, SCRAM_KEY_LEN, stored_key) < 0 ||
|
|
scram_ServerKey(salted_password, server_key) < 0)
|
|
{
|
|
#ifdef FRONTEND
|
|
return NULL;
|
|
#else
|
|
elog(ERROR, "could not calculate stored key and server key");
|
|
#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(SCRAM_KEY_LEN);
|
|
encoded_server_len = pg_b64_enc_len(SCRAM_KEY_LEN);
|
|
|
|
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)
|
|
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)
|
|
{
|
|
#ifdef FRONTEND
|
|
free(result);
|
|
return NULL;
|
|
#else
|
|
elog(ERROR, "could not encode salt");
|
|
#endif
|
|
}
|
|
p += encoded_result;
|
|
*(p++) = '$';
|
|
|
|
/* stored key */
|
|
encoded_result = pg_b64_encode((char *) stored_key, SCRAM_KEY_LEN, p,
|
|
encoded_stored_len);
|
|
if (encoded_result < 0)
|
|
{
|
|
#ifdef FRONTEND
|
|
free(result);
|
|
return NULL;
|
|
#else
|
|
elog(ERROR, "could not encode stored key");
|
|
#endif
|
|
}
|
|
|
|
p += encoded_result;
|
|
*(p++) = ':';
|
|
|
|
/* server key */
|
|
encoded_result = pg_b64_encode((char *) server_key, SCRAM_KEY_LEN, p,
|
|
encoded_server_len);
|
|
if (encoded_result < 0)
|
|
{
|
|
#ifdef FRONTEND
|
|
free(result);
|
|
return NULL;
|
|
#else
|
|
elog(ERROR, "could not encode server key");
|
|
#endif
|
|
}
|
|
|
|
p += encoded_result;
|
|
*(p++) = '\0';
|
|
|
|
Assert(p - result <= maxlen);
|
|
|
|
return result;
|
|
}
|