diff options
Diffstat (limited to 'board')
-rw-r--r-- | board/cr50/build.mk | 1 | ||||
-rw-r--r-- | board/cr50/fips_cmd.c | 90 | ||||
-rw-r--r-- | board/cr50/power_button.c | 38 | ||||
-rw-r--r-- | board/cr50/tpm2/platform.c | 2 | ||||
-rw-r--r-- | board/cr50/tpm2/virtual_nvmem.c | 2 | ||||
-rw-r--r-- | board/cr50/u2f.c | 708 | ||||
-rw-r--r-- | board/cr50/u2f_state_load.c | 198 |
7 files changed, 834 insertions, 205 deletions
diff --git a/board/cr50/build.mk b/board/cr50/build.mk index e3b2555a8d..42a095e54c 100644 --- a/board/cr50/build.mk +++ b/board/cr50/build.mk @@ -103,6 +103,7 @@ custom-board-ro_objs-${CONFIG_FIPS_UTIL} = $(BDIR)/dcrypto/util.o # FIPS console and TPM2 commands are outside FIPS module board-y += fips_cmd.o +board-y += u2f_state_load.o board-y += tpm2/NVMem.o board-y += tpm2/aes.o diff --git a/board/cr50/fips_cmd.c b/board/cr50/fips_cmd.c index 6642bd3396..554b048c25 100644 --- a/board/cr50/fips_cmd.c +++ b/board/cr50/fips_cmd.c @@ -21,7 +21,7 @@ #include "system.h" #include "task.h" #include "tpm_nvmem_ops.h" -#include "u2f_impl.h" +#include "u2f_cmds.h" /** * Create IRQ handler calling FIPS module's dcrypto_done_interrupt() on @@ -68,47 +68,46 @@ static void fips_print_status(void) } DECLARE_HOOK(HOOK_INIT, fips_print_status, HOOK_PRIO_INIT_PRINT_FIPS_STATUS); -#ifdef CRYPTO_TEST_SETUP +#if defined(CRYPTO_TEST_SETUP) || defined(CR50_DEV) static const uint8_t k_salt = NVMEM_VAR_G2F_SALT; -/* Can't include TPM2 headers, so just define constant locally. */ -#define HR_NV_INDEX (1U << 24) +static void print_u2f_keys_status(void) +{ + struct u2f_state state; + bool load_result; + size_t hmac_len, drbg_len; + + hmac_len = read_tpm_nvmem_size(TPM_HIDDEN_U2F_KEK); + drbg_len = read_tpm_nvmem_size(TPM_HIDDEN_U2F_KH_SALT); + load_result = u2f_load_or_create_state(&state, false); -/* Wipe old U2F keys. */ -static void u2f_zeroize_non_fips(void) + CPRINTS("U2F HMAC len: %u, U2F Entropy len: %u, U2F load:%u, " + "State DRBG len:%u", hmac_len, + drbg_len, load_result, state.drbg_entropy_size); +} + +static void u2f_keys(void) { - const uint32_t u2fobjs[] = { TPM_HIDDEN_U2F_KEK | HR_NV_INDEX, - TPM_HIDDEN_U2F_KH_SALT | HR_NV_INDEX, 0 }; - /* Delete NVMEM_VAR_G2F_SALT. */ - setvar(&k_salt, sizeof(k_salt), NULL, 0); - /* Remove U2F keys and wipe all deleted objects. */ - nvmem_erase_tpm_data_selective(u2fobjs); + CPRINTS("U2F state %x", (uintptr_t)u2f_get_state()); + print_u2f_keys_status(); } -/* Set U2F keys to old or new version. */ -static void fips_set_u2f_keys(bool active) +/* Set U2F keys as old. */ +static void fips_set_old_u2f_keys(void) { - if (!active) { - /* Old version. */ - uint8_t random[32]; - /* Create fake u2f keys old style */ - fips_trng_bytes(random, sizeof(random)); - setvar(&k_salt, sizeof(k_salt), random, sizeof(random)); - - fips_trng_bytes(random, sizeof(random)); - write_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KEK, sizeof(random), - random, 1); - fips_trng_bytes(random, sizeof(random)); - write_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KH_SALT, sizeof(random), - random, 1); - } else { - /** - * TODO(sukhomlinov): Implement new key generation after merging - * https://crrev.com/c/3034852 and adding FIPS key gen. - */ - u2f_zeroize_non_fips(); - } - system_reset(EC_RESET_FLAG_SECURITY); + uint8_t random[32]; + + u2f_zeroize_keys(); + + /* Create fake u2f keys old style */ + fips_trng_bytes(random, sizeof(random)); + setvar(&k_salt, sizeof(k_salt), random, sizeof(random)); + + fips_trng_bytes(random, sizeof(random)); + write_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KEK, sizeof(random), random, 1); + fips_trng_bytes(random, sizeof(random)); + write_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KH_SALT, sizeof(random), random, + 1); } #endif @@ -127,11 +126,20 @@ static int cmd_fips_status(int argc, char **argv) fips_print_test_time(); fips_print_mode(); } -#ifdef CRYPTO_TEST_SETUP +#if defined(CRYPTO_TEST_SETUP) || defined(CR50_DEV) else if (!strncmp(argv[1], "new", 3)) - fips_set_u2f_keys(true); /* we can reboot here... */ + CPRINTS("u2f update status: %d", u2f_update_keys()); + else if (!strncmp(argv[1], "del", 3)) + CPRINTS("u2f zeroization status: %d", + u2f_zeroize_keys()); else if (!strncmp(argv[1], "old", 3)) - fips_set_u2f_keys(false); /* we can reboot here... */ + fips_set_old_u2f_keys(); + else if (!strncmp(argv[1], "u2f", 3)) + print_u2f_keys_status(); + else if (!strncmp(argv[1], "gen", 3)) + u2f_keys(); +#endif +#ifdef CRYPTO_TEST_SETUP else if (!strncmp(argv[1], "trng", 4)) fips_break_cmd = FIPS_BREAK_TRNG; else if (!strncmp(argv[1], "sha", 3)) @@ -144,7 +152,7 @@ static int cmd_fips_status(int argc, char **argv) DECLARE_SAFE_CONSOLE_COMMAND( fips, cmd_fips_status, #ifdef CRYPTO_TEST_SETUP - "[test | new | old | trng | sha]", + "[test | new | old | u2f | gen | trng | sha]", "Report FIPS status, switch U2F key, run tests, simulate errors"); #else "[test]", "Report FIPS status, run tests"); @@ -181,10 +189,10 @@ static enum vendor_cmd_rc fips_cmd(enum vendor_cmd_cc code, void *buf, memcpy(buf, &fips_reverse, sizeof(fips_reverse)); *response_size = sizeof(fips_reverse); break; -#ifdef CRYPTO_TEST_SETUP case FIPS_CMD_ON: - fips_set_u2f_keys(true); /* we can reboot here... */ + u2f_update_keys(); break; +#ifdef CRYPTO_TEST_SETUP case FIPS_CMD_BREAK_TRNG: fips_break_cmd = FIPS_BREAK_TRNG; break; diff --git a/board/cr50/power_button.c b/board/cr50/power_button.c index 2d22966273..a13f450031 100644 --- a/board/cr50/power_button.c +++ b/board/cr50/power_button.c @@ -15,7 +15,6 @@ #include "system_chip.h" #include "task.h" #include "timer.h" -#include "u2f_impl.h" #define CPRINTS(format, args...) cprints(CC_RBOX, format, ## args) #define CPRINTF(format, args...) cprintf(CC_RBOX, format, ## args) @@ -308,6 +307,43 @@ static void power_button_init(void) DECLARE_HOOK(HOOK_INIT, power_button_init, HOOK_PRIO_DEFAULT); #endif /* CONFIG_U2F */ +/* ---- physical presence (using the laptop power button) ---- */ + +static timestamp_t last_press; + +/* how long do we keep the last button press as valid presence */ +#define PRESENCE_TIMEOUT (10 * SECOND) + +void power_button_record(void) +{ + if (ap_is_on() && rbox_powerbtn_is_pressed()) { + last_press = get_time(); +#ifdef CR50_DEV + CPRINTS("record pp"); +#endif + } +} + +enum touch_state pop_check_presence(int consume) +{ +#ifdef CRYPTO_TEST_SETUP + return POP_TOUCH_YES; +#else + int recent = ((last_press.val > 0) && + ((get_time().val - last_press.val) < PRESENCE_TIMEOUT)); + +#ifdef CR50_DEV + if (recent) + CPRINTS("User presence: consumed %d", consume); +#endif + if (consume) + last_press.val = 0; + + /* user physical presence on the power button */ + return recent ? POP_TOUCH_YES : POP_TOUCH_NO; +#endif +} + void board_physical_presence_enable(int enable) { #ifndef CONFIG_U2F diff --git a/board/cr50/tpm2/platform.c b/board/cr50/tpm2/platform.c index 07851b9a23..267ac7cf7a 100644 --- a/board/cr50/tpm2/platform.c +++ b/board/cr50/tpm2/platform.c @@ -10,7 +10,7 @@ #include "pinweaver.h" #include "tpm_nvmem.h" #include "trng.h" -#include "u2f_impl.h" +#include "u2f_cmds.h" #include "util.h" #include "version.h" diff --git a/board/cr50/tpm2/virtual_nvmem.c b/board/cr50/tpm2/virtual_nvmem.c index dc65d75f46..3ddaed067d 100644 --- a/board/cr50/tpm2/virtual_nvmem.c +++ b/board/cr50/tpm2/virtual_nvmem.c @@ -13,7 +13,7 @@ #include "link_defs.h" #include "rma_auth.h" #include "sn_bits.h" -#include "u2f_impl.h" +#include "u2f_cmds.h" #include "virtual_nvmem.h" /* diff --git a/board/cr50/u2f.c b/board/cr50/u2f.c index 43082d5008..78bc25c01f 100644 --- a/board/cr50/u2f.c +++ b/board/cr50/u2f.c @@ -3,181 +3,179 @@ * found in the LICENSE file. */ -/* Helpers to emulate a U2F HID dongle over the TPM transport */ - +#if defined(CRYPTO_TEST_SETUP) || defined(CR50_DEV) #include "console.h" +#endif + #include "dcrypto.h" -#include "extension.h" -#include "nvmem_vars.h" -#include "rbox.h" -#include "registers.h" -#include "signed_header.h" -#include "system.h" -#include "tpm_nvmem_ops.h" -#include "tpm_vendor_cmds.h" -#include "u2f.h" +#include "fips_rand.h" + +#include "u2f_cmds.h" #include "u2f_impl.h" #include "util.h" -#define CPRINTS(format, args...) cprints(CC_EXTENSION, format, ## args) - -/* ---- physical presence (using the laptop power button) ---- */ - -static timestamp_t last_press; - -/* how long do we keep the last button press as valid presence */ -#define PRESENCE_TIMEOUT (10 * SECOND) +enum ec_error_list u2f_generate_hmac_key(struct u2f_state *state) +{ + /* HMAC key for key handle. */ + if (!fips_rand_bytes(state->hmac_key, sizeof(state->hmac_key))) + return EC_ERROR_HW_INTERNAL; + return EC_SUCCESS; +} -void power_button_record(void) +enum ec_error_list u2f_generate_drbg_entropy(struct u2f_state *state) { - if (ap_is_on() && rbox_powerbtn_is_pressed()) { - last_press = get_time(); -#ifdef CR50_DEV - CPRINTS("record pp"); -#endif - } + state->drbg_entropy_size = 0; + /* Get U2F entropy from health-checked TRNG. */ + if (!fips_trng_bytes(state->drbg_entropy, sizeof(state->drbg_entropy))) + return EC_ERROR_HW_INTERNAL; + state->drbg_entropy_size = sizeof(state->drbg_entropy); + return EC_SUCCESS; } -enum touch_state pop_check_presence(int consume) +enum ec_error_list u2f_generate_g2f_secret(struct u2f_state *state) { -#ifdef CRYPTO_TEST_SETUP - return POP_TOUCH_YES; -#else - int recent = ((last_press.val > 0) && - ((get_time().val - last_press.val) < PRESENCE_TIMEOUT)); + /* G2F specific path. */ + if (!fips_rand_bytes(state->salt, sizeof(state->salt))) + return EC_ERROR_HW_INTERNAL; + return EC_SUCCESS; +} -#ifdef CR50_DEV - if (recent) - CPRINTS("User presence: consumed %d", consume); +/* Compute Key handle's HMAC. */ +static void u2f_origin_user_mac(const struct u2f_state *state, + const uint8_t *user, const uint8_t *origin, + const uint8_t *origin_seed, uint8_t kh_version, + uint8_t *kh_hmac) +{ + struct hmac_sha256_ctx ctx; + + /* HMAC(u2f_hmac_key, origin || user || origin seed || version) */ + + HMAC_SHA256_hw_init(&ctx, state->hmac_key, SHA256_DIGEST_SIZE); + HMAC_SHA256_update(&ctx, origin, U2F_APPID_SIZE); + HMAC_SHA256_update(&ctx, user, U2F_USER_SECRET_SIZE); + HMAC_SHA256_update(&ctx, origin_seed, U2F_ORIGIN_SEED_SIZE); + if (kh_version != 0) + HMAC_SHA256_update(&ctx, &kh_version, sizeof(kh_version)); +#ifdef CR50_DEV_U2F_VERBOSE + ccprintf("origin %ph\n", HEX_BUF(origin, U2F_APPID_SIZE)); + ccprintf("user %ph\n", HEX_BUF(user, U2F_USER_SECRET_SIZE)); + ccprintf("origin_seed %ph\n", + HEX_BUF(origin_seed, U2F_ORIGIN_SEED_SIZE)); + cflush(); #endif - if (consume) - last_press.val = 0; - - /* user physical presence on the power button */ - return recent ? POP_TOUCH_YES : POP_TOUCH_NO; + memcpy(kh_hmac, HMAC_SHA256_final(&ctx), SHA256_DIGEST_SIZE); +#ifdef CR50_DEV_U2F_VERBOSE + ccprintf("kh_hmac %ph\n", HEX_BUF(kh_hmac, SHA256_DIGEST_SIZE)); + cflush(); #endif } -static const uint8_t k_salt = NVMEM_VAR_G2F_SALT; -static const uint8_t k_salt_deprecated = NVMEM_VAR_U2F_SALT; - -static int load_state(struct u2f_state *state) +static void u2f_authorization_mac(const struct u2f_state *state, + const union u2f_key_handle_variant *kh, + uint8_t kh_version, + const uint8_t *auth_time_secret_hash, + uint8_t *kh_auth_mac) { - const struct tuple *t_salt = getvar(&k_salt, sizeof(k_salt)); - - if (!t_salt) { - /* Delete the old salt if present, no-op if not. */ - if (setvar(&k_salt_deprecated, sizeof(k_salt_deprecated), - NULL, 0)) - return 0; - - /* create random salt */ - if (!DCRYPTO_ladder_random(state->salt)) - return 0; - if (setvar(&k_salt, sizeof(k_salt), - (const uint8_t *)state->salt, sizeof(state->salt))) - return 0; - } else { - memcpy(state->salt, tuple_val(t_salt), sizeof(state->salt)); - freevar(t_salt); - } - - if (read_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KEK, sizeof(state->salt_kek), - state->salt_kek) == TPM_READ_NOT_FOUND) { - /* - * Not found means that we have not used u2f before, - * or not used it with updated fw that resets kek seed - * on TPM clear. - */ - if (t_salt) { /* Note that memory has been freed already!. */ - /* - * We have previously used u2f, and may have - * existing registrations; we don't want to - * invalidate these, so preserve the existing - * seed as a one-off. It will be changed on - * next TPM clear. - */ - memcpy(state->salt_kek, state->salt, - sizeof(state->salt_kek)); - } else { - /* - * We have never used u2f before - generate - * new seed. - */ - if (!DCRYPTO_ladder_random(state->salt_kek)) - return 0; - } - if (write_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KEK, - sizeof(state->salt_kek), - state->salt_kek, - 1 /* commit */) != TPM_WRITE_CREATED) - return 0; + struct hmac_sha256_ctx ctx; + const uint8_t *auth_salt = NULL; + const void *kh_header = NULL; + size_t kh_header_size = 0; + + if (kh_version == 0) { + memset(kh_auth_mac, 0xff, SHA256_DIGEST_SIZE); + return; } - - if (read_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KH_SALT, - sizeof(state->salt_kh), - state->salt_kh) == TPM_READ_NOT_FOUND) { - /* - * We have never used u2f before - generate - * new seed. - */ - if (!DCRYPTO_ladder_random(state->salt_kh)) - return 0; - - if (write_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KH_SALT, - sizeof(state->salt_kh), - state->salt_kh, - 1 /* commit */) != TPM_WRITE_CREATED) - return 0; + /* At some point we may have v2 key handle, so prepare for it. */ + if (kh_version == 1) { + auth_salt = kh->v1.authorization_salt; + kh_header = &kh->v1; + kh_header_size = U2F_V1_KH_HEADER_SIZE; } - return 1; -} - -struct u2f_state *get_state(void) -{ - static int state_loaded; - static struct u2f_state state; + /** + * HMAC(u2f_hmac_key, auth_salt || key_handle_header + * || authTimeSecret) + */ + HMAC_SHA256_hw_init(&ctx, state->hmac_key, SHA256_DIGEST_SIZE); + HMAC_SHA256_update(&ctx, auth_salt, U2F_AUTHORIZATION_SALT_SIZE); + HMAC_SHA256_update(&ctx, kh_header, kh_header_size); - if (!state_loaded) - state_loaded = load_state(&state); + HMAC_SHA256_update(&ctx, auth_time_secret_hash, + U2F_AUTH_TIME_SECRET_SIZE); - return state_loaded ? &state : NULL; + memcpy(kh_auth_mac, HMAC_SHA256_final(&ctx), SHA256_DIGEST_SIZE); } -/* ---- chip-specific U2F crypto ---- */ - -static int _derive_key(enum dcrypto_appid appid, const uint32_t input[8], - uint32_t output[8]) +static int app_hw_device_id(enum dcrypto_appid appid, const uint32_t input[8], + uint32_t output[8]) { struct APPKEY_CTX ctx; int result; - /* Setup USR-based application key. */ + /** + * Setup USR-based application key. This loads (if not already done) + * application-specific DeviceID. + * Internally it computes: + * HMAC(hw_device_id, SHA256(name[appid])), but we don't care about + * process. + * Important property: + * For same appid it will load same value. + */ if (!DCRYPTO_appkey_init(appid, &ctx)) return 0; + + /** + * Compute HMAC(HMAC(hw_device_id, SHA256(name[appid])), input) + * It is not used as a key though, and treated as personalization + * string for DRBG. + */ result = DCRYPTO_appkey_derive(appid, input, output); DCRYPTO_appkey_finish(&ctx); return result; } -int u2f_origin_user_keypair(const uint8_t *key_handle, size_t key_handle_size, - p256_int *d, p256_int *pk_x, p256_int *pk_y) +/** + * Generate an origin and user-specific ECDSA key pair from the specified + * key handle. + * + * If pk_x and pk_y are NULL, public key generation will be skipped. + * + * @param state U2F state parameters + * @param kh key handle + * @param kh_version key handle version (0 - legacy, 1 - versioned) + * @param d pointer to ECDSA private key + * @param pk_x pointer to public key point + * @param pk_y pointer to public key point + * + * @return EC_SUCCESS if a valid key pair was created. + */ +static enum ec_error_list u2f_origin_user_key_pair( + const struct u2f_state *state, const union u2f_key_handle_variant *kh, + uint8_t kh_version, p256_int *d, p256_int *pk_x, p256_int *pk_y) { uint32_t dev_salt[P256_NDIGITS]; uint8_t key_seed[P256_NBYTES]; struct drbg_ctx drbg; - struct u2f_state *state = get_state(); - - if (!state) - return EC_ERROR_UNKNOWN; + size_t key_handle_size = 0; + uint8_t *key_handle = NULL; + + if (kh_version == 0) { + key_handle_size = sizeof(struct u2f_key_handle_v0); + key_handle = (uint8_t *)&kh->v0; + } else if ((kh_version == 1) && (kh->v1.version == kh_version)) { + key_handle_size = U2F_V1_KH_HEADER_SIZE; + key_handle = (uint8_t *)&kh->v1; + } else { + return EC_ERROR_INVAL; + } - if (!_derive_key(U2F_ORIGIN, state->salt_kek, dev_salt)) + /* TODO(sukhomlinov): implement new FIPS path. */ + if (!app_hw_device_id(U2F_ORIGIN, state->hmac_key, dev_salt)) return EC_ERROR_UNKNOWN; - hmac_drbg_init(&drbg, state->salt_kh, P256_NBYTES, dev_salt, + hmac_drbg_init(&drbg, state->drbg_entropy, P256_NBYTES, dev_salt, P256_NBYTES, NULL, 0); hmac_drbg_generate(&drbg, key_seed, sizeof(key_seed), key_handle, @@ -186,41 +184,243 @@ int u2f_origin_user_keypair(const uint8_t *key_handle, size_t key_handle_size, if (!DCRYPTO_p256_key_from_bytes(pk_x, pk_y, d, key_seed)) return EC_ERROR_TRY_AGAIN; +#ifdef CR50_DEV_U2F_VERBOSE + ccprintf("user private key %ph\n", HEX_BUF(d, sizeof(*d))); + cflush(); + if (pk_x) + ccprintf("user public x %ph\n", HEX_BUF(pk_x, sizeof(*pk_x))); + if (pk_y) + ccprintf("user public y %ph\n", HEX_BUF(pk_y, sizeof(*pk_y))); + cflush(); +#endif + return EC_SUCCESS; } -int u2f_gen_kek(const uint8_t *origin, uint8_t *kek, size_t key_len) +enum ec_error_list u2f_generate(const struct u2f_state *state, + const uint8_t *user, const uint8_t *origin, + const uint8_t *authTimeSecretHash, + union u2f_key_handle_variant *kh, + uint8_t kh_version, struct u2f_ec_point *pubKey) { - uint32_t buf[P256_NDIGITS]; + uint8_t *kh_hmac, *kh_origin_seed; + int generate_key_pair_rc; + /* Generated public keys associated with key handle. */ + p256_int opk_x, opk_y; + + /* Compute constants for request key handler version. */ + if (kh_version == 0) { + kh_hmac = kh->v0.hmac; + kh_origin_seed = kh->v0.origin_seed; + } else if (kh_version == 1) { + kh_hmac = kh->v1.kh_hmac; + kh_origin_seed = kh->v1.origin_seed; + /** + * This may overwrite input parameters if shared + * request/response buffer is used by caller. + */ + kh->v1.version = kh_version; + } else + return EC_ERROR_INVAL; + + /* Generate key handle candidates and origin-specific key pair. */ + do { + p256_int od; + /* Generate random origin seed for key handle candidate. */ + if (!fips_rand_bytes(kh_origin_seed, U2F_ORIGIN_SEED_SIZE)) + return EC_ERROR_HW_INTERNAL; + + u2f_origin_user_mac(state, user, origin, kh_origin_seed, + kh_version, kh_hmac); + + /** + * Try to generate key pair using key handle. This may fail if + * key handle results in private key which is out of allowed + * range. If this is the case, repeat with another origin seed. + */ + generate_key_pair_rc = u2f_origin_user_key_pair( + state, kh, kh_version, &od, &opk_x, &opk_y); - struct u2f_state *state = get_state(); + p256_clear(&od); + } while (generate_key_pair_rc == EC_ERROR_TRY_AGAIN); - if (!state) - return EC_ERROR_UNKNOWN; + if (generate_key_pair_rc != EC_SUCCESS) + return generate_key_pair_rc; - if (key_len != sizeof(buf)) - return EC_ERROR_UNKNOWN; - if (!_derive_key(U2F_WRAP, state->salt_kek, buf)) - return EC_ERROR_UNKNOWN; - memcpy(kek, buf, key_len); + if (kh_version == 1) { + if (!fips_rand_bytes(kh->v1.authorization_salt, + U2F_AUTHORIZATION_SALT_SIZE)) + return EC_ERROR_HW_INTERNAL; + + u2f_authorization_mac(state, kh, kh_version, authTimeSecretHash, + kh->v1.authorization_hmac); + } + + pubKey->pointFormat = U2F_POINT_UNCOMPRESSED; + p256_to_bin(&opk_x, pubKey->x); /* endianness */ + p256_to_bin(&opk_y, pubKey->y); /* endianness */ return EC_SUCCESS; } -int g2f_individual_keypair(p256_int *d, p256_int *pk_x, p256_int *pk_y) +enum ec_error_list u2f_authorize_keyhandle( + const struct u2f_state *state, const union u2f_key_handle_variant *kh, + uint8_t kh_version, const uint8_t *user, const uint8_t *origin, + const uint8_t *authTimeSecretHash) { - uint8_t buf[SHA256_DIGEST_SIZE]; + /* Re-created key handle. */ + uint8_t recreated_hmac[SHA256_DIGEST_SIZE]; + const uint8_t *origin_seed, *kh_hmac; + int result = 0; + + /* + * Re-create the key handle and compare against that which + * was provided. This allows us to verify that the key handle + * is owned by this combination of device, current user and origin. + */ + if (kh_version == 0) { + origin_seed = kh->v0.origin_seed; + kh_hmac = kh->v0.hmac; + } else { + origin_seed = kh->v1.origin_seed; + kh_hmac = kh->v1.kh_hmac; + } + /* First, check inner part. */ + u2f_origin_user_mac(state, user, origin, origin_seed, kh_version, + recreated_hmac); + + /** + * DCRYPTO_equals return 1 if success, by subtracting 1 we make it + * zero, and other results - zero or non-zero will be detected. + */ + result |= DCRYPTO_equals(&recreated_hmac, kh_hmac, + sizeof(recreated_hmac)) - + 1; + + always_memset(recreated_hmac, 0, sizeof(recreated_hmac)); + + if ((kh_version != 0) && (authTimeSecretHash != NULL)) { + u2f_authorization_mac(state, kh, kh_version, authTimeSecretHash, + recreated_hmac); + result |= DCRYPTO_equals(&recreated_hmac, + kh->v1.authorization_hmac, + sizeof(recreated_hmac)) - + 1; + always_memset(recreated_hmac, 0, sizeof(recreated_hmac)); + } + + return (result == 0) ? EC_SUCCESS : EC_ERROR_ACCESS_DENIED; +} + +static enum ec_error_list +u2f_attest_keyhandle_pubkey(const struct u2f_state *state, + const union u2f_key_handle_variant *key_handle, + uint8_t kh_version, const uint8_t *user, + const uint8_t *origin, + const uint8_t *authTimeSecretHash, + const struct u2f_ec_point *public_key) +{ + struct u2f_ec_point kh_pubkey; + p256_int od, opk_x, opk_y; + enum ec_error_list result; + + /* Check this is a correct key handle for provided user/origin. */ + result = u2f_authorize_keyhandle(state, key_handle, kh_version, user, + origin, authTimeSecretHash); + + if (result != EC_SUCCESS) + return result; + + /* Recreate public key from key handle. */ + result = u2f_origin_user_key_pair(state, key_handle, kh_version, &od, + &opk_x, &opk_y); + if (result != EC_SUCCESS) + return result; + + p256_clear(&od); + /* Reconstruct the public key. */ + p256_to_bin(&opk_x, kh_pubkey.x); + p256_to_bin(&opk_y, kh_pubkey.y); + kh_pubkey.pointFormat = U2F_POINT_UNCOMPRESSED; + +#ifdef CR50_DEV_U2F_VERBOSE + ccprintf("recreated key %ph\n", HEX_BUF(&kh_pubkey, sizeof(kh_pubkey))); + ccprintf("provided key %ph\n", HEX_BUF(public_key, sizeof(kh_pubkey))); +#endif + return (DCRYPTO_equals(&kh_pubkey, public_key, + sizeof(struct u2f_ec_point)) == 1) ? + EC_SUCCESS : + EC_ERROR_ACCESS_DENIED; +} - struct u2f_state *state = get_state(); +enum ec_error_list u2f_sign(const struct u2f_state *state, + const union u2f_key_handle_variant *kh, + uint8_t kh_version, const uint8_t *user, + const uint8_t *origin, + const uint8_t *authTimeSecretHash, + const uint8_t *hash, struct u2f_signature *sig) +{ + /* Origin private key. */ + p256_int origin_d; - if (!state) - return EC_ERROR_UNKNOWN; + /* Hash, and corresponding signature. */ + p256_int h, r, s; - /* Incorporate HIK & diversification constant */ - if (!_derive_key(U2F_ATTEST, state->salt, (uint32_t *)buf)) - return EC_ERROR_UNKNOWN; + struct drbg_ctx ctx; + enum ec_error_list result; + + result = u2f_authorize_keyhandle(state, kh, kh_version, user, origin, + authTimeSecretHash); + + if (result != EC_SUCCESS) + return result; + + /* Re-create origin-specific key. */ + result = u2f_origin_user_key_pair(state, kh, kh_version, &origin_d, + NULL, NULL); + if (result != EC_SUCCESS) + return result; + + /* Prepare hash to sign. */ + p256_from_bin(hash, &h); + + /* Now, we processed input parameters, so clean-up output. */ + memset(sig, 0, sizeof(*sig)); + + /* Sign. */ + hmac_drbg_init_rfc6979(&ctx, &origin_d, &h); + result = (dcrypto_p256_ecdsa_sign(&ctx, &origin_d, &h, &r, &s) != 0) ? + EC_SUCCESS : + EC_ERROR_HW_INTERNAL; + + p256_clear(&origin_d); - /* Generate unbiased private key */ + p256_to_bin(&r, sig->sig_r); + p256_to_bin(&s, sig->sig_s); + + return result; +} + +/** + * Generate a hardware derived ECDSA key pair for individual attestation. + * + * @param state U2F state parameters + * @param d pointer to ECDSA private key + * @param pk_x pointer to public key point + * @param pk_y pointer to public key point + * + * @return true if a valid key pair was created. + */ +static bool g2f_individual_key_pair(const struct u2f_state *state, p256_int *d, + p256_int *pk_x, p256_int *pk_y) +{ + uint8_t buf[SHA256_DIGEST_SIZE]; + + /* Incorporate HIK & diversification constant. */ + if (!app_hw_device_id(U2F_ATTEST, state->salt, (uint32_t *)buf)) + return false; + + /* Generate unbiased private key (non-FIPS path). */ while (!DCRYPTO_p256_key_from_bytes(pk_x, pk_y, d, buf)) { struct sha256_ctx sha; @@ -229,22 +429,208 @@ int g2f_individual_keypair(p256_int *d, p256_int *pk_x, p256_int *pk_y) memcpy(buf, SHA256_final(&sha), sizeof(buf)); } - return EC_SUCCESS; + return true; } -int u2f_gen_kek_seed(int commit) +#define G2F_CERT_NAME "CrO2" + +size_t g2f_attestation_cert_serial(const struct u2f_state *state, + const uint8_t *serial, uint8_t *buf) { - struct u2f_state *state = get_state(); + p256_int d, pk_x, pk_y; - if (!state) - return EC_ERROR_UNKNOWN; + if (g2f_individual_key_pair(state, &d, &pk_x, &pk_y)) + return 0; + + /* Note that max length is not currently respected here. */ + return DCRYPTO_x509_gen_u2f_cert_name(&d, &pk_x, &pk_y, + (p256_int *)serial, G2F_CERT_NAME, + buf, + G2F_ATTESTATION_CERT_MAX_LEN); +} + +enum ec_error_list u2f_attest(const struct u2f_state *state, + const union u2f_key_handle_variant *kh, + uint8_t kh_version, const uint8_t *user, + const uint8_t *origin, + const uint8_t *authTimeSecretHash, + const struct u2f_ec_point *public_key, + const uint8_t *data, size_t data_size, + struct u2f_signature *sig) +{ + struct sha256_ctx h_ctx; + struct drbg_ctx dr_ctx; + + /* Data hash, and corresponding signature. */ + p256_int h, r, s; + + /* Attestation key. */ + p256_int d, pk_x, pk_y; + + enum ec_error_list result; + + result = u2f_attest_keyhandle_pubkey(state, kh, kh_version, user, + origin, authTimeSecretHash, + public_key); + + if (result != EC_SUCCESS) + return result; - if (!DCRYPTO_ladder_random(state->salt_kek)) + /* Derive G2F Attestation Key. */ + if (!g2f_individual_key_pair(state, &d, &pk_x, &pk_y)) { +#ifdef CR50_DEV + ccprintf("G2F Attestation key generation failed\n"); +#endif return EC_ERROR_HW_INTERNAL; + } - if (write_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KEK, sizeof(state->salt_kek), - state->salt_kek, commit) == TPM_WRITE_FAIL) - return EC_ERROR_UNKNOWN; + /* Message signature. */ + SHA256_hw_init(&h_ctx); + SHA256_update(&h_ctx, data, data_size); + p256_from_bin(SHA256_final(&h_ctx)->b8, &h); - return EC_SUCCESS; + /* Now, we processed input parameters, so clean-up output. */ + memset(sig, 0, sizeof(*sig)); + + /* Sign over the response w/ the attestation key. */ + hmac_drbg_init_rfc6979(&dr_ctx, &d, &h); + + result = (dcrypto_p256_ecdsa_sign(&dr_ctx, &d, &h, &r, &s) != 0) ? + EC_SUCCESS : + EC_ERROR_HW_INTERNAL; + p256_clear(&d); + + p256_to_bin(&r, sig->sig_r); + p256_to_bin(&s, sig->sig_s); + + return result; +} + +#ifdef CRYPTO_TEST_SETUP +static const char *expect_bool(enum ec_error_list value, + enum ec_error_list expect) +{ + if (value == expect) + return "PASSED"; + return "NOT PASSED"; } + +static int cmd_u2f_test(int argc, char **argv) +{ + static struct u2f_state state; + static union u2f_key_handle_variant kh; + static const uint8_t origin[32] = { 0xff, 0xfe, 0xfd, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8 }; + static const uint8_t user[32] = { 0x88, 0x8e, 0x8d, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7 }; + static const uint8_t authTime[32] = { 0x99, 0x91, 2, 3, 4, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5 }; + static struct u2f_ec_point pubKey; + static struct u2f_signature sig; + + ccprintf("u2f_generate_hmac_key - %s\n", + expect_bool(u2f_generate_hmac_key(&state), EC_SUCCESS)); + + ccprintf("u2f_generate_g2f_secret - %s\n", + expect_bool(u2f_generate_g2f_secret(&state), EC_SUCCESS)); + + ccprintf("u2f_generate_drbg_entropy - %s\n", + expect_bool(u2f_generate_drbg_entropy(&state), EC_SUCCESS)); + + /* Version 0 key handle. */ + ccprintf("u2f_generate - %s\n", + expect_bool(u2f_generate(&state, user, origin, authTime, &kh, + 0, &pubKey), + EC_SUCCESS)); + ccprintf("kh: %ph\n", HEX_BUF(&kh, sizeof(kh.v0))); + ccprintf("pubKey: %ph\n", HEX_BUF(&pubKey, sizeof(pubKey))); + + ccprintf("u2f_authorize_keyhandle - %s\n", + expect_bool(u2f_authorize_keyhandle(&state, &kh, 0, user, + origin, authTime), + EC_SUCCESS)); + + kh.v0.origin_seed[0] ^= 0x10; + ccprintf("u2f_authorize_keyhandle - %s\n", + expect_bool(u2f_authorize_keyhandle(&state, &kh, 0, user, + origin, authTime), + EC_ERROR_ACCESS_DENIED)); + + kh.v0.origin_seed[0] ^= 0x10; + ccprintf("u2f_sign - %s\n", + expect_bool(u2f_sign(&state, &kh, 0, user, origin, authTime, + authTime, &sig), + EC_SUCCESS)); + ccprintf("sig: %ph\n", HEX_BUF(&sig, sizeof(sig))); + + ccprintf("u2f_attest - %s\n", + expect_bool(u2f_attest(&state, &kh, 0, user, origin, authTime, + &pubKey, authTime, sizeof(authTime), + &sig), + EC_SUCCESS)); + ccprintf("sig: %ph\n", HEX_BUF(&sig, sizeof(sig))); + + /* Should fail with incorrect key handle. */ + kh.v0.origin_seed[0] ^= 0x10; + ccprintf("u2f_sign - %s\n", + expect_bool(u2f_sign(&state, &kh, 0, user, origin, authTime, + authTime, &sig), + EC_ERROR_ACCESS_DENIED)); + ccprintf("sig: %ph\n", HEX_BUF(&sig, sizeof(sig))); + + /* Version 1 key handle. */ + ccprintf("\nVersion 1 tests\n"); + ccprintf("u2f_generate - %s\n", + expect_bool(u2f_generate(&state, user, origin, authTime, &kh, + 1, &pubKey), + EC_SUCCESS)); + ccprintf("kh: %ph\n", HEX_BUF(&kh, sizeof(kh.v1))); + ccprintf("pubKey: %ph\n", HEX_BUF(&pubKey, sizeof(pubKey))); + + ccprintf("u2f_authorize_keyhandle - %s\n", + expect_bool(u2f_authorize_keyhandle(&state, &kh, 1, user, + origin, authTime), + EC_SUCCESS)); + + kh.v1.authorization_salt[0] ^= 0x10; + ccprintf("u2f_authorize_keyhandle - %s\n", + expect_bool(u2f_authorize_keyhandle(&state, &kh, 1, user, + origin, authTime), + EC_ERROR_ACCESS_DENIED)); + + kh.v1.authorization_salt[0] ^= 0x10; + ccprintf("u2f_sign - %s\n", + expect_bool(u2f_sign(&state, &kh, 1, user, origin, authTime, + authTime, &sig), + EC_SUCCESS)); + ccprintf("sig: %ph\n", HEX_BUF(&sig, sizeof(sig))); + + ccprintf("u2f_attest - %s\n", + expect_bool(u2f_attest(&state, &kh, 1, user, origin, authTime, + &pubKey, authTime, sizeof(authTime), + &sig), + EC_SUCCESS)); + ccprintf("sig: %ph\n", HEX_BUF(&sig, sizeof(sig))); + + /* Should fail with incorrect key handle. */ + kh.v1.origin_seed[0] ^= 0x10; + ccprintf("u2f_sign - %s\n", + expect_bool(u2f_sign(&state, &kh, 1, user, origin, authTime, + authTime, &sig), + EC_ERROR_ACCESS_DENIED)); + ccprintf("sig: %ph\n", HEX_BUF(&sig, sizeof(sig))); + + cflush(); + + return 0; +} + +DECLARE_SAFE_CONSOLE_COMMAND(u2f_test, cmd_u2f_test, NULL, + "Test U2F functionality"); + +#endif 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 */ |