/*
 * Hashing functions.

 * Copyright IBM Corp. 2022
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */
/* Must be included before any other header */
#include "config.h"

#include "libpv/crypto.h"
#include "libpv/hash.h"

GBytes *pv_sha256_hash(uint8_t *buf, size_t size, GError **error)
{
	g_autoptr(EVP_MD_CTX) ctx = NULL;

	ctx = pv_digest_ctx_new(EVP_sha256(), error);
	if (!ctx)
		return NULL;

	if (pv_digest_ctx_update_raw(ctx, buf, size, error) != 0)
		return NULL;

	return pv_digest_ctx_finalize(ctx, error);
}

EVP_MD_CTX *pv_digest_ctx_new(const EVP_MD *md, GError **error)
{
	g_autoptr(EVP_MD_CTX) ctx = EVP_MD_CTX_new();

	if (!ctx) {
		g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
			    _("Hash context generation failed"));
		return NULL;
	}

	if (EVP_DigestInit_ex(ctx, md, NULL) != 1) {
		g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
			    _("EVP_DigestInit_ex failed"));
		return NULL;
	}

	return g_steal_pointer(&ctx);
}

int pv_digest_ctx_update_raw(EVP_MD_CTX *ctx, const uint8_t *buf, size_t size, GError **error)
{
	if (!buf || size == 0)
		return 0;

	if (EVP_DigestUpdate(ctx, buf, size) != 1) {
		g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
			    _("EVP_DigestUpdate failed"));
		return -1;
	}
	return 0;
}

int pv_digest_ctx_update(EVP_MD_CTX *ctx, GBytes *data, GError **error)
{
	const uint8_t *buf;
	size_t buf_size;

	if (!data)
		return 0;
	buf = g_bytes_get_data((GBytes *)data, &buf_size);
	return pv_digest_ctx_update_raw(ctx, buf, buf_size, error);
}

GBytes *pv_digest_ctx_finalize(EVP_MD_CTX *ctx, GError **error)
{
	int md_size = EVP_MD_size(EVP_MD_CTX_md(ctx));
	g_autofree uint8_t *digest = NULL;
	unsigned int digest_size;

	g_assert(md_size > 0);

	digest = g_malloc0((uint)md_size);
	if (EVP_DigestFinal_ex(ctx, digest, &digest_size) != 1) {
		g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
			    _("EVP_DigestFinal_ex failed"));
		return NULL;
	}

	g_assert(digest_size == (uint)md_size);
	return g_bytes_new_take(g_steal_pointer(&digest), digest_size);
}

HMAC_CTX *pv_hmac_ctx_new(GBytes *key, const EVP_MD *md, GError **error)
{
	g_autoptr(HMAC_CTX) ctx = HMAC_CTX_new();
	const uint8_t *key_data;
	size_t key_size;

	key_data = g_bytes_get_data(key, &key_size);

	if (HMAC_Init_ex(ctx, key_data, (int)key_size, md, NULL) != 1) {
		g_autofree char *openssl_err_msg = pv_get_openssl_errors();

		g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
			    "unable to create HMAC context: %s", openssl_err_msg);
		return NULL;
	}
	return g_steal_pointer(&ctx);
}

int pv_hmac_ctx_update_raw(HMAC_CTX *ctx, const void *buf, size_t size, GError **error)
{
	if (!buf || size == 0)
		return 0;

	if (HMAC_Update(ctx, buf, size) != 1) {
		g_autofree char *openssl_err_msg = pv_get_openssl_errors();

		g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
			    "unable to add data to HMAC context: %s", openssl_err_msg);
		return -1;
	}
	return 0;
}

int pv_hmac_ctx_update(HMAC_CTX *ctx, GBytes *data, GError **error)
{
	const uint8_t *buf;
	size_t buf_size;

	if (!data)
		return 0;
	buf = g_bytes_get_data((GBytes *)data, &buf_size);
	return pv_hmac_ctx_update_raw(ctx, buf, buf_size, error);
}

GBytes *pv_hamc_ctx_finalize(HMAC_CTX *ctx, GError **error)
{
	int md_size = EVP_MD_size(HMAC_CTX_get_md(ctx));
	g_autofree uint8_t *hmac = NULL;
	unsigned int hmac_size = 0;

	g_assert(md_size > 0);

	hmac = g_malloc0((unsigned int)md_size);

	if (HMAC_Final(ctx, hmac, &hmac_size) != 1) {
		g_autofree char *openssl_err_msg = pv_get_openssl_errors();

		g_set_error(error, PV_HASH_ERROR, PV_HASH_ERROR_INTERNAL,
			    "unable to calculate HMAC: %s", openssl_err_msg);
		return NULL;
	}
	return g_bytes_new_take(g_steal_pointer(&hmac), hmac_size);
}
