From 8855605441c69ace829d4acbe584df4f8bf140a5 Mon Sep 17 00:00:00 2001 From: Yicheng Li Date: Tue, 12 May 2020 11:10:11 -0700 Subject: u2f: Add support for versioned key handles Support generating and signing versioned key handles in addition to non-versioned ones. BUG=b:144861739 TEST=used webauthntool to verify that KH generated by old cr50 firmware can be signed with this firmware TEST=used webauthntool to verify that non-versioned KH generated by this firmware can be signed by old cr50 firmware (This and the first TEST proves that non-versioned path is the same as old firmware.) TEST=used webauthntool to verify that non-versioned KH generated by this firmware can be signed by this firmware TEST=used webauthntool to verify that versioned KH generated by this firmware can be signed by this firmware TEST=test_that --board=nami firmware_Cr50U2fCommands Cq-Depend: chromium:2280394 Change-Id: Idf413a1a3e6c35a3e7e651faaa91fe2894b805db Signed-off-by: Yicheng Li Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2202949 Reviewed-by: Louis Collard --- board/cr50/u2f.c | 45 ++++++++---- common/u2f.c | 203 +++++++++++++++++++++++++++++++++++++++++------------ include/u2f.h | 33 ++++++++- include/u2f_impl.h | 34 ++++++--- 4 files changed, 246 insertions(+), 69 deletions(-) diff --git a/board/cr50/u2f.c b/board/cr50/u2f.c index b9ad2de20a..3e6e0c3a69 100644 --- a/board/cr50/u2f.c +++ b/board/cr50/u2f.c @@ -177,10 +177,9 @@ int u2f_origin_key(const uint8_t *seed, p256_int *d) (const uint8_t *)tmp) == 0; } -int u2f_origin_user_keyhandle(const uint8_t *origin, - const uint8_t *user, +int u2f_origin_user_keyhandle(const uint8_t *origin, const uint8_t *user, const uint8_t *origin_seed, - uint8_t *key_handle) + struct u2f_key_handle *key_handle) { LITE_HMAC_CTX ctx; struct u2f_state *state = get_state(); @@ -188,23 +187,44 @@ int u2f_origin_user_keyhandle(const uint8_t *origin, if (!state) return EC_ERROR_UNKNOWN; - memcpy(key_handle, origin_seed, P256_NBYTES); + memcpy(key_handle->origin_seed, origin_seed, P256_NBYTES); DCRYPTO_HMAC_SHA256_init(&ctx, state->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); + memcpy(key_handle->hmac, 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) +int u2f_origin_user_versioned_keyhandle( + const uint8_t *origin, const uint8_t *user, const uint8_t *origin_seed, + uint8_t version, struct u2f_versioned_key_handle *key_handle) +{ + LITE_HMAC_CTX ctx; + struct u2f_state *state = get_state(); + + if (!state) + return EC_ERROR_UNKNOWN; + + key_handle->version = version; + memcpy(key_handle->origin_seed, origin_seed, P256_NBYTES); + + DCRYPTO_HMAC_SHA256_init(&ctx, state->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); + HASH_update(&ctx.hash, &version, sizeof(key_handle->version)); + + memcpy(key_handle->hmac, DCRYPTO_HMAC_final(&ctx), SHA256_DIGEST_SIZE); + + return EC_SUCCESS; +} + +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) { uint32_t dev_salt[P256_NDIGITS]; uint8_t key_seed[P256_NBYTES]; @@ -221,9 +241,8 @@ int u2f_origin_user_keypair(const uint8_t *key_handle, hmac_drbg_init(&drbg, state->salt_kh, P256_NBYTES, dev_salt, P256_NBYTES, NULL, 0); - hmac_drbg_generate(&drbg, - key_seed, sizeof(key_seed), - key_handle, P256_NBYTES * 2); + hmac_drbg_generate(&drbg, key_seed, sizeof(key_seed), key_handle, + key_handle_size); if (!DCRYPTO_p256_key_from_bytes(pk_x, pk_y, d, key_seed)) return EC_ERROR_TRY_AGAIN; diff --git a/common/u2f.c b/common/u2f.c index 79ad69c01a..27e1685696 100644 --- a/common/u2f.c +++ b/common/u2f.c @@ -79,20 +79,59 @@ int g2f_attestation_cert(uint8_t *buf) G2F_ATTESTATION_CERT_MAX_LEN); } +static void copy_kh_pubkey_out(p256_int *opk_x, p256_int *opk_y, + struct u2f_key_handle *kh, void *buf) +{ + struct u2f_generate_resp *resp = buf; + + /* 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(struct u2f_key_handle)); +} + +static void copy_versioned_kh_pubkey_out(p256_int *opk_x, p256_int *opk_y, + struct u2f_versioned_key_handle *kh, + void *buf) +{ + struct u2f_generate_versioned_resp *resp = buf; + + /* 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(struct u2f_versioned_key_handle)); +} + /* 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) { struct u2f_generate_req *req = buf; - struct u2f_generate_resp *resp; + uint8_t kh_version = + (req->flags & U2F_UV_ENABLED_KH) ? U2F_KH_VERSION_1 : 0; - /* Origin keypair */ - uint8_t od_seed[P256_NBYTES]; + /* Origin keypair. Must be word aligned, otherwise TRNG will crash. */ + uint8_t od_seed[P256_NBYTES] __aligned(4); p256_int od, opk_x, opk_y; - /* Key handle */ - uint8_t kh[U2F_FIXED_KH_SIZE]; + /* Buffer for generating key handle. */ + union { + struct u2f_key_handle kh; + struct u2f_versioned_key_handle vkh; + } kh_buf; + size_t kh_size = (kh_version == 0) ? sizeof(kh_buf.kh) : + sizeof(kh_buf.vkh); + /* Whether key handle generation succeeded */ + int generate_kh_rc; /* Whether keypair generation succeeded */ int generate_keypair_rc; @@ -100,10 +139,18 @@ static enum vendor_cmd_rc u2f_generate(enum vendor_cmd_cc code, void *buf, *response_size = 0; - if (input_size != sizeof(struct u2f_generate_req) || - response_buf_size < sizeof(struct u2f_generate_resp)) + if (input_size != sizeof(struct u2f_generate_req)) return VENDOR_RC_BOGUS_ARGS; + if (kh_version == 0) { + if (response_buf_size < sizeof(struct u2f_generate_resp)) + return VENDOR_RC_BOGUS_ARGS; + } else { + if (response_buf_size < + sizeof(struct u2f_generate_versioned_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) @@ -114,12 +161,20 @@ static enum vendor_cmd_rc u2f_generate(enum vendor_cmd_cc code, void *buf, 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) + if (kh_version == 0) + generate_kh_rc = u2f_origin_user_keyhandle( + req->appId, req->userSecret, od_seed, + &kh_buf.kh); + else + generate_kh_rc = u2f_origin_user_versioned_keyhandle( + req->appId, req->userSecret, od_seed, + kh_version, &kh_buf.vkh); + + if (generate_kh_rc != EC_SUCCESS) return VENDOR_RC_INTERNAL_ERROR; - generate_keypair_rc = - u2f_origin_user_keypair(kh, &od, &opk_x, &opk_y); + generate_keypair_rc = u2f_origin_user_keypair( + (uint8_t *)&kh_buf, kh_size, &od, &opk_x, &opk_y); } while (generate_keypair_rc == EC_ERROR_TRY_AGAIN); if (generate_keypair_rc != EC_SUCCESS) @@ -129,31 +184,27 @@ static enum vendor_cmd_rc u2f_generate(enum vendor_cmd_cc code, void *buf, * 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)); + if (kh_version == 0) { + copy_kh_pubkey_out(&opk_x, &opk_y, &kh_buf.kh, buf); + *response_size = sizeof(struct u2f_generate_resp); + } else { + copy_versioned_kh_pubkey_out(&opk_x, &opk_y, &kh_buf.vkh, buf); + *response_size = sizeof(struct u2f_generate_versioned_resp); + } return VENDOR_RC_SUCCESS; } DECLARE_VENDOR_COMMAND(VENDOR_CC_U2F_GENERATE, u2f_generate); -static int verify_kh_pubkey(const uint8_t *key_handle, +static int verify_kh_pubkey(const uint8_t *key_handle, size_t key_handle_size, const struct u2f_ec_point *public_key, int *matches) { int rc; struct u2f_ec_point kh_pubkey; p256_int od, opk_x, opk_y; - rc = u2f_origin_user_keypair(key_handle, &od, &opk_x, &opk_y); + rc = u2f_origin_user_keypair(key_handle, key_handle_size, &od, &opk_x, + &opk_y); if (rc != EC_SUCCESS) return rc; @@ -169,11 +220,36 @@ static int verify_kh_pubkey(const uint8_t *key_handle, } static int verify_kh_owned(const uint8_t *user_secret, const uint8_t *app_id, - const uint8_t *key_handle, int *owned) + const struct u2f_key_handle *key_handle, int *owned) +{ + int rc; + /* Re-created key handle. */ + struct u2f_key_handle recreated_kh; + + /* + * 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->origin_seed, &recreated_kh); + + if (rc == EC_SUCCESS) + *owned = safe_memcmp(&recreated_kh, key_handle, + sizeof(recreated_kh)) == 0; + + return rc; +} + +static int +verify_versioned_kh_owned(const uint8_t *user_secret, const uint8_t *app_id, + const struct u2f_versioned_key_handle *key_handle, + int *owned) { int rc; /* Re-created key handle. */ - uint8_t recreated_kh[KH_LEN]; + struct u2f_versioned_key_handle recreated_kh; /* * Re-create the key handle and compare against that which @@ -181,11 +257,14 @@ static int verify_kh_owned(const uint8_t *user_secret, const uint8_t *app_id, * 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); + rc = u2f_origin_user_versioned_keyhandle(app_id, user_secret, + key_handle->origin_seed, + key_handle->version, + &recreated_kh); if (rc == EC_SUCCESS) - *owned = safe_memcmp(recreated_kh, key_handle, KH_LEN) == 0; + *owned = safe_memcmp(&recreated_kh, key_handle, + sizeof(recreated_kh)) == 0; return rc; } @@ -219,12 +298,15 @@ static enum vendor_cmd_rc u2f_sign(enum vendor_cmd_cc code, void *buf, size_t input_size, size_t *response_size) { const struct u2f_sign_req *req = buf; + const struct u2f_sign_versioned_req *req_versioned = buf; + const uint8_t *key_handle, *hash; + uint8_t flags; struct u2f_sign_resp *resp; struct drbg_ctx ctx; /* Whether the key handle is owned by this device. */ - int kh_owned; + int kh_owned = 0; /* Origin private key. */ uint8_t legacy_origin_seed[SHA256_DIGEST_SIZE]; @@ -236,18 +318,47 @@ static enum vendor_cmd_rc u2f_sign(enum vendor_cmd_cc code, void *buf, /* Whether the key handle uses the legacy key derivation scheme. */ int legacy_kh = 0; + /* Version of KH; 0 if KH is not versioned. */ + uint8_t version; + + /* Size of KH in bytes. */ + size_t kh_size; + + int verify_owned_rc; + /* Response is smaller than request, so no need to check this. */ *response_size = 0; - if (input_size != sizeof(struct u2f_sign_req)) + if (input_size == sizeof(struct u2f_sign_req)) { + version = 0; + key_handle = (uint8_t *)&req->keyHandle; + hash = req->hash; + flags = req->flags; + kh_size = sizeof(struct u2f_key_handle); + verify_owned_rc = verify_kh_owned(req->userSecret, req->appId, + &req->keyHandle, &kh_owned); + } else if (input_size == sizeof(struct u2f_sign_versioned_req)) { + version = req_versioned->keyHandle.version; + key_handle = (uint8_t *)&req->keyHandle; + hash = req_versioned->hash; + flags = req_versioned->flags; + kh_size = sizeof(struct u2f_versioned_key_handle); + verify_owned_rc = verify_versioned_kh_owned( + req_versioned->userSecret, req_versioned->appId, + &req_versioned->keyHandle, &kh_owned); + } else { return VENDOR_RC_BOGUS_ARGS; + } - if (verify_kh_owned(req->userSecret, req->appId, req->keyHandle, - &kh_owned) != EC_SUCCESS) + if (verify_owned_rc != EC_SUCCESS) return VENDOR_RC_INTERNAL_ERROR; if (!kh_owned) { - if ((req->flags & SIGN_LEGACY_KH) == 0) + if ((flags & SIGN_LEGACY_KH) == 0) + return VENDOR_RC_PASSWORD_REQUIRED; + + /* Legacy KH must be version 0. */ + if (version != 0) return VENDOR_RC_PASSWORD_REQUIRED; /* @@ -255,7 +366,8 @@ static enum vendor_cmd_rc u2f_sign(enum vendor_cmd_cc code, void *buf, * 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, + if (verify_legacy_kh_owned(req->appId, + (uint8_t *)&req->keyHandle, legacy_origin_seed)) legacy_kh = 1; else @@ -263,11 +375,11 @@ static enum vendor_cmd_rc u2f_sign(enum vendor_cmd_cc code, void *buf, } /* We might not actually need to sign anything. */ - if (req->flags == U2F_AUTH_CHECK_ONLY) + if ((flags & U2F_AUTH_CHECK_ONLY) == 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) + if (pop_check_presence(flags & G2F_CONSUME) != POP_TOUCH_YES) return VENDOR_RC_NOT_ALLOWED; /* Re-create origin-specific key. */ @@ -275,13 +387,13 @@ static enum vendor_cmd_rc u2f_sign(enum vendor_cmd_cc code, void *buf, 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) + if (u2f_origin_user_keypair(key_handle, kh_size, &origin_d, + NULL, NULL) != EC_SUCCESS) return VENDOR_RC_INTERNAL_ERROR; } /* Prepare hash to sign. */ - p256_from_bin(req->hash, &h); + p256_from_bin(hash, &h); /* Sign. */ hmac_drbg_init_rfc6979(&ctx, &origin_d, &h); @@ -321,6 +433,8 @@ static inline int u2f_attest_verify_reg_resp(const uint8_t *user_secret, { struct g2f_register_msg *msg = (void *)data; int verified; + /* We only do u2f_attest on non-versioned KHs. */ + const int key_handle_size = sizeof(struct u2f_key_handle); if (data_size != sizeof(struct g2f_register_msg)) return VENDOR_RC_NOT_ALLOWED; @@ -328,15 +442,16 @@ static inline int u2f_attest_verify_reg_resp(const uint8_t *user_secret, if (msg->reserved != 0) return VENDOR_RC_NOT_ALLOWED; - if (verify_kh_owned(user_secret, msg->app_id, msg->key_handle, + if (verify_kh_owned(user_secret, msg->app_id, + (struct u2f_key_handle *)&msg->key_handle, &verified) != EC_SUCCESS) return VENDOR_RC_INTERNAL_ERROR; if (!verified) return VENDOR_RC_NOT_ALLOWED; - if (verify_kh_pubkey(msg->key_handle, &msg->public_key, &verified) != - EC_SUCCESS) + if (verify_kh_pubkey(msg->key_handle, key_handle_size, &msg->public_key, + &verified) != EC_SUCCESS) return VENDOR_RC_INTERNAL_ERROR; if (!verified) diff --git a/include/u2f.h b/include/u2f.h index 5af23bc773..61b9677185 100644 --- a/include/u2f.h +++ b/include/u2f.h @@ -30,7 +30,6 @@ extern "C" { #define U2F_CHAL_SIZE 32 /* Size of challenge */ #define U2F_MAX_ATTEST_SIZE 256 /* Size of largest blob to sign */ #define U2F_P256_SIZE 32 -#define U2F_FIXED_KH_SIZE 64 /* Size of fixed size key handles */ #define ENC_SIZE(x) ((x + 7) & 0xfff8) @@ -49,6 +48,21 @@ struct u2f_ec_point { #define U2F_AUTH_ENFORCE 0x03 /* Enforce user presence and sign */ #define U2F_AUTH_CHECK_ONLY 0x07 /* Check only */ #define U2F_AUTH_FLAG_TUP 0x01 /* Test of user presence set */ +/* The key handle can be used with fingerprint or PIN. */ +#define U2F_UV_ENABLED_KH 0x08 + +#define U2F_KH_VERSION_1 0x01 + +struct u2f_key_handle { + uint8_t origin_seed[U2F_P256_SIZE]; + uint8_t hmac[U2F_P256_SIZE]; +}; + +struct u2f_versioned_key_handle { + uint8_t version; + uint8_t origin_seed[U2F_P256_SIZE]; + uint8_t hmac[U2F_P256_SIZE]; +}; /* TODO(louiscollard): Add Descriptions. */ @@ -60,15 +74,28 @@ struct u2f_generate_req { struct u2f_generate_resp { struct u2f_ec_point pubKey; /* Generated public key */ - uint8_t keyHandle[U2F_FIXED_KH_SIZE]; /* Key handle */ + struct u2f_key_handle keyHandle; +}; + +struct u2f_generate_versioned_resp { + struct u2f_ec_point pubKey; /* Generated public key */ + struct u2f_versioned_key_handle keyHandle; }; struct u2f_sign_req { uint8_t appId[U2F_APPID_SIZE]; /* Application id */ uint8_t userSecret[U2F_P256_SIZE]; - uint8_t keyHandle[U2F_FIXED_KH_SIZE]; /* Key handle */ + struct u2f_key_handle keyHandle; + uint8_t hash[U2F_P256_SIZE]; + uint8_t flags; +}; + +struct u2f_sign_versioned_req { + uint8_t appId[U2F_APPID_SIZE]; /* Application id */ + uint8_t userSecret[U2F_P256_SIZE]; uint8_t hash[U2F_P256_SIZE]; uint8_t flags; + struct u2f_versioned_key_handle keyHandle; }; struct u2f_sign_resp { diff --git a/include/u2f_impl.h b/include/u2f_impl.h index 0732a1b72d..5bd69309c6 100644 --- a/include/u2f_impl.h +++ b/include/u2f_impl.h @@ -10,6 +10,7 @@ #include "common.h" #include "cryptoc/p256.h" +#include "u2f.h" /* ---- Physical presence ---- */ @@ -58,14 +59,30 @@ int u2f_origin_key(const uint8_t *seed, p256_int *d); * * @param origin pointer to origin id * @param user pointer to user secret - * @param pointer to origin-specific random seed + * @param seed pointer to origin-specific random seed + * @param key_handle buffer to hold the output key handle * * @return EC_SUCCESS if a valid keypair was created. */ -int u2f_origin_user_keyhandle(const uint8_t *origin, - const uint8_t *user, +int u2f_origin_user_keyhandle(const uint8_t *origin, const uint8_t *user, const uint8_t *seed, - uint8_t *key_handle); + struct u2f_key_handle *key_handle); + +/** + * Pack the specified origin, user secret, origin-specific seed and version + * byte into a key handle. + * + * @param origin pointer to origin id + * @param user pointer to user secret + * @param seed pointer to origin-specific random seed + * @param version the version byte to pack; should be greater than 0. + * @param key_handle buffer to hold the output key handle + * + * @return EC_SUCCESS if a valid keypair was created. + */ +int u2f_origin_user_versioned_keyhandle( + const uint8_t *origin, const uint8_t *user, const uint8_t *seed, + uint8_t version, struct u2f_versioned_key_handle *key_handle); /** * Generate an origin and user-specific ECDSA keypair from the specified @@ -73,17 +90,16 @@ int u2f_origin_user_keyhandle(const uint8_t *origin, * * If pk_x and pk_y are NULL, public key generation will be skipped. * - * @param key_handle pointer to the 64 byte key handle + * @param key_handle pointer to the key handle + * @param key_handle_size size of the key handle in bytes * @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 keypair was created. */ -int u2f_origin_user_keypair(const uint8_t *key_handle, - p256_int *d, - p256_int *pk_x, - p256_int *pk_y); +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 a hardware derived 256b private key. -- cgit v1.2.1