postgresql/src/common/hmac.c

331 lines
6.8 KiB
C

/*-------------------------------------------------------------------------
*
* hmac.c
* Implements Keyed-Hashing for Message Authentication (HMAC)
*
* Fallback implementation of HMAC, as specified in RFC 2104.
*
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/common/hmac.c
*
*-------------------------------------------------------------------------
*/
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
#include "common/cryptohash.h"
#include "common/hmac.h"
#include "common/md5.h"
#include "common/sha1.h"
#include "common/sha2.h"
/*
* In backend, use palloc/pfree to ease the error handling. In frontend,
* use malloc to be able to return a failure status back to the caller.
*/
#ifndef FRONTEND
#define ALLOC(size) palloc(size)
#define FREE(ptr) pfree(ptr)
#else
#define ALLOC(size) malloc(size)
#define FREE(ptr) free(ptr)
#endif
/* Set of error states */
typedef enum pg_hmac_errno
{
PG_HMAC_ERROR_NONE = 0,
PG_HMAC_ERROR_OOM,
PG_HMAC_ERROR_INTERNAL,
} pg_hmac_errno;
/* Internal pg_hmac_ctx structure */
struct pg_hmac_ctx
{
pg_cryptohash_ctx *hash;
pg_cryptohash_type type;
pg_hmac_errno error;
const char *errreason;
int block_size;
int digest_size;
/*
* Use the largest block size among supported options. This wastes some
* memory but simplifies the allocation logic.
*/
uint8 k_ipad[PG_SHA512_BLOCK_LENGTH];
uint8 k_opad[PG_SHA512_BLOCK_LENGTH];
};
#define HMAC_IPAD 0x36
#define HMAC_OPAD 0x5C
/*
* pg_hmac_create
*
* Allocate a hash context. Returns NULL on failure for an OOM. The
* backend issues an error, without returning.
*/
pg_hmac_ctx *
pg_hmac_create(pg_cryptohash_type type)
{
pg_hmac_ctx *ctx;
ctx = ALLOC(sizeof(pg_hmac_ctx));
if (ctx == NULL)
return NULL;
memset(ctx, 0, sizeof(pg_hmac_ctx));
ctx->type = type;
ctx->error = PG_HMAC_ERROR_NONE;
ctx->errreason = NULL;
/*
* Initialize the context data. This requires to know the digest and
* block lengths, that depend on the type of hash used.
*/
switch (type)
{
case PG_MD5:
ctx->digest_size = MD5_DIGEST_LENGTH;
ctx->block_size = MD5_BLOCK_SIZE;
break;
case PG_SHA1:
ctx->digest_size = SHA1_DIGEST_LENGTH;
ctx->block_size = SHA1_BLOCK_SIZE;
break;
case PG_SHA224:
ctx->digest_size = PG_SHA224_DIGEST_LENGTH;
ctx->block_size = PG_SHA224_BLOCK_LENGTH;
break;
case PG_SHA256:
ctx->digest_size = PG_SHA256_DIGEST_LENGTH;
ctx->block_size = PG_SHA256_BLOCK_LENGTH;
break;
case PG_SHA384:
ctx->digest_size = PG_SHA384_DIGEST_LENGTH;
ctx->block_size = PG_SHA384_BLOCK_LENGTH;
break;
case PG_SHA512:
ctx->digest_size = PG_SHA512_DIGEST_LENGTH;
ctx->block_size = PG_SHA512_BLOCK_LENGTH;
break;
}
ctx->hash = pg_cryptohash_create(type);
if (ctx->hash == NULL)
{
explicit_bzero(ctx, sizeof(pg_hmac_ctx));
FREE(ctx);
return NULL;
}
return ctx;
}
/*
* pg_hmac_init
*
* Initialize a HMAC context. Returns 0 on success, -1 on failure.
*/
int
pg_hmac_init(pg_hmac_ctx *ctx, const uint8 *key, size_t len)
{
int i;
int digest_size;
int block_size;
uint8 *shrinkbuf = NULL;
if (ctx == NULL)
return -1;
digest_size = ctx->digest_size;
block_size = ctx->block_size;
memset(ctx->k_opad, HMAC_OPAD, ctx->block_size);
memset(ctx->k_ipad, HMAC_IPAD, ctx->block_size);
/*
* If the key is longer than the block size, pass it through the hash once
* to shrink it down.
*/
if (len > block_size)
{
pg_cryptohash_ctx *hash_ctx;
/* temporary buffer for one-time shrink */
shrinkbuf = ALLOC(digest_size);
if (shrinkbuf == NULL)
{
ctx->error = PG_HMAC_ERROR_OOM;
return -1;
}
memset(shrinkbuf, 0, digest_size);
hash_ctx = pg_cryptohash_create(ctx->type);
if (hash_ctx == NULL)
{
ctx->error = PG_HMAC_ERROR_OOM;
FREE(shrinkbuf);
return -1;
}
if (pg_cryptohash_init(hash_ctx) < 0 ||
pg_cryptohash_update(hash_ctx, key, len) < 0 ||
pg_cryptohash_final(hash_ctx, shrinkbuf, digest_size) < 0)
{
ctx->error = PG_HMAC_ERROR_INTERNAL;
ctx->errreason = pg_cryptohash_error(hash_ctx);
pg_cryptohash_free(hash_ctx);
FREE(shrinkbuf);
return -1;
}
key = shrinkbuf;
len = digest_size;
pg_cryptohash_free(hash_ctx);
}
for (i = 0; i < len; i++)
{
ctx->k_ipad[i] ^= key[i];
ctx->k_opad[i] ^= key[i];
}
/* tmp = H(K XOR ipad, text) */
if (pg_cryptohash_init(ctx->hash) < 0 ||
pg_cryptohash_update(ctx->hash, ctx->k_ipad, ctx->block_size) < 0)
{
ctx->error = PG_HMAC_ERROR_INTERNAL;
ctx->errreason = pg_cryptohash_error(ctx->hash);
if (shrinkbuf)
FREE(shrinkbuf);
return -1;
}
if (shrinkbuf)
FREE(shrinkbuf);
return 0;
}
/*
* pg_hmac_update
*
* Update a HMAC context. Returns 0 on success, -1 on failure.
*/
int
pg_hmac_update(pg_hmac_ctx *ctx, const uint8 *data, size_t len)
{
if (ctx == NULL)
return -1;
if (pg_cryptohash_update(ctx->hash, data, len) < 0)
{
ctx->error = PG_HMAC_ERROR_INTERNAL;
ctx->errreason = pg_cryptohash_error(ctx->hash);
return -1;
}
return 0;
}
/*
* pg_hmac_final
*
* Finalize a HMAC context. Returns 0 on success, -1 on failure.
*/
int
pg_hmac_final(pg_hmac_ctx *ctx, uint8 *dest, size_t len)
{
uint8 *h;
if (ctx == NULL)
return -1;
h = ALLOC(ctx->digest_size);
if (h == NULL)
{
ctx->error = PG_HMAC_ERROR_OOM;
return -1;
}
memset(h, 0, ctx->digest_size);
if (pg_cryptohash_final(ctx->hash, h, ctx->digest_size) < 0)
{
ctx->error = PG_HMAC_ERROR_INTERNAL;
ctx->errreason = pg_cryptohash_error(ctx->hash);
FREE(h);
return -1;
}
/* H(K XOR opad, tmp) */
if (pg_cryptohash_init(ctx->hash) < 0 ||
pg_cryptohash_update(ctx->hash, ctx->k_opad, ctx->block_size) < 0 ||
pg_cryptohash_update(ctx->hash, h, ctx->digest_size) < 0 ||
pg_cryptohash_final(ctx->hash, dest, len) < 0)
{
ctx->error = PG_HMAC_ERROR_INTERNAL;
ctx->errreason = pg_cryptohash_error(ctx->hash);
FREE(h);
return -1;
}
FREE(h);
return 0;
}
/*
* pg_hmac_free
*
* Free a HMAC context.
*/
void
pg_hmac_free(pg_hmac_ctx *ctx)
{
if (ctx == NULL)
return;
pg_cryptohash_free(ctx->hash);
explicit_bzero(ctx, sizeof(pg_hmac_ctx));
FREE(ctx);
}
/*
* pg_hmac_error
*
* Returns a static string providing details about an error that happened
* during a HMAC computation.
*/
const char *
pg_hmac_error(pg_hmac_ctx *ctx)
{
if (ctx == NULL)
return _("out of memory");
/*
* If a reason is provided, rely on it, else fallback to any error code
* set.
*/
if (ctx->errreason)
return ctx->errreason;
switch (ctx->error)
{
case PG_HMAC_ERROR_NONE:
return _("success");
case PG_HMAC_ERROR_INTERNAL:
return _("internal error");
case PG_HMAC_ERROR_OOM:
return _("out of memory");
}
Assert(false); /* cannot be reached */
return _("success");
}