| /* 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 "nvcounter.h" |
| #include "system.h" |
| #include "u2f_impl.h" |
| #include "u2f.h" |
| #include "util.h" |
| |
| #define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ##args) |
| |
| /* Crypto parameters */ |
| #define AES_BLOCK_LEN 16 |
| #define KH_LEN 64 |
| |
| /* Interleave bytes of two 32 byte arrays */ |
| static void interleave32(const uint8_t *a, const uint8_t *b, uint8_t *out) |
| { |
| size_t i; |
| |
| for (i = 0; i < 32; ++i) { |
| out[2 * i + 0] = a[i]; |
| out[2 * i + 1] = b[i]; |
| } |
| } |
| |
| /* 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 anonymous_cert(const p256_int *d, const p256_int *pk_x, |
| const p256_int *pk_y, uint8_t *cert, const int n) |
| { |
| return DCRYPTO_x509_gen_u2f_cert(d, pk_x, pk_y, NULL, cert, n); |
| } |
| |
| 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(d, pk_x, pk_y, serial, cert, n); |
| } |
| |
| static unsigned u2f_version(struct apdu apdu, void *buf, unsigned *ret_len, |
| unsigned max_len) |
| { |
| static const char version[] = "U2F_V2"; |
| |
| if (apdu.len || max_len < sizeof(version) - 1) |
| return U2F_SW_WRONG_LENGTH; |
| |
| memcpy(buf, version, sizeof(version) - 1 /* not ending zero */); |
| *ret_len = sizeof(version) - 1; |
| |
| return U2F_SW_NO_ERROR; |
| } |
| |
| /* U2F REGISTER command */ |
| static unsigned u2f_register(struct apdu apdu, void *buf, |
| unsigned *ret_len, unsigned max_len) |
| { |
| const U2F_REGISTER_REQ *req = (const U2F_REGISTER_REQ *)apdu.data; |
| U2F_REGISTER_RESP *resp; |
| int l, m_off; /* msg length and interior offset */ |
| |
| p256_int r, s; /* ecdsa signature */ |
| struct drbg_ctx ctx; |
| /* Origin keypair */ |
| uint8_t od_seed[SHA256_DIGEST_SIZE]; |
| p256_int od, opk_x, opk_y; |
| /* KDF, Key handle */ |
| HASH_CTX sha; |
| uint8_t kh[U2F_APPID_SIZE + sizeof(p256_int)]; |
| uint8_t tmp[U2F_APPID_SIZE + sizeof(p256_int)]; |
| /* sha256({RFU, app ID, nonce, keyhandle, public key}) */ |
| p256_int h; |
| const uint8_t rfu = U2F_REGISTER_HASH_ID; |
| const uint8_t pk_start = U2F_POINT_UNCOMPRESSED; |
| p256_int att_d; |
| int cert_len; |
| const int cert_max_len = max_len - sizeof(kh) |
| - offsetof(U2F_REGISTER_RESP, keyHandleCertSig); |
| |
| if (apdu.len != sizeof(U2F_REGISTER_REQ)) { |
| CPRINTF("#ERR REGISTER wrong length"); |
| return U2F_SW_WRONG_LENGTH; |
| } |
| |
| /* Check user presence, w/ optional consume */ |
| if (pop_check_presence(apdu.p1 & G2F_CONSUME) != POP_TOUCH_YES && |
| (apdu.p1 & U2F_AUTH_FLAG_TUP) != 0) { |
| return U2F_SW_CONDITIONS_NOT_SATISFIED; |
| } |
| |
| /* Generate origin-specific keypair */ |
| if (u2f_origin_keypair(od_seed, &od, &opk_x, &opk_y) != |
| EC_SUCCESS) { |
| CPRINTF("#ERR Origin-specific keypair generation failed"); |
| return U2F_SW_WTF + 1; |
| } |
| |
| /* Generate key handle */ |
| /* Interleave origin ID, origin priv key, wrap and export. */ |
| interleave32(req->appId, od_seed, tmp); |
| if (wrap_kh(req->appId, tmp, kh, ENCRYPT_MODE) != EC_SUCCESS) |
| return U2F_SW_WTF + 2; |
| |
| /* Response message hash for signing */ |
| DCRYPTO_SHA256_init(&sha, 0); |
| HASH_update(&sha, &rfu, sizeof(rfu)); |
| HASH_update(&sha, req->appId, U2F_APPID_SIZE); |
| HASH_update(&sha, req->chal, U2F_CHAL_SIZE); |
| HASH_update(&sha, kh, sizeof(kh)); |
| HASH_update(&sha, &pk_start, sizeof(pk_start)); |
| |
| /* |
| * 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; |
| |
| /* 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 */ |
| HASH_update(&sha, resp->pubKey.x, sizeof(p256_int)); |
| HASH_update(&sha, resp->pubKey.y, sizeof(p256_int)); |
| p256_from_bin(HASH_final(&sha), &h); |
| |
| /* Construct remainder of the response */ |
| resp->registerId = U2F_REGISTER_ID; |
| l = sizeof(resp->registerId); |
| resp->pubKey.pointFormat = U2F_POINT_UNCOMPRESSED; |
| l += sizeof(resp->pubKey); |
| resp->keyHandleLen = sizeof(kh); |
| l += sizeof(resp->keyHandleLen); |
| memcpy(resp->keyHandleCertSig, kh, sizeof(kh)); |
| l += sizeof(kh); |
| m_off = sizeof(kh); |
| |
| if (use_g2f() && apdu.p1 & G2F_ATTEST) { |
| /* Use a hw-derived keypair for Individual attestation */ |
| if (g2f_individual_keypair(&att_d, &opk_x, &opk_y)) { |
| CPRINTF("#ERR Attestation key generation failed"); |
| return U2F_SW_WTF + 3; |
| } |
| cert_len = individual_cert(&att_d, &opk_x, &opk_y, |
| resp->keyHandleCertSig + m_off, cert_max_len); |
| } else { |
| /* Anon attestation keypair; use origin key to self-sign */ |
| cert_len = anonymous_cert(&od, &opk_x, &opk_y, |
| resp->keyHandleCertSig + m_off, cert_max_len); |
| att_d = od; |
| } |
| if (cert_len == 0) |
| return U2F_SW_WTF + 4; |
| |
| l += cert_len; |
| m_off += cert_len; |
| |
| /* Sign over the response w/ the attestation key */ |
| drbg_rfc6979_init(&ctx, &att_d, &h); |
| if (!dcrypto_p256_ecdsa_sign(&ctx, &att_d, &h, &r, &s)) { |
| p256_clear(&att_d); |
| p256_clear(&od); |
| CPRINTF("#ERR signing error"); |
| return U2F_SW_WTF + 5; |
| } |
| p256_clear(&att_d); |
| p256_clear(&od); |
| |
| /* Signature -> ASN.1 DER encoded bytes */ |
| l += DCRYPTO_asn1_sigp(resp->keyHandleCertSig + m_off, &r, &s); |
| |
| *ret_len = l; |
| |
| return U2F_SW_NO_ERROR; /* APDU success */ |
| } |
| |
| static unsigned u2f_authenticate(struct apdu apdu, void *buf, |
| unsigned *ret_len, unsigned max_len) |
| { |
| const U2F_AUTHENTICATE_REQ *req = (const void *)apdu.data; |
| U2F_AUTHENTICATE_RESP *resp; |
| uint8_t unwrapped_kh[KH_LEN]; |
| uint8_t od_seed[SHA256_DIGEST_SIZE]; |
| struct drbg_ctx ctx; |
| |
| p256_int origin_d; |
| uint8_t origin[U2F_APPID_SIZE]; |
| |
| HASH_CTX sha; |
| p256_int h, r, s; |
| unsigned sig_len; |
| |
| uint8_t flags; |
| uint8_t ctr[U2F_CTR_SIZE]; |
| uint32_t count = 0; |
| |
| if (apdu.len != U2F_APPID_SIZE + U2F_CHAL_SIZE + 1 + KH_LEN) { |
| CPRINTF("#ERR AUTHENTICATE wrong length %d", apdu.len); |
| return U2F_SW_WRONG_LENGTH; |
| } |
| |
| /* Unwrap key handle */ |
| if (wrap_kh(req->appId, req->keyHandle, unwrapped_kh, DECRYPT_MODE)) |
| return U2F_SW_WTF + 1; |
| deinterleave64(unwrapped_kh, origin, od_seed); |
| |
| /* Check whether appId (i.e. origin) matches. |
| * Constant time. |
| */ |
| p256_from_bin(origin, &r); |
| p256_from_bin(req->appId, &s); |
| if (p256_cmp(&r, &s) != 0) |
| return U2F_SW_WRONG_DATA; |
| |
| /* Origin check only? */ |
| if (apdu.p1 == U2F_AUTH_CHECK_ONLY) |
| return U2F_SW_CONDITIONS_NOT_SATISFIED; |
| |
| /* Sense user presence, with optional consume */ |
| flags = pop_check_presence(apdu.p1 & G2F_CONSUME) == POP_TOUCH_YES; |
| |
| /* Mandatory user presence? */ |
| if ((apdu.p1 & U2F_AUTH_ENFORCE) != 0 && flags == 0) |
| return U2F_SW_CONDITIONS_NOT_SATISFIED; |
| |
| /* Increment-only counter in flash. OK to share between origins. */ |
| count = nvcounter_incr(); |
| ctr[0] = (count >> 24) & 0xFF; |
| ctr[1] = (count >> 16) & 0xFF; |
| ctr[2] = (count >> 8) & 0xFF; |
| ctr[3] = count & 0xFF; |
| |
| /* Message signature */ |
| DCRYPTO_SHA256_init(&sha, 0); |
| HASH_update(&sha, req->appId, U2F_APPID_SIZE); |
| HASH_update(&sha, &flags, sizeof(uint8_t)); |
| HASH_update(&sha, ctr, U2F_CTR_SIZE); |
| HASH_update(&sha, req->chal, U2F_CHAL_SIZE); |
| p256_from_bin(HASH_final(&sha), &h); |
| |
| if (u2f_origin_key(od_seed, &origin_d)) |
| return U2F_SW_WTF + 2; |
| |
| drbg_rfc6979_init(&ctx, &origin_d, &h); |
| if (!dcrypto_p256_ecdsa_sign(&ctx, &origin_d, &h, &r, &s)) { |
| p256_clear(&origin_d); |
| return U2F_SW_WTF + 3; |
| } |
| 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; |
| resp->flags = flags; |
| memcpy(resp->ctr, ctr, U2F_CTR_SIZE); |
| |
| sig_len = DCRYPTO_asn1_sigp(resp->sig, &r, &s); |
| |
| *ret_len = sizeof(resp->flags) + U2F_CTR_SIZE + sig_len; |
| return U2F_SW_NO_ERROR; |
| } |
| |
| unsigned u2f_apdu_rcv(uint8_t *buf, unsigned in_len, unsigned max_len) |
| { |
| unsigned ret_len = 0; |
| uint16_t sw = U2F_SW_CLA_NOT_SUPPORTED; |
| /* |
| * APDU structure: |
| * [CLA INS P1 P2 [LC1 [LC2 LC3 <request-data>]]] |
| */ |
| uint8_t cla = buf[0]; |
| uint8_t ins = buf[1]; |
| struct apdu apdu = { |
| .p1 = buf[2], |
| .p2 = buf[3], |
| .len = 0, |
| .data = buf + 5 |
| }; |
| |
| /* ISO7618 LC decoding */ |
| if (in_len >= 5) |
| apdu.len = buf[4]; |
| |
| if (apdu.len == 0 && in_len >= 7) { |
| apdu.len = (buf[5] << 8) | buf[6]; |
| apdu.data += 2; |
| } |
| |
| CPRINTF("%T/%d U2F APDU ", apdu.len); |
| /* Is the APDU well-formed including its payload ? */ |
| if (in_len < 4 || (apdu.len > in_len - (apdu.data - buf))) { |
| sw = U2F_SW_WRONG_LENGTH; |
| goto ret_status; |
| } |
| |
| if (cla == 0x00) { /* Always 0x00 */ |
| sw = U2F_SW_INS_NOT_SUPPORTED; |
| max_len -= 2; /* reserve space for the status */ |
| |
| switch (ins) { |
| case (U2F_REGISTER): |
| CPRINTF("REGISTER"); |
| sw = u2f_register(apdu, buf, &ret_len, max_len); |
| break; |
| |
| case (U2F_AUTHENTICATE): |
| CPRINTF("AUTHENTICATE"); |
| sw = u2f_authenticate(apdu, buf, &ret_len, max_len); |
| break; |
| |
| case (U2F_VERSION): |
| CPRINTF("VERSION"); |
| sw = u2f_version(apdu, buf, &ret_len, max_len); |
| break; |
| } |
| |
| /* Not a U2F INS. Try internal extensions next. */ |
| if (sw == U2F_SW_INS_NOT_SUPPORTED && u2f_custom_dispatch && |
| (use_g2f() || ins == U2F_VENDOR_MODE)) |
| sw = u2f_custom_dispatch(ins, apdu, buf, &ret_len); |
| } |
| |
| ret_status: |
| /* append SW status word */ |
| buf[ret_len++] = sw >> 8; |
| buf[ret_len++] = sw; |
| |
| CPRINTF(" resp %04x len %d\n", sw, ret_len); |
| |
| return ret_len; |
| } |