/* Copyright 2021 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "console.h"
#include "new_nvmem.h"
#include "nvmem.h"
#include "nvmem_vars.h"
#include "tpm_nvmem_ops.h"
#include "tpm_vendor_cmds.h"
#include "u2f_impl.h"
#include "util.h"

/* For test/u2f.c we provide a mock-up implementation of u2f_get_state(). */
#ifndef U2F_TEST
static const uint8_t k_salt = NVMEM_VAR_G2F_SALT;
static const uint8_t k_salt_deprecated = NVMEM_VAR_U2F_SALT;

#define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ##args)

bool u2f_load_or_create_state(struct u2f_state *state, bool force_create)
{
	bool g2f_secret_was_created = false;

	const struct tuple *t_salt = NULL;

	t_salt = getvar(&k_salt, sizeof(k_salt));

	if (force_create && t_salt) {
		/* Remove k_salt variable first. */
		freevar(t_salt);
		setvar(&k_salt, sizeof(k_salt), NULL, 0);
		t_salt = NULL;
	}

	/* Load or create G2F secret. */
	if (!t_salt) {
		g2f_secret_was_created = true;
		if (u2f_generate_g2f_secret(state) != EC_SUCCESS)
			return false;

		/* Delete the old salt if present, no-op if not. */
		if (setvar(&k_salt_deprecated, sizeof(k_salt_deprecated), NULL,
			   0) != EC_SUCCESS)
			return false;
		if (setvar(&k_salt, sizeof(k_salt),
			   (const uint8_t *)state->salt,
			   sizeof(state->salt)) != EC_SUCCESS)
			return false;
	} else {
		memcpy(state->salt, tuple_val(t_salt), sizeof(state->salt));
		freevar(t_salt);
	}

	/* Load or create HMAC key. Force creation if G2F wasn't loaded. */
	if (g2f_secret_was_created ||
	    read_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KEK, sizeof(state->hmac_key),
				  state->hmac_key) != TPM_READ_SUCCESS) {
		if (u2f_generate_hmac_key(state) != EC_SUCCESS)
			return false;

		if (write_tpm_nvmem_hidden(
			    TPM_HIDDEN_U2F_KEK, sizeof(state->hmac_key),
			    state->hmac_key, 1 /* commit */) == TPM_WRITE_FAIL)
			return false;
	}

	/* Load or create DRBG entropy. Force creation if G2F wasn't loaded. */
	state->drbg_entropy_size = read_tpm_nvmem_size(TPM_HIDDEN_U2F_KH_SALT);

	if (g2f_secret_was_created ||
	    ((state->drbg_entropy_size != sizeof(state->drbg_entropy)) &&
	     (state->drbg_entropy_size != 32)) ||
	    (read_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KH_SALT,
				   state->drbg_entropy_size,
				   state->drbg_entropy) != TPM_READ_SUCCESS)) {

		if (u2f_generate_drbg_entropy(state) != EC_SUCCESS)
			return false;

		/**
		 * We are in the inconsistent state with only G2F valid.
		 * This could be a result of very old platform being updated.
		 * In such case continue to use old, non FIPS path which is
		 * indicated by 'old' DRBG entropy size.
		 *
		 * Note, that if keys weren't properly created all at once it
		 * will continue in non-FIPS mode until keys are deleted and
		 * properly created again.
		 */
		if (!g2f_secret_was_created)
			state->drbg_entropy_size = 32;

		if (write_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KH_SALT,
					   state->drbg_entropy_size,
					   state->drbg_entropy,
					   1 /* commit */) == TPM_WRITE_FAIL) {
			state->drbg_entropy_size = 0;
			return false;
		}
	}

	/**
	 * If we loaded G2F secrets, but failed to load U2F secrets, it means
	 * we should continue in non FIPS mode until all keys will be recreated
	 * properly.
	 *
	 * On first run after update:
	 *  1. Load G2F key
	 *  2. Failed or succeeded to load HMAC. Failing at this point means
	 *     DRBG load will also fail.
	 *  3. Failed to load DRBG, created DRBG with size = 32 as
	 *     g2f_secret_was_created == false
	 *
	 * On subsequent runs it will load DRBG size == 32 until keys would be
	 * removed and recreated.
	 */

	return true;
}

/**
 * Get the current u2f state from the board.
 */
static bool u2f_state_loaded;
static struct u2f_state u2f_state;

struct u2f_state *u2f_get_state(void)
{
	if (!u2f_state_loaded)
		u2f_state_loaded = u2f_load_or_create_state(&u2f_state, false);

	return u2f_state_loaded ? &u2f_state : NULL;
}

enum ec_error_list u2f_gen_kek_seed(int commit)
{
	struct u2f_state *state = u2f_get_state();

	if (!state)
		return EC_ERROR_UNKNOWN;

	if (!u2f_generate_hmac_key(state))
		return EC_ERROR_HW_INTERNAL;

	if (write_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KEK, sizeof(state->hmac_key),
				   state->hmac_key, commit) == TPM_WRITE_FAIL)
		return EC_ERROR_UNKNOWN;

	return EC_SUCCESS;
}

/* Can't include TPM2 headers, so just define constant locally. */
#define HR_NV_INDEX (1U << 24)

enum ec_error_list u2f_zeroize_keys(void)
{
	const uint32_t u2fobjs[] = { TPM_HIDDEN_U2F_KEK | HR_NV_INDEX,
				     TPM_HIDDEN_U2F_KH_SALT | HR_NV_INDEX, 0 };

	enum ec_error_list result1, result2;

	/* Delete NVMEM_VAR_G2F_SALT. */
	result1 = setvar(&k_salt, sizeof(k_salt), NULL, 0);

	/* Remove U2F keys and wipe all deleted objects. */
	result2 = nvmem_erase_tpm_data_selective(u2fobjs);

	always_memset(&u2f_state, 0, sizeof(u2f_state));
	u2f_state_loaded = false;
	if ((result1 == EC_SUCCESS) && (result2 != EC_SUCCESS))
		result1 = result2;

	return result1;
}

enum ec_error_list u2f_update_keys(void)
{
	struct u2f_state *state = u2f_get_state();
	enum ec_error_list result = EC_SUCCESS;

	/* if we couldn't load state or state is not representing new keys */
	if (!state || state->drbg_entropy_size != sizeof(state->drbg_entropy)) {
		result = u2f_zeroize_keys();
		/* Force creation of new keys. */
		u2f_state_loaded = u2f_load_or_create_state(&u2f_state, true);

		/* try to load again */
		state = u2f_get_state();
	}
	if (!state || state->drbg_entropy_size != sizeof(state->drbg_entropy))
		result = EC_ERROR_HW_INTERNAL;

	return result;
}

#endif /* U2F_TEST */
