Files
android_kernel_samsung_sm8750/crypto/fips140_3_services.c
2025-08-11 13:49:01 +02:00

361 lines
8.9 KiB
C

// SPDX-License-Identifier: GPL-2.0
//
// In Samsung R&D Institute Ukraine, LLC (SRUKR) under a contract between
// Samsung R&D Institute Ukraine, LLC (Kyiv, Ukraine)
// and "Samsung Electronics Co", Ltd (Seoul, Republic of Korea)
// Copyright: (c) Samsung Electronics Co, Ltd 2024. All rights reserved.
//
#include <linux/kernel.h>
#include <crypto/hash.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/crypto.h>
#include "fips140.h"
#include "fips140_3_services.h"
#include "fips140_3_services_internal.h"
static const char * const approved_algs[] = {
"cbc(aes-generic)",
"ecb(aes-generic)",
"hmac(sha1-generic)",
"hmac(sha224-generic)",
"hmac(sha256-generic)",
"hmac(sha384-generic)",
"hmac(sha512-generic)",
"sha1-generic",
"sha224-generic",
"sha256-generic",
"sha384-generic",
"sha512-generic",
"ecb(aes-ce)",
"cbc(aes-ce)",
"hmac(sha1-ce)",
"hmac(sha224-ce)",
"hmac(sha256-ce)",
"sha1-ce",
"sha224-ce",
"sha256-ce",
};
uint32_t skc_is_approved_service(const char *alg_name)
{
size_t i = 0;
for (i = 0; i < ARRAY_SIZE(approved_algs); ++i) {
if (!strcmp(alg_name, approved_algs[i]))
return 1;
}
return 0;
}
EXPORT_SYMBOL_GPL(skc_is_approved_service);
const char *skc_module_get_version(void)
{
return SKC_VERSION_TEXT;
}
EXPORT_SYMBOL_GPL(skc_module_get_version);
struct tfm_func {
const void *(*get_tfm)(const void *data);
void (*final_tfm_approve_status)(void *data);
void (*clean_tfm_approve_status)(void *data);
int (*get_tfm_approve_status)(const void *data);
};
/* Data structures for general crypto */
struct general_data {
const void *tfm;
char crypto_id[CRYPTO_MAX_ALG_NAME];
int service_final_status;
};
static int get_general_approve_status(const void *data);
static const void *get_general_tfm(const void *data);
static void crypto_general_clean_approve_status(void *data);
static const struct tfm_func general_tfm_f = {
.get_tfm = get_general_tfm,
.clean_tfm_approve_status = crypto_general_clean_approve_status,
.final_tfm_approve_status = NULL,
.get_tfm_approve_status = get_general_approve_status,
};
/* Data structures for HMAC crypto */
#define FIPS_HMAC_MIN_KEY_SIZE_BYTES (14)
struct hmac_data {
const void *tfm;
char crypto_id[CRYPTO_MAX_ALG_NAME];
uint32_t service_hash_by_key_status;
int service_final_status;
};
static const void *get_hmac_tfm(const void *data);
static void crypto_hmac_clean_approve_status(void *data);
static void crypto_hmac_final_approve_status(void *data);
static int get_hmac_approve_status(const void *data);
static const struct tfm_func hmac_f = {
.get_tfm = get_hmac_tfm,
.clean_tfm_approve_status = crypto_hmac_clean_approve_status,
.final_tfm_approve_status = crypto_hmac_final_approve_status,
.get_tfm_approve_status = get_hmac_approve_status,
};
/* Indicator properties storage harness */
struct service_table_node {
void *tfm_data;
const struct tfm_func *tfm_func;
struct list_head list;
};
static LIST_HEAD(service_table);
static DEFINE_SPINLOCK(service_table_lock);
#define LOCK_SERVICE_TABLE(flag) spin_lock_irqsave(&service_table_lock, flag)
#define UNLOCK_SERVICE_TABLE(flag) spin_unlock_irqrestore(&service_table_lock, flag)
static struct service_table_node *service_table_add_node(struct service_table_node *node)
{
list_add_tail(&node->list, &service_table);
return node;
}
static void service_table_remove_node(struct service_table_node *node)
{
list_del(&(node->list));
kfree_sensitive(node);
}
static struct service_table_node *service_table_get_item(const void *tfm)
{
struct service_table_node *res = NULL;
struct service_table_node *temp_node = NULL;
list_for_each_entry(temp_node, &service_table, list) {
if (temp_node->tfm_func->get_tfm &&
tfm == temp_node->tfm_func->get_tfm(temp_node->tfm_data)) {
res = temp_node;
goto exit;
}
}
exit:
return res;
}
/* Indicator API implementation for associated crypto */
static int prepare_tfm_support_str(const char *crypto_id, const void *tfm,
const void **func, void **data, gfp_t flags)
{
int res = 0;
/* Currently, only HMAC needs check for runtime params, so make it simple. */
if (strstr(crypto_id, "hmac") == crypto_id) {
*data = kzalloc(sizeof(struct hmac_data), flags);
if (!*data) {
res = -ENOMEM;
goto err;
}
((struct hmac_data *)(*data))->tfm = tfm;
((struct hmac_data *)(*data))->service_hash_by_key_status = STATUS_UNDEFINED;
((struct hmac_data *)(*data))->service_final_status = STATUS_UNDEFINED;
strscpy(((struct hmac_data *)(*data))->crypto_id, crypto_id,
CRYPTO_MAX_ALG_NAME);
*func = &hmac_f;
} else {
*data = kzalloc(sizeof(struct general_data), flags);
if (!*data) {
res = -ENOMEM;
goto err;
}
((struct general_data *)(*data))->tfm = tfm;
((struct general_data *)(*data))->service_final_status = STATUS_UNDEFINED;
strscpy(((struct general_data *)(*data))->crypto_id, crypto_id,
CRYPTO_MAX_ALG_NAME);
*func = &general_tfm_f;
}
err:
return res;
}
int init_service_indicator(const void *tfm, const char *crypto_id, gfp_t flags)
{
struct service_table_node *node = NULL;
void *tfm_serv_data = NULL;
const void *tfm_serv_func = NULL;
int res = -EINVAL;
unsigned long lock_flags;
if (!tfm
|| !crypto_id
|| (strnlen(crypto_id, CRYPTO_MAX_ALG_NAME) > (CRYPTO_MAX_ALG_NAME - 1)))
return -EINVAL;
/* If the property item exists in the list already. */
LOCK_SERVICE_TABLE(lock_flags);
node = service_table_get_item(tfm);
if (node) {
node->tfm_func->clean_tfm_approve_status(node->tfm_data);
UNLOCK_SERVICE_TABLE(lock_flags);
return 0;
}
UNLOCK_SERVICE_TABLE(lock_flags);
/* If the new property has to be added into the list. */
res = prepare_tfm_support_str(crypto_id, tfm, &tfm_serv_func, &tfm_serv_data, flags);
if (res)
return res;
node = kzalloc(sizeof(struct service_table_node), flags);
if (!node) {
if (tfm_serv_data)
kfree_sensitive(tfm_serv_data);
return -ENOMEM;
}
LOCK_SERVICE_TABLE(lock_flags);
node = service_table_add_node(node);
node->tfm_data = tfm_serv_data;
node->tfm_func = tfm_serv_func;
UNLOCK_SERVICE_TABLE(lock_flags);
return 0;
}
EXPORT_SYMBOL_GPL(init_service_indicator);
int get_service_status(const void *tfm)
{
int res = -EINVAL;
const struct service_table_node *node = NULL;
unsigned long lock_flags;
LOCK_SERVICE_TABLE(lock_flags);
node = service_table_get_item(tfm);
if (!node)
goto exit;
if (node->tfm_func->get_tfm_approve_status)
res = node->tfm_func->get_tfm_approve_status(node->tfm_data);
if (res == STATUS_UNDEFINED)
res = -EINVAL;
exit:
UNLOCK_SERVICE_TABLE(lock_flags);
return res;
}
EXPORT_SYMBOL_GPL(get_service_status);
void calc_final_state_service_indicator(const void *tfm)
{
const struct service_table_node *node = NULL;
unsigned long lock_flags;
LOCK_SERVICE_TABLE(lock_flags);
node = service_table_get_item(tfm);
if (!node)
goto exit;
if (node->tfm_func->final_tfm_approve_status)
node->tfm_func->final_tfm_approve_status(node->tfm_data);
exit:
UNLOCK_SERVICE_TABLE(lock_flags);
}
EXPORT_SYMBOL_GPL(calc_final_state_service_indicator);
void finit_service_indicator(const void *tfm)
{
struct service_table_node *node = NULL;
unsigned long lock_flags;
LOCK_SERVICE_TABLE(lock_flags);
node = service_table_get_item(tfm);
if (!node)
goto exit;
if (node->tfm_data)
kfree_sensitive(node->tfm_data);
service_table_remove_node(node);
exit:
UNLOCK_SERVICE_TABLE(lock_flags);
}
EXPORT_SYMBOL_GPL(finit_service_indicator);
/* HMAC associated functionality */
void crypto_hmac_set_key_approve_status(const void *tfm, unsigned int key_size)
{
struct service_table_node *node = NULL;
unsigned long lock_flags;
LOCK_SERVICE_TABLE(lock_flags);
node = service_table_get_item(tfm);
if (!node)
goto exit;
((struct hmac_data *)node->tfm_data)->service_hash_by_key_status = STATUS_NON_APPROVED;
if (key_size >= FIPS_HMAC_MIN_KEY_SIZE_BYTES)
((struct hmac_data *)node->tfm_data)->service_hash_by_key_status = STATUS_APPROVED;
exit:
UNLOCK_SERVICE_TABLE(lock_flags);
}
static void crypto_hmac_clean_approve_status(void *data)
{
((struct hmac_data *)data)->service_final_status = STATUS_UNDEFINED;
}
static void crypto_hmac_final_approve_status(void *data)
{
struct hmac_data *tfm_data = (struct hmac_data *)data;
tfm_data->service_final_status = STATUS_NON_APPROVED;
if ((tfm_data->service_hash_by_key_status == STATUS_APPROVED)
&& skc_is_approved_service(tfm_data->crypto_id)) {
tfm_data->service_final_status = STATUS_APPROVED;
}
}
static int get_hmac_approve_status(const void *data)
{
return ((struct hmac_data *)data)->service_final_status;
};
static const void *get_hmac_tfm(const void *data)
{
return ((struct hmac_data *)data)->tfm;
};
/* General crypto Functionality */
static void crypto_general_clean_approve_status(void *data)
{
((struct general_data *)data)->service_final_status = STATUS_UNDEFINED;
}
static int get_general_approve_status(const void *data)
{
return skc_is_approved_service(((struct general_data *)data)->crypto_id);
};
static const void *get_general_tfm(const void *data)
{
return ((struct general_data *)data)->tfm;
};