coreboot-libre-fam15h-rdimm/3rdparty/chromeec/common/u2f.c

418 lines
10 KiB
C
Raw Permalink Normal View History

2024-03-04 11:14:53 +01:00
/* 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.
*/
/* APDU dispatcher and U2F command handlers. */
#include "console.h"
#include "cryptoc/p256.h"
#include "cryptoc/sha256.h"
#include "dcrypto.h"
#include "extension.h"
#include "system.h"
#include "u2f_impl.h"
#include "u2f.h"
#include "util.h"
#define G2F_CERT_NAME "CrOS"
#define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ##args)
/* Crypto parameters */
#define AES_BLOCK_LEN 16
#define KH_LEN 64
/* De-interleave 64 bytes into two 32 arrays. */
static void deinterleave64(const uint8_t *in, uint8_t *a, uint8_t *b)
{
size_t i;
for (i = 0; i < 32; ++i) {
a[i] = in[2 * i + 0];
b[i] = in[2 * i + 1];
}
}
/* (un)wrap w/ the origin dependent KEK. */
static int wrap_kh(const uint8_t *origin, const uint8_t *in,
uint8_t *out, enum encrypt_mode mode)
{
uint8_t kek[SHA256_DIGEST_SIZE];
uint8_t iv[AES_BLOCK_LEN] = {0};
int i;
/* KEK derivation */
if (u2f_gen_kek(origin, kek, sizeof(kek)))
return EC_ERROR_UNKNOWN;
DCRYPTO_aes_init(kek, 256, iv, CIPHER_MODE_CBC, mode);
for (i = 0; i < 4; i++)
DCRYPTO_aes_block(in + i * AES_BLOCK_LEN,
out + i * AES_BLOCK_LEN);
return EC_SUCCESS;
}
static int individual_cert(const p256_int *d, const p256_int *pk_x,
const p256_int *pk_y, uint8_t *cert, const int n)
{
p256_int *serial;
if (system_get_chip_unique_id((uint8_t **)&serial) != P256_NBYTES)
return 0;
return DCRYPTO_x509_gen_u2f_cert_name(d, pk_x, pk_y, serial,
G2F_CERT_NAME, cert, n);
}
int g2f_attestation_cert(uint8_t *buf)
{
p256_int d, pk_x, pk_y;
if (!use_g2f())
return 0;
if (g2f_individual_keypair(&d, &pk_x, &pk_y))
return 0;
/* Note that max length is not currently respected here. */
return individual_cert(&d, &pk_x, &pk_y,
buf, G2F_ATTESTATION_CERT_MAX_LEN);
}
/* U2F GENERATE command */
static enum vendor_cmd_rc u2f_generate(enum vendor_cmd_cc code,
void *buf,
size_t input_size,
size_t *response_size)
{
U2F_GENERATE_REQ *req = buf;
U2F_GENERATE_RESP *resp;
/* Origin keypair */
uint8_t od_seed[P256_NBYTES];
p256_int od, opk_x, opk_y;
/* Key handle */
uint8_t kh[U2F_FIXED_KH_SIZE];
/* Whether keypair generation succeeded */
int generate_keypair_rc;
size_t response_buf_size = *response_size;
*response_size = 0;
if (input_size != sizeof(U2F_GENERATE_REQ) ||
response_buf_size < sizeof(U2F_GENERATE_RESP))
return VENDOR_RC_BOGUS_ARGS;
/* Maybe enforce user presence, w/ optional consume */
if (pop_check_presence(req->flags & G2F_CONSUME) != POP_TOUCH_YES &&
(req->flags & U2F_AUTH_FLAG_TUP) != 0)
return VENDOR_RC_NOT_ALLOWED;
/* Generate origin-specific keypair */
do {
if (!DCRYPTO_ladder_random(&od_seed))
return VENDOR_RC_INTERNAL_ERROR;
if (u2f_origin_user_keyhandle(req->appId, req->userSecret,
od_seed, kh) != EC_SUCCESS)
return VENDOR_RC_INTERNAL_ERROR;
generate_keypair_rc =
u2f_origin_user_keypair(kh, &od, &opk_x, &opk_y);
} while (generate_keypair_rc == EC_ERROR_TRY_AGAIN);
if (generate_keypair_rc != EC_SUCCESS)
return VENDOR_RC_INTERNAL_ERROR;
/*
* From this point: the request 'req' content is invalid as it is
* overridden by the response we are building in the same buffer.
*/
resp = buf;
*response_size = sizeof(*resp);
/* Insert origin-specific public keys into the response */
p256_to_bin(&opk_x, resp->pubKey.x); /* endianness */
p256_to_bin(&opk_y, resp->pubKey.y); /* endianness */
resp->pubKey.pointFormat = U2F_POINT_UNCOMPRESSED;
/* Copy key handle to response. */
memcpy(resp->keyHandle, kh, sizeof(kh));
return VENDOR_RC_SUCCESS;
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_U2F_GENERATE, u2f_generate);
static int verify_kh_owned(const uint8_t *user_secret, const uint8_t *app_id,
const uint8_t *key_handle, int *owned)
{
int rc;
/* Re-created key handle. */
uint8_t recreated_kh[KH_LEN];
/*
* 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 app_id.
*/
rc = u2f_origin_user_keyhandle(app_id, user_secret, key_handle,
recreated_kh);
if (rc == EC_SUCCESS)
*owned = safe_memcmp(recreated_kh, key_handle, KH_LEN) == 0;
return rc;
}
static int verify_legacy_kh_owned(const uint8_t *app_id,
const uint8_t *key_handle,
uint8_t *origin_seed)
{
uint8_t unwrapped_kh[KH_LEN];
uint8_t kh_app_id[U2F_APPID_SIZE];
p256_int app_id_p256;
p256_int kh_app_id_p256;
/* Unwrap key handle */
if (wrap_kh(app_id, key_handle, unwrapped_kh, DECRYPT_MODE))
return 0;
deinterleave64(unwrapped_kh, kh_app_id, origin_seed);
/* Return whether appId (i.e. origin) matches. */
p256_from_bin(app_id, &app_id_p256);
p256_from_bin(kh_app_id, &kh_app_id_p256);
return p256_cmp(&app_id_p256, &kh_app_id_p256) == 0;
}
/* Below, we depend on the response not being larger than than the request. */
BUILD_ASSERT(sizeof(U2F_SIGN_RESP) <= sizeof(U2F_SIGN_REQ));
/* U2F SIGN command */
static enum vendor_cmd_rc u2f_sign(enum vendor_cmd_cc code,
void *buf,
size_t input_size,
size_t *response_size)
{
const U2F_SIGN_REQ *req = buf;
U2F_SIGN_RESP *resp;
struct drbg_ctx ctx;
/* Whether the key handle is owned by this device. */
int kh_owned;
/* Origin private key. */
uint8_t legacy_origin_seed[SHA256_DIGEST_SIZE];
p256_int origin_d;
/* Hash, and corresponding signature. */
p256_int h, r, s;
/* Whether the key handle uses the legacy key derivation scheme. */
int legacy_kh = 0;
/* Response is smaller than request, so no need to check this. */
*response_size = 0;
if (input_size != sizeof(U2F_SIGN_REQ))
return VENDOR_RC_BOGUS_ARGS;
if (verify_kh_owned(req->userSecret, req->appId, req->keyHandle,
&kh_owned) != EC_SUCCESS)
return VENDOR_RC_INTERNAL_ERROR;
if (!kh_owned) {
if ((req->flags & SIGN_LEGACY_KH) == 0)
return VENDOR_RC_PASSWORD_REQUIRED;
/*
* We have a key handle which is not valid for the new scheme,
* but may be a valid legacy key handle, and we have been asked
* to sign legacy key handles.
*/
if (verify_legacy_kh_owned(req->appId, req->keyHandle,
legacy_origin_seed))
legacy_kh = 1;
else
return VENDOR_RC_PASSWORD_REQUIRED;
}
/* We might not actually need to sign anything. */
if (req->flags == U2F_AUTH_CHECK_ONLY)
return VENDOR_RC_SUCCESS;
/* Always enforce user presence, with optional consume. */
if (pop_check_presence(req->flags & G2F_CONSUME) != POP_TOUCH_YES)
return VENDOR_RC_NOT_ALLOWED;
/* Re-create origin-specific key. */
if (legacy_kh) {
if (u2f_origin_key(legacy_origin_seed, &origin_d) != EC_SUCCESS)
return VENDOR_RC_INTERNAL_ERROR;
} else {
if (u2f_origin_user_keypair(req->keyHandle, &origin_d, NULL,
NULL) != EC_SUCCESS)
return VENDOR_RC_INTERNAL_ERROR;
}
/* Prepare hash to sign. */
p256_from_bin(req->hash, &h);
/* Sign. */
hmac_drbg_init_rfc6979(&ctx, &origin_d, &h);
if (!dcrypto_p256_ecdsa_sign(&ctx, &origin_d, &h, &r, &s)) {
p256_clear(&origin_d);
return VENDOR_RC_INTERNAL_ERROR;
}
p256_clear(&origin_d);
/*
* From this point: the request 'req' content is invalid as it is
* overridden by the response we are building in the same buffer.
* The response is smaller than the request, so we have the space.
*/
resp = buf;
*response_size = sizeof(*resp);
p256_to_bin(&r, resp->sig_r);
p256_to_bin(&s, resp->sig_s);
return VENDOR_RC_SUCCESS;
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_U2F_SIGN, u2f_sign);
struct G2F_REGISTER_MSG {
uint8_t reserved;
uint8_t app_id[U2F_APPID_SIZE];
uint8_t challenge[U2F_CHAL_SIZE];
uint8_t key_handle[U2F_APPID_SIZE + sizeof(p256_int)];
U2F_EC_POINT public_key;
};
static inline int u2f_attest_verify_reg_resp(const uint8_t *user_secret,
uint8_t data_size,
const uint8_t *data)
{
struct G2F_REGISTER_MSG *msg = (void *) data;
int kh_owned;
if (data_size != sizeof(struct G2F_REGISTER_MSG))
return VENDOR_RC_NOT_ALLOWED;
if (verify_kh_owned(user_secret, msg->app_id, msg->key_handle,
&kh_owned) != EC_SUCCESS)
return VENDOR_RC_INTERNAL_ERROR;
if (!kh_owned)
return VENDOR_RC_NOT_ALLOWED;
return VENDOR_RC_SUCCESS;
}
static int u2f_attest_verify(const uint8_t *user_secret,
uint8_t format,
uint8_t data_size,
const uint8_t *data)
{
switch (format) {
case U2F_ATTEST_FORMAT_REG_RESP:
return u2f_attest_verify_reg_resp(user_secret, data_size, data);
default:
return VENDOR_RC_NOT_ALLOWED;
}
}
static inline size_t u2f_attest_format_size(uint8_t format)
{
switch (format) {
case U2F_ATTEST_FORMAT_REG_RESP:
return sizeof(struct G2F_REGISTER_MSG);
default:
return 0;
}
}
/* U2F ATTEST command */
static enum vendor_cmd_rc u2f_attest(enum vendor_cmd_cc code,
void *buf,
size_t input_size,
size_t *response_size)
{
const U2F_ATTEST_REQ *req = buf;
U2F_ATTEST_RESP *resp;
int verify_ret;
HASH_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;
size_t response_buf_size = *response_size;
*response_size = 0;
if (input_size < 2 ||
input_size < (2 + req->dataLen) ||
input_size > sizeof(U2F_ATTEST_REQ) ||
response_buf_size < sizeof(*resp))
return VENDOR_RC_BOGUS_ARGS;
verify_ret = u2f_attest_verify(req->userSecret,
req->format,
req->dataLen,
req->data);
if (verify_ret != VENDOR_RC_SUCCESS)
return verify_ret;
/* Message signature */
DCRYPTO_SHA256_init(&h_ctx, 0);
HASH_update(&h_ctx, req->data, u2f_attest_format_size(req->format));
p256_from_bin(HASH_final(&h_ctx), &h);
/* Derive G2F Attestation Key */
if (g2f_individual_keypair(&d, &pk_x, &pk_y)) {
CPRINTF("G2F Attestation key generation failed");
return VENDOR_RC_INTERNAL_ERROR;
}
/* Sign over the response w/ the attestation key */
hmac_drbg_init_rfc6979(&dr_ctx, &d, &h);
if (!dcrypto_p256_ecdsa_sign(&dr_ctx, &d, &h, &r, &s)) {
CPRINTF("Signing error");
return VENDOR_RC_INTERNAL_ERROR;
}
p256_clear(&d);
/*
* From this point: the request 'req' content is invalid as it is
* overridden by the response we are building in the same buffer.
* The response is smaller than the request, so we have the space.
*/
resp = buf;
*response_size = sizeof(*resp);
p256_to_bin(&r, resp->sig_r);
p256_to_bin(&s, resp->sig_s);
return VENDOR_RC_SUCCESS;
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_U2F_ATTEST, u2f_attest);