// SPDX-License-Identifier: GPL-2.0
/*
 * Functions corresponding to secure platform management object type
 * attributes under BIOS PASSWORD for use with hp-bioscfg driver
 *
 * Copyright (c) 2022 HP Development Company, L.P.
 */

#include "bioscfg.h"

static const char * const spm_state_types[] = {
	"not provisioned",
	"provisioned",
	"provisioning in progress",
};

static const char * const spm_mechanism_types[] = {
	"not provisioned",
	"signing-key",
	"endorsement-key",
};

struct secureplatform_provisioning_data {
	u8 state;
	u8 version[2];
	u8 reserved1;
	u32 features;
	u32 nonce;
	u8 reserved2[28];
	u8 sk_mod[MAX_KEY_MOD_SIZE];
	u8 kek_mod[MAX_KEY_MOD_SIZE];
};

/**
 * hp_calculate_security_buffer() - determines size of security buffer
 * for authentication scheme
 *
 * @authentication: the authentication content
 *
 * Currently only supported type is Admin password
 */
size_t hp_calculate_security_buffer(const char *authentication)
{
	size_t size, authlen;

	if (!authentication)
		return sizeof(u16) * 2;

	authlen = strlen(authentication);
	if (!authlen)
		return sizeof(u16) * 2;

	size = sizeof(u16) + authlen * sizeof(u16);
	if (!strstarts(authentication, BEAM_PREFIX))
		size += strlen(UTF_PREFIX) * sizeof(u16);

	return size;
}

/**
 * hp_populate_security_buffer() - builds a security buffer for
 * authentication scheme
 *
 * @authbuf: the security buffer
 * @authentication: the authentication content
 *
 * Currently only supported type is PLAIN TEXT
 */
int hp_populate_security_buffer(u16 *authbuf, const char *authentication)
{
	u16 *auth = authbuf;
	char *strprefix = NULL;
	int ret = 0;

	if (strstarts(authentication, BEAM_PREFIX)) {
		/*
		 * BEAM_PREFIX is append to authbuf when a signature
		 * is provided and Sure Admin is enabled in BIOS
		 */
		/* BEAM_PREFIX found, convert part to unicode */
		auth = hp_ascii_to_utf16_unicode(auth, authentication);
		if (!auth)
			return -EINVAL;

	} else {
		/*
		 * UTF-16 prefix is append to the * authbuf when a BIOS
		 * admin password is configured in BIOS
		 */

		/* append UTF_PREFIX to part and then convert it to unicode */
		strprefix = kasprintf(GFP_KERNEL, "%s%s", UTF_PREFIX,
				      authentication);
		if (!strprefix)
			return -ENOMEM;

		auth = hp_ascii_to_utf16_unicode(auth, strprefix);
		kfree(strprefix);

		if (!auth) {
			ret = -EINVAL;
			goto out_buffer;
		}
	}

out_buffer:
	return ret;
}

static ssize_t update_spm_state(void)
{
	struct secureplatform_provisioning_data data;
	int ret;

	ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
				   HPWMI_SECUREPLATFORM, &data, 0,
				   sizeof(data));
	if (ret < 0)
		return ret;

	bioscfg_drv.spm_data.mechanism = data.state;
	if (bioscfg_drv.spm_data.mechanism)
		bioscfg_drv.spm_data.is_enabled = 1;

	return 0;
}

static ssize_t statusbin(struct kobject *kobj,
			 struct kobj_attribute *attr,
			 struct secureplatform_provisioning_data *buf)
{
	int ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
				       HPWMI_SECUREPLATFORM, buf, 0,
				       sizeof(*buf));

	if (ret < 0)
		return ret;

	return sizeof(struct secureplatform_provisioning_data);
}

/*
 * status_show - Reads SPM status
 */
static ssize_t status_show(struct kobject *kobj, struct kobj_attribute
			   *attr, char *buf)
{
	int ret, i;
	int len = 0;
	struct secureplatform_provisioning_data data;

	ret = statusbin(kobj, attr, &data);
	if (ret < 0)
		return ret;

	/*
	 * 'status' is a read-only file that returns ASCII text in
	 * JSON format reporting the status information.
	 *
	 * "State": "not provisioned | provisioned | provisioning in progress ",
	 * "Version": " Major. Minor ",
	 * "Nonce": <16-bit unsigned number display in base 10>,
	 * "FeaturesInUse": <16-bit unsigned number display in base 10>,
	 * "EndorsementKeyMod": "<256 bytes in base64>",
	 * "SigningKeyMod": "<256 bytes in base64>"
	 */

	len += sysfs_emit_at(buf, len, "{\n");
	len += sysfs_emit_at(buf, len, "\t\"State\": \"%s\",\n",
			     spm_state_types[data.state]);
	len += sysfs_emit_at(buf, len, "\t\"Version\": \"%d.%d\"",
			     data.version[0], data.version[1]);

	/*
	 * state == 0 means secure platform management
	 * feature is not configured in BIOS.
	 */
	if (data.state == 0) {
		len += sysfs_emit_at(buf, len, "\n");
		goto status_exit;
	} else {
		len += sysfs_emit_at(buf, len, ",\n");
	}

	len += sysfs_emit_at(buf, len, "\t\"Nonce\": %d,\n", data.nonce);
	len += sysfs_emit_at(buf, len, "\t\"FeaturesInUse\": %d,\n", data.features);
	len += sysfs_emit_at(buf, len, "\t\"EndorsementKeyMod\": \"");

	for (i = 255; i >= 0; i--)
		len += sysfs_emit_at(buf, len, " %u", data.kek_mod[i]);

	len += sysfs_emit_at(buf, len, " \",\n");
	len += sysfs_emit_at(buf, len, "\t\"SigningKeyMod\": \"");

	for (i = 255; i >= 0; i--)
		len += sysfs_emit_at(buf, len, " %u", data.sk_mod[i]);

	/* Return buf contents */
	len += sysfs_emit_at(buf, len, " \"\n");

status_exit:
	len += sysfs_emit_at(buf, len, "}\n");

	return len;
}

static struct kobj_attribute password_spm_status = __ATTR_RO(status);

ATTRIBUTE_SPM_N_PROPERTY_SHOW(is_enabled, spm);
static struct kobj_attribute password_spm_is_key_enabled = __ATTR_RO(is_enabled);

static ssize_t key_mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
				  char *buf)
{
	return sysfs_emit(buf, "%s\n",
			  spm_mechanism_types[bioscfg_drv.spm_data.mechanism]);
}

static struct kobj_attribute password_spm_key_mechanism = __ATTR_RO(key_mechanism);

static ssize_t sk_store(struct kobject *kobj,
			struct kobj_attribute *attr,
			const char *buf, size_t count)
{
	int ret;
	int length;

	length = count;
	if (buf[length - 1] == '\n')
		length--;

	/* allocate space and copy current signing key */
	bioscfg_drv.spm_data.signing_key = kmemdup(buf, length, GFP_KERNEL);
	if (!bioscfg_drv.spm_data.signing_key)
		return -ENOMEM;

	/* submit signing key payload */
	ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_SK,
				   HPWMI_SECUREPLATFORM,
				   (void *)bioscfg_drv.spm_data.signing_key,
				   count, 0);

	if (!ret) {
		bioscfg_drv.spm_data.mechanism = SIGNING_KEY;
		hp_set_reboot_and_signal_event();
	}

	kfree(bioscfg_drv.spm_data.signing_key);
	bioscfg_drv.spm_data.signing_key = NULL;

	return ret ? ret : count;
}

static struct kobj_attribute password_spm_signing_key = __ATTR_WO(sk);

static ssize_t kek_store(struct kobject *kobj,
			 struct kobj_attribute *attr,
			 const char *buf, size_t count)
{
	int ret;
	int length;

	length = count;
	if (buf[length - 1] == '\n')
		length--;

	/* allocate space and copy current signing key */
	bioscfg_drv.spm_data.endorsement_key = kmemdup(buf, length, GFP_KERNEL);
	if (!bioscfg_drv.spm_data.endorsement_key) {
		ret = -ENOMEM;
		goto exit_kek;
	}

	ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_KEK,
				   HPWMI_SECUREPLATFORM,
				   (void *)bioscfg_drv.spm_data.endorsement_key,
				   count, 0);

	if (!ret) {
		bioscfg_drv.spm_data.mechanism = ENDORSEMENT_KEY;
		hp_set_reboot_and_signal_event();
	}

exit_kek:
	kfree(bioscfg_drv.spm_data.endorsement_key);
	bioscfg_drv.spm_data.endorsement_key = NULL;

	return ret ? ret : count;
}

static struct kobj_attribute password_spm_endorsement_key = __ATTR_WO(kek);

static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
			 char *buf)
{
	return sysfs_emit(buf, "%s\n", BIOS_SPM);
}

static struct kobj_attribute password_spm_role = __ATTR_RO(role);

static ssize_t auth_token_store(struct kobject *kobj,
				struct kobj_attribute *attr,
				const char *buf, size_t count)
{
	int ret = 0;
	int length;

	length = count;
	if (buf[length - 1] == '\n')
		length--;

	/* allocate space and copy current auth token */
	bioscfg_drv.spm_data.auth_token = kmemdup(buf, length, GFP_KERNEL);
	if (!bioscfg_drv.spm_data.auth_token) {
		ret = -ENOMEM;
		goto exit_token;
	}

	return count;

exit_token:
	kfree(bioscfg_drv.spm_data.auth_token);
	bioscfg_drv.spm_data.auth_token = NULL;

	return ret;
}

static struct kobj_attribute password_spm_auth_token = __ATTR_WO(auth_token);

static struct attribute *secure_platform_attrs[] = {
	&password_spm_is_key_enabled.attr,
	&password_spm_signing_key.attr,
	&password_spm_endorsement_key.attr,
	&password_spm_key_mechanism.attr,
	&password_spm_status.attr,
	&password_spm_role.attr,
	&password_spm_auth_token.attr,
	NULL,
};

static const struct attribute_group secure_platform_attr_group = {
	.attrs = secure_platform_attrs,
};

void hp_exit_secure_platform_attributes(void)
{
	/* remove secure platform sysfs entry and free key data*/

	kfree(bioscfg_drv.spm_data.endorsement_key);
	bioscfg_drv.spm_data.endorsement_key = NULL;

	kfree(bioscfg_drv.spm_data.signing_key);
	bioscfg_drv.spm_data.signing_key = NULL;

	kfree(bioscfg_drv.spm_data.auth_token);
	bioscfg_drv.spm_data.auth_token = NULL;

	if (bioscfg_drv.spm_data.attr_name_kobj)
		sysfs_remove_group(bioscfg_drv.spm_data.attr_name_kobj,
				   &secure_platform_attr_group);
}

int hp_populate_secure_platform_data(struct kobject *attr_name_kobj)
{
	/* Populate data for Secure Platform Management */
	bioscfg_drv.spm_data.attr_name_kobj = attr_name_kobj;

	strscpy(bioscfg_drv.spm_data.attribute_name, SPM_STR,
		sizeof(bioscfg_drv.spm_data.attribute_name));

	bioscfg_drv.spm_data.is_enabled = 0;
	bioscfg_drv.spm_data.mechanism = 0;
	bioscfg_drv.pending_reboot = false;
	update_spm_state();

	bioscfg_drv.spm_data.endorsement_key = NULL;
	bioscfg_drv.spm_data.signing_key = NULL;
	bioscfg_drv.spm_data.auth_token = NULL;

	return sysfs_create_group(attr_name_kobj, &secure_platform_attr_group);
}