blob: 038a990b43e02e9080d0209387cd5afb7533547c [file] [log] [blame]
/* Copyright 2017 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.
*/
/* Helpers to emulate a U2F HID dongle over the TPM transport */
#include "console.h"
#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 "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)
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)
{
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;
}
/* ---- non-volatile U2F parameters ---- */
/*
* Current mode defining the behavior of the U2F feature.
* Identical to the one defined on the host side by the enum U2fMode
* in the chrome_device_policy.proto protobuf.
*/
enum u2f_mode {
MODE_UNSET = 0,
/* Feature disabled */
MODE_DISABLED = 1,
/* U2F as defined by the FIDO Alliance specification */
MODE_U2F = 2,
/* U2F plus extensions for individual attestation certificate */
MODE_U2F_EXTENDED = 3,
};
static uint32_t salt[8];
static uint32_t salt_kek[8];
static uint32_t salt_kh[8];
static uint8_t u2f_mode = MODE_UNSET;
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(void)
{
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(salt))
return 0;
if (setvar(&k_salt, sizeof(k_salt), (const uint8_t *)salt,
sizeof(salt)))
return 0;
} else {
memcpy(salt, tuple_val(t_salt), sizeof(salt));
freevar(t_salt);
}
if (read_tpm_nvmem_hidden(TPM_HIDDEN_U2F_KEK, sizeof(salt_kek),
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(salt_kek, salt, sizeof(salt_kek));
} else {
/*
* We have never used u2f before - generate
* new seed.
*/
if (!DCRYPTO_ladder_random(salt_kek))
return 0;
}
if (write_tpm_nvmem_hidden(
TPM_HIDDEN_U2F_KEK,
sizeof(salt_kek), salt_kek, 1 /* commit */) !=
tpm_write_created)
return 0;
}
if (read_tpm_nvmem_hidden(
TPM_HIDDEN_U2F_KH_SALT,
sizeof(salt_kh), salt_kh) ==
tpm_read_not_found) {
/*
* We have never used u2f before - generate
* new seed.
*/
if (!DCRYPTO_ladder_random(salt_kh))
return 0;
if (write_tpm_nvmem_hidden(
TPM_HIDDEN_U2F_KH_SALT,
sizeof(salt_kh), salt_kh, 1 /* commit */) !=
tpm_write_created)
return 0;
}
return 1;
}
static int use_u2f(void)
{
/*
* TODO(b/62294740): Put board ID check here if needed
* if (!board_id_we_want)
* return 0;
*/
if (u2f_mode == MODE_UNSET) {
if (load_state())
/* Start without extension enabled, host will set it */
u2f_mode = MODE_U2F;
}
return u2f_mode >= MODE_U2F;
}
int use_g2f(void)
{
return use_u2f() && u2f_mode == MODE_U2F_EXTENDED;
}
static enum vendor_cmd_rc set_u2f_mode(enum vendor_cmd_cc code, void *buf,
size_t input_size, size_t *response_size)
{
uint8_t *mode = (uint8_t *)buf;
if (input_size != 1)
return VENDOR_RC_BOGUS_ARGS;
u2f_mode = *mode;
*mode = use_u2f() ? u2f_mode : 0;
*response_size = 1;
return VENDOR_RC_SUCCESS;
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_U2F_MODE, set_u2f_mode);
/* ---- chip-specific U2F crypto ---- */
static int _derive_key(enum dcrypto_appid appid, const uint32_t input[8],
uint32_t output[8])
{
struct APPKEY_CTX ctx;
int result;
/* Setup USR-based application key. */
if (!DCRYPTO_appkey_init(appid, &ctx))
return 0;
result = DCRYPTO_appkey_derive(appid, input, output);
DCRYPTO_appkey_finish(&ctx);
return result;
}
int u2f_origin_key(const uint8_t *seed, p256_int *d)
{
uint32_t tmp[P256_NDIGITS];
memcpy(tmp, seed, sizeof(tmp));
if (!_derive_key(U2F_ORIGIN, tmp, tmp))
return EC_ERROR_UNKNOWN;
return DCRYPTO_p256_key_from_bytes(NULL, NULL, d,
(const uint8_t *)tmp) == 0;
}
int u2f_origin_user_keyhandle(const uint8_t *origin,
const uint8_t *user,
const uint8_t *origin_seed,
uint8_t *key_handle)
{
LITE_HMAC_CTX ctx;
memcpy(key_handle, origin_seed, P256_NBYTES);
DCRYPTO_HMAC_SHA256_init(&ctx, salt_kek, SHA256_DIGEST_SIZE);
HASH_update(&ctx.hash, origin, P256_NBYTES);
HASH_update(&ctx.hash, user, P256_NBYTES);
HASH_update(&ctx.hash, origin_seed, P256_NBYTES);
memcpy(key_handle + P256_NBYTES,
DCRYPTO_HMAC_final(&ctx), SHA256_DIGEST_SIZE);
return EC_SUCCESS;
}
int u2f_origin_user_keypair(const uint8_t *key_handle,
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;
if (!_derive_key(U2F_ORIGIN, salt_kek, dev_salt))
return EC_ERROR_UNKNOWN;
hmac_drbg_init(&drbg,
salt_kh, P256_NBYTES,
dev_salt, P256_NBYTES,
NULL, 0);
hmac_drbg_generate(&drbg,
key_seed, sizeof(key_seed),
key_handle, P256_NBYTES * 2);
return DCRYPTO_p256_key_from_bytes(
pk_x, pk_y, d, key_seed) == 0;
}
int u2f_gen_kek(const uint8_t *origin, uint8_t *kek, size_t key_len)
{
uint32_t buf[P256_NDIGITS];
if (key_len != sizeof(buf))
return EC_ERROR_UNKNOWN;
if (!_derive_key(U2F_WRAP, salt_kek, buf))
return EC_ERROR_UNKNOWN;
memcpy(kek, buf, key_len);
return EC_SUCCESS;
}
int g2f_individual_keypair(p256_int *d, p256_int *pk_x, p256_int *pk_y)
{
uint8_t buf[SHA256_DIGEST_SIZE];
/* Incorporate HIK & diversification constant */
if (!_derive_key(U2F_ATTEST, salt, (uint32_t *)buf))
return EC_ERROR_UNKNOWN;
/* Generate unbiased private key */
while (!DCRYPTO_p256_key_from_bytes(pk_x, pk_y, d, buf)) {
HASH_CTX sha;
DCRYPTO_SHA256_init(&sha, 0);
HASH_update(&sha, buf, sizeof(buf));
memcpy(buf, HASH_final(&sha), sizeof(buf));
}
return EC_SUCCESS;
}
int u2f_gen_kek_seed(int commit)
{
if (!DCRYPTO_ladder_random(salt_kek))
return EC_ERROR_HW_INTERNAL;
if (write_tpm_nvmem_hidden(
TPM_HIDDEN_U2F_KEK, sizeof(salt_kek), salt_kek, commit) ==
tpm_write_fail)
return EC_ERROR_UNKNOWN;
return EC_SUCCESS;
}