/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

#include "zbxcrypto.h"

#include "zbxcommon.h"

static void	*hmac_hash_init(zbx_crypto_hash_t type)
{
	void	*ctx;

	switch (type)
	{
		case ZBX_HASH_MD5:
			ctx = zbx_malloc(NULL, sizeof(md5_state_t));
			zbx_md5_init((md5_state_t *)ctx);
			break;
		case ZBX_HASH_SHA256:
			ctx = zbx_malloc(NULL, sizeof(sha256_ctx));
			zbx_sha256_init((sha256_ctx *)ctx);
			break;
		default:
			return NULL;
	}

	return ctx;
}

static void	hmac_hash_append(zbx_crypto_hash_t type, void *ctx, const char *text, size_t text_len)
{
	switch (type)
	{
		case ZBX_HASH_MD5:
			zbx_md5_append((md5_state_t *)(ctx), (const md5_byte_t *)text, text_len);
			break;
		case ZBX_HASH_SHA256:
			zbx_sha256_process_bytes(text, text_len, (sha256_ctx *)ctx);
			break;
		default:
			return;
	}
}

static void	hmac_hash_finish(zbx_crypto_hash_t type, void *ctx, char *out)
{
	switch (type)
	{
		case ZBX_HASH_MD5:
			zbx_md5_finish((md5_state_t *)(ctx), (md5_byte_t *)out);
			break;
		case ZBX_HASH_SHA256:
			zbx_sha256_finish((sha256_ctx *)ctx, out);
			break;
		default:
			return;
	}

	zbx_free(ctx);
}

static void	hmac_hash(zbx_crypto_hash_t type, const char *left, size_t left_len, const char *right,
		size_t right_len, char *out)
{
	void	*ctx;

	ctx = hmac_hash_init(type);
	hmac_hash_append(type, ctx, left, left_len);
	if (0 != right_len)
		hmac_hash_append(type, ctx, right, right_len);
	hmac_hash_finish(type, ctx, out);
}

int	zbx_hmac(zbx_crypto_hash_t hash_type, const char *key, size_t key_len, const char *text, size_t text_len,
		char **out)
{
	size_t	block_size, digest_size, out_len, i;
	char	*key_block, *key_ipad, *key_opad, *ihash, *ohash;
#define MD5_BLOCK_SIZE		64
#define SHA256_BLOCK_SIZE	64
	switch (hash_type)
	{
		case ZBX_HASH_MD5:
			block_size = MD5_BLOCK_SIZE;
			digest_size = ZBX_MD5_DIGEST_SIZE;
			break;
		case ZBX_HASH_SHA256:
			block_size = SHA256_BLOCK_SIZE;
			digest_size = ZBX_SHA256_DIGEST_SIZE;
			break;
		default:
			return FAIL;
	}
#undef MD5_BLOCK_SIZE
#undef SHA256_BLOCK_SIZE
	key_block = (char *)zbx_malloc(NULL, block_size);
	key_ipad = (char *)zbx_malloc(NULL, block_size);
	key_opad = (char *)zbx_malloc(NULL, block_size);

	memset(key_block, 0, block_size);
	memset(key_ipad, 0x36, block_size);
	memset(key_opad, 0x5c, block_size);

	ihash = (char *)zbx_malloc(NULL, digest_size);
	ohash = (char *)zbx_malloc(NULL, digest_size);

	if (key_len > block_size)
		hmac_hash(hash_type, key, key_len, "", 0, key_block);
	else
		memcpy(key_block, key, key_len);

	for (i = 0; i < block_size; i++)
	{
		key_ipad[i] ^= key_block[i];
		key_opad[i] ^= key_block[i];
	}

	hmac_hash(hash_type, key_ipad, block_size, text, text_len, ihash);
	hmac_hash(hash_type, key_opad, block_size, ihash, digest_size, ohash);

	out_len = digest_size * 2 + 1;
	*out = (char *)zbx_malloc(NULL, out_len);

	(void)zbx_bin2hex((const unsigned char *)ohash, digest_size, *out, out_len);

	zbx_free(ohash);
	zbx_free(ihash);
	zbx_free(key_opad);
	zbx_free(key_ipad);
	zbx_free(key_block);

	return SUCCEED;
}
