diff options
Diffstat (limited to 'board/cr50/u2f_state_load.c')
-rw-r--r-- | board/cr50/u2f_state_load.c | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/board/cr50/u2f_state_load.c b/board/cr50/u2f_state_load.c new file mode 100644 index 0000000000..a1c8927dab --- /dev/null +++ b/board/cr50/u2f_state_load.c @@ -0,0 +1,198 @@ +/* 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 */ |