/**
* @file sad_gnutls.c
* @brief Security Association Database gnutls functions
* @note Copyright (C) 2024 IBM Corporation, Clay Kaiser <Clay.Kaiser@ibm.com>
* @note SPDX-License-Identifier: GPL-2.0+
*/
#include <gnutls/crypto.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include "print.h"
#include "sad.h"
#include "sad_private.h"
static int mac_count = -1;
struct mac_data {
gnutls_mac_algorithm_t algorithm;
gnutls_hmac_hd_t handle;
};
static int sad_init_gnutls(void)
{
int err = 0;
if (mac_count >= 0) {
return 0;
}
err = gnutls_global_init();
if (err < 0) {
pr_err("gnutls_global_init() failed: %s",
gnutls_strerror(err));
return -1;
}
mac_count = 0;
return 0;
}
static void sad_deinit_gnutls(void)
{
if (mac_count == 0) {
gnutls_global_deinit();
mac_count = -1;
}
}
struct mac_data *sad_init_mac(integrity_alg_type algorithm,
const unsigned char *key, size_t key_len)
{
int err, length;
gnutls_hmac_hd_t handle;
gnutls_mac_algorithm_t mac_algorithm;
struct mac_data *mac_data;
/* initialize gnutls if not already */
if (mac_count < 0) {
if (sad_init_gnutls() < 0) {
return NULL;
}
}
/* verify key length */
if (key_len == 0) {
pr_err("BUG: key_len is zero");
sad_deinit_gnutls();
return NULL;
}
/* retrieve mac algorithm */
switch (algorithm) {
case HMAC_SHA256_128:
case HMAC_SHA256:
mac_algorithm = GNUTLS_MAC_SHA256;
break;
case CMAC_AES128:
mac_algorithm = GNUTLS_MAC_AES_CMAC_128;
break;
case CMAC_AES256:
mac_algorithm = GNUTLS_MAC_AES_CMAC_256;
break;
default:
pr_err("BUG: unknown algorithm");
sad_deinit_gnutls();
return NULL;
}
/* retrieve mac key length */
length = gnutls_hmac_get_key_size(mac_algorithm);
if (length < 0) {
sad_deinit_gnutls();
return NULL;
}
/* verify key length matches for cmac only */
switch (algorithm) {
case CMAC_AES128:
case CMAC_AES256:
if (key_len != length) {
pr_err("BUG: cipher key_len does not match");
sad_deinit_gnutls();
return NULL;
}
break;
default:
break;
}
/* initialize handle and set key */
err = gnutls_hmac_init(&handle, mac_algorithm, key, key_len);
if (err < 0) {
pr_err("gnutls_hmac_init() failed: %s",
gnutls_strerror(err));
sad_deinit_gnutls();
return NULL;
}
/* initialize mac_data */
mac_data = calloc(1, sizeof(*mac_data));
if (!mac_data) {
sad_deinit_gnutls();
return NULL;
}
mac_data->algorithm = mac_algorithm;
mac_data->handle = handle;
mac_count++;
return mac_data;
}
void sad_deinit_mac(struct mac_data *data)
{
gnutls_hmac_deinit(data->handle, NULL);
free(data);
mac_count--;
sad_deinit_gnutls();
}
static inline int sad_output_mac(struct mac_data *mac_data,
const void *data, size_t data_len,
unsigned char *mac, size_t mac_len)
{
size_t digest_len;
/* confirm mac length is within library support */
digest_len = gnutls_hmac_get_len(mac_data->algorithm);
if (mac_len > digest_len) {
pr_err("BUG: mac_len larger than library support");
return 0;
}
/* confirm mac length is within buffer size */
if (mac_len > MAX_DIGEST_LENGTH) {
pr_err("BUG: mac_len larger than buffer");
return 0;
}
/* update data and retrieve mac */
if (gnutls_hmac(mac_data->handle, data, data_len) < 0) {
gnutls_hmac_output(mac_data->handle, mac);
pr_err("gnutls_hmac() failed");
return 0;
}
gnutls_hmac_output(mac_data->handle, mac);
return 1;
}
int sad_hash(struct mac_data *mac_data,
const void *data, size_t data_len,
unsigned char *mac, size_t mac_len)
{
unsigned char digest_buffer[MAX_DIGEST_LENGTH];
/* update data and output mac */
if (!sad_output_mac(mac_data, data, data_len,
digest_buffer, mac_len)) {
return 0;
}
/* move mac to desired location */
memcpy(mac, digest_buffer, mac_len);
return mac_len;
}
int sad_verify(struct mac_data *mac_data,
const void *data, size_t data_len,
unsigned char *mac, size_t mac_len)
{
unsigned char digest_buffer[MAX_DIGEST_LENGTH];
/* update data and output mac */
if (!sad_output_mac(mac_data, data, data_len,
digest_buffer, mac_len)) {
return -1;
}
/* compare calculated with received */
return gnutls_memcmp(digest_buffer, mac, mac_len);
}