summaryrefslogtreecommitdiff
path: root/board/cr50/u2f.c
diff options
context:
space:
mode:
Diffstat (limited to 'board/cr50/u2f.c')
-rw-r--r--board/cr50/u2f.c226
1 files changed, 226 insertions, 0 deletions
diff --git a/board/cr50/u2f.c b/board/cr50/u2f.c
new file mode 100644
index 0000000000..f379e66566
--- /dev/null
+++ b/board/cr50/u2f.c
@@ -0,0 +1,226 @@
+/* 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_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();
+}
+
+enum touch_state pop_check_presence(int consume)
+{
+ int recent = (get_time().val - PRESENCE_TIMEOUT) < last_press.val;
+
+ CPRINTS("Presence:%d", recent);
+ 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 uint8_t u2f_mode = MODE_UNSET;
+static const uint8_t k_salt = NVMEM_VAR_U2F_SALT;
+
+static int load_state(void)
+{
+ const struct tuple *t_salt = getvar(&k_salt, sizeof(k_salt));
+
+ if (!t_salt) {
+ /* 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;
+ /* really save the new variable to flash */
+ writevars();
+ } else {
+ memcpy(salt, tuple_val(t_salt), sizeof(salt));
+ }
+
+ 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;
+}
+
+unsigned u2f_custom_dispatch(uint8_t ins, struct apdu apdu,
+ uint8_t *buf, unsigned *ret_len)
+{
+ if (ins == U2F_VENDOR_MODE) {
+ if (apdu.p1) { /* Set mode */
+ u2f_mode = apdu.p2;
+ }
+ /* return the current mode */
+ buf[0] = use_u2f() ? u2f_mode : 0;
+ *ret_len = 1;
+ return U2F_SW_NO_ERROR;
+ }
+ return U2F_SW_INS_NOT_SUPPORTED;
+}
+
+/* ---- 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_keypair(uint8_t *seed, p256_int *d,
+ p256_int *pk_x, p256_int *pk_y)
+{
+ uint32_t tmp[P256_NDIGITS];
+
+ do {
+ if (!DCRYPTO_ladder_random(seed))
+ return EC_ERROR_UNKNOWN;
+ memcpy(tmp, seed, sizeof(tmp));
+ if (!_derive_key(U2F_ORIGIN, tmp, tmp))
+ return EC_ERROR_UNKNOWN;
+ } while (
+ !DCRYPTO_p256_key_from_bytes(pk_x, pk_y, d, (const uint8_t *)tmp));
+
+ return EC_SUCCESS;
+}
+
+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_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, 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;
+}
+
+/* ---- Send/receive U2F APDU over TPM vendor commands ---- */
+
+enum vendor_cmd_rc vc_u2f_apdu(enum vendor_cmd_cc code, void *body,
+ size_t cmd_size, size_t *response_size)
+{
+ unsigned retlen;
+
+ if (!use_u2f()) { /* the feature is disabled */
+ uint8_t *cmd = body;
+ /* process it only if the host tries to enable the feature */
+ if (cmd_size < 2 || cmd[1] != U2F_VENDOR_MODE) {
+ *response_size = 0;
+ return VENDOR_RC_NO_SUCH_COMMAND;
+ }
+ }
+
+ /* Process U2F APDU */
+ retlen = u2f_apdu_rcv(body, cmd_size, *response_size);
+
+ *response_size = retlen;
+ return VENDOR_RC_SUCCESS;
+}
+DECLARE_VENDOR_COMMAND(VENDOR_CC_U2F_APDU, vc_u2f_apdu);