From 7b2480b05595578c1559fcdd9ccc483b82dd5e90 Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Mon, 29 Jun 2026 15:55:51 +0800 Subject: [PATCH 1/2] feat: add CTAPHID_CANCEL and multi-credential selection for FIDO2. --- legacy/firmware/fido2/ctap.c | 161 +++++++++++++++++++++++------ legacy/firmware/fido2/ctap_trans.c | 59 ++++++++--- legacy/firmware/fido2/ctap_trans.h | 1 + legacy/firmware/fido2/u2f_hid.h | 2 + 4 files changed, 177 insertions(+), 46 deletions(-) diff --git a/legacy/firmware/fido2/ctap.c b/legacy/firmware/fido2/ctap.c index cdbc9ad48..c864bb615 100644 --- a/legacy/firmware/fido2/ctap.c +++ b/legacy/firmware/fido2/ctap.c @@ -76,7 +76,7 @@ const uint8_t CTAP_AAGUID[16] = "\x70\xe7\xc3\x6f\xf2\xf6\x9e\x0d\x07\xa6\xbc\xc2\x43\x26\x2e\x6b"; const uint8_t device_cert[] = - "\x30\x82\x02\x65\x30\x82\x02\x0C\xA0\x03\x02\x01\x02\x02\x08\x2F\x1F\xAB" + "\x30\x82\x02\x7C\x30\x82\x02\x21\xA0\x03\x02\x01\x02\x02\x08\x2F\x1F\xAB" "\x58\x0B\xEB\xE5\xF0\x30\x0A\x06\x08\x2A\x86\x48\xCE\x3D\x04\x03\x02\x30" "\x81\x97\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x10\x30" "\x0E\x06\x03\x55\x04\x08\x13\x07\x42\x45\x49\x4A\x49\x4E\x47\x31\x10\x30" @@ -87,7 +87,7 @@ const uint8_t device_cert[] = "\x04\x03\x13\x0B\x4F\x4E\x45\x4B\x45\x59\x20\x52\x4F\x4F\x54\x31\x1C\x30" "\x1A\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01\x16\x0D\x64\x65\x76\x40" "\x6F\x6E\x65\x6B\x65\x79\x2E\x73\x6F\x30\x1E\x17\x0D\x32\x34\x31\x30\x31" - "\x38\x30\x32\x30\x37\x30\x30\x5A\x17\x0D\x32\x39\x31\x30\x31\x38\x30\x32" + "\x38\x30\x32\x30\x37\x30\x30\x5A\x17\x0D\x34\x34\x31\x30\x31\x38\x30\x32" "\x30\x37\x30\x30\x5A\x30\x81\xAA\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13" "\x02\x43\x4E\x31\x10\x30\x0E\x06\x03\x55\x04\x08\x13\x07\x42\x45\x49\x4A" "\x49\x4E\x47\x31\x10\x30\x0E\x06\x03\x55\x04\x07\x13\x07\x48\x41\x49\x44" @@ -103,14 +103,15 @@ const uint8_t device_cert[] = "\x25\xB7\x2C\x5F\xC3\xAC\xFE\xB4\x9C\x64\xB0\x27\xC1\x84\xA3\xEA\x10\xE8" "\xD0\x3D\x48\xA4\xA4\x12\x6C\x3D\xBC\xC6\x1F\x9F\x54\xDA\xB5\xDE\x30\x85" "\xB7\x30\x9F\x28\x2A\xC7\x63\xAF\x6C\x0B\xF2\xFA\xA2\x33\x88\x0F\x75\xA3" - "\x2D\x30\x2B\x30\x09\x06\x03\x55\x1D\x13\x04\x02\x30\x00\x30\x1E\x06\x09" - "\x60\x86\x48\x01\x86\xF8\x42\x01\x0D\x04\x11\x16\x0F\x78\x63\x61\x20\x63" - "\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x30\x0A\x06\x08\x2A\x86\x48\xCE" - "\x3D\x04\x03\x02\x03\x47\x00\x30\x44\x02\x20\x2F\x73\x6A\x80\xBC\x4F\x38" - "\x0D\xDE\x21\xC1\x35\x40\x59\x09\x8E\x4C\x81\x9D\x3E\xA9\x6A\x51\x2F\xB3" - "\x54\xEE\xEF\x5B\x84\xA5\xF9\x02\x20\x04\xD8\x37\x35\x88\x76\xED\x71\x02" - "\x84\x82\x6E\x26\x3A\xB8\x8F\x82\xA4\xF8\xD4\x2E\x15\x94\xB6\xE2\x7E\xDD" - "\xC3\x7D\xE1\xA5\x63"; + "\x42\x30\x40\x30\x09\x06\x03\x55\x1D\x13\x04\x02\x30\x00\x30\x13\x06\x0B" + "\x2B\x06\x01\x04\x01\x82\xE5\x1C\x02\x01\x01\x04\x04\x03\x02\x05\x60\x30" + "\x1E\x06\x09\x60\x86\x48\x01\x86\xF8\x42\x01\x0D\x04\x11\x16\x0F\x78\x63" + "\x61\x20\x63\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x30\x0A\x06\x08\x2A" + "\x86\x48\xCE\x3D\x04\x03\x02\x03\x49\x00\x30\x46\x02\x21\x00\xF8\xCD\xE4" + "\x2F\x83\xBB\x8A\x97\xF4\x58\x64\x8F\x49\x46\x08\x31\x0F\x09\xBB\xB3\xBF" + "\x54\x83\xFF\x27\x07\x25\xEB\x3D\xC4\x01\x08\x02\x21\x00\xF9\xA2\x7C\x63" + "\x94\x2B\xB6\x66\x46\xC6\x8A\x87\x5E\x39\x23\x11\xEC\x39\x83\x10\xDD\x6F" + "\x06\x74\x22\x9F\x26\x9D\x11\x04\x3B\x17"; #endif uint8_t PIN_TOKEN[PIN_TOKEN_SIZE]; @@ -680,7 +681,7 @@ static int ctap_make_auth_data(CTAP_makeCredential mc, uint32_t counter, ((counter & 0xFF) << 24) | ((counter & 0xFF00) << 8) | ((counter & 0xFF0000) >> 8) | ((counter & 0xFF000000) >> 24); - memcpy(authData->attest.aaguid, CTAP_AAGUID, sizeof(CTAP_AAGUID) - 1); + memcpy(authData->attest.aaguid, CTAP_AAGUID, sizeof(CTAP_AAGUID)); authData->attest.credLenL = cred_id_len & 0x00FF; authData->attest.credLenH = (cred_id_len & 0xFF00) >> 8; memcpy(auth_data_buf + sizeof(CTAP_authData), cred_id, cred_id_len); @@ -1048,6 +1049,10 @@ uint8_t ctap_make_credential(CborEncoder *encoder, uint8_t *request, bool yes_up = false; while (1) { usbPoll(); + if (ctap_hid_cancel_is_requested()) { + layoutHome(); + return CTAP2_ERR_KEEPALIVE_CANCEL; + } buttonUpdate(); if (button.YesUp) { yes_up = true; @@ -1280,6 +1285,73 @@ static int8_t save_credential_list(uint8_t *clientDataHash, return 0; } +#define FIDO_ASSERTION_LIST_WINDOW_SIZE 3 + +static void ctap_get_assertion_layout_credential_list(CTAP_getAssertion *GA) { + int start_index = 0; + int end_index = 0; + layout_item_t items[FIDO_ASSERTION_LIST_WINDOW_SIZE] = {0}; + + if (getAssertionState.count <= FIDO_ASSERTION_LIST_WINDOW_SIZE) { + start_index = 0; + end_index = getAssertionState.count - 1; + } else { + start_index = getAssertionState.index - 1; + if (start_index < 0) { + start_index = 0; + } + end_index = start_index + FIDO_ASSERTION_LIST_WINDOW_SIZE - 1; + if (end_index >= (int)getAssertionState.count) { + end_index = getAssertionState.count - 1; + start_index = end_index - FIDO_ASSERTION_LIST_WINDOW_SIZE + 1; + } + } + + for (int i = start_index; i <= end_index; i++) { + CTAP_credentialDescriptor *cred = &GA->creds[i]; + items[i - start_index].label = get_account_name(&cred->credential.user); + items[i - start_index].center = false; + } + + layout_screen_t screen = { + .bmp_up = &bmp_bottom_middle_arrow_up, + .bmp_down = &bmp_bottom_middle_arrow_down, + .bmp_no = &bmp_bottom_left_close, + .bmp_yes = &bmp_bottom_right_confirm, + .title = GA->rp.id, + .items = items, + .item_count = getAssertionState.count, + .item_index = getAssertionState.index, + .item_offset = start_index, + .show_index = true, + .loop = true, + }; + layout_screen(screen); +} + +static void ctap_get_assertion_select_credential(CTAP_getAssertion *GA, + bool next) { + if (getAssertionState.count == 0) { + return; + } + + if (next) { + if (getAssertionState.index < getAssertionState.count - 1) { + getAssertionState.index++; + } else { + getAssertionState.index = 0; + } + } else { + if (getAssertionState.index > 0) { + getAssertionState.index--; + } else { + getAssertionState.index = getAssertionState.count - 1; + } + } + + ctap_get_assertion_layout_credential_list(GA); +} + // static CTAP_credentialDescriptor *pop_credential() { // if (getAssertionState.count > 0 && // getAssertionState.index < getAssertionState.count) { @@ -1847,7 +1919,9 @@ uint8_t ctap_get_assertion(CborEncoder *encoder, uint8_t *request, int length) { ctap_printf("CRED ID (# %d)\n", GA.creds[j].credential.creation_time); } - CTAP_credentialDescriptor *cred = &GA.creds[0]; + getAssertionState.index = 0; + getAssertionState.count = validCredCount; + CTAP_credentialDescriptor *cred = &GA.creds[getAssertionState.index]; if (GA.up || GA.uv) { const char *appname = NULL; @@ -1862,11 +1936,13 @@ uint8_t ctap_get_assertion(CborEncoder *encoder, uint8_t *request, int length) { int current_position = 0; char *account_name = NULL; + cred = &GA.creds[getAssertionState.index]; if (cred->type == PUB_KEY_CRED_PUB_KEY) { account_name = get_account_name(&cred->credential.user); } refresh: + cred = &GA.creds[getAssertionState.index]; if (cred->type == PUB_KEY_CRED_CTAP1) { sha256_Raw((uint8_t *)GA.rp.id, GA.rp.size, rp_id_hash); getReadableAppId(rp_id_hash, &appname); @@ -1874,21 +1950,27 @@ uint8_t ctap_get_assertion(CborEncoder *encoder, uint8_t *request, int length) { NULL, &bmp_bottom_right_confirm, NULL, NULL, _(I__APP_NAME_COLON), appname, NULL, NULL); } else { - truncate_pos = - get_truncate_position(account_name + current_position, &is_end); - strncpy(account_name_buf, account_name + current_position, truncate_pos); - account_name_buf[truncate_pos] = '\0'; - layoutDialogAdapterEx(_(FIDO_2_AUTHENTICATE), &bmp_bottom_left_close, - NULL, &bmp_bottom_right_confirm, NULL, NULL, - _(GLOBAL_APP_NAME), cred->credential.rp.id, - _(GLOBAL_ACCOUNT), account_name_buf); - if (!is_end) { - oledDrawBitmap(3 * OLED_WIDTH / 4, OLED_HEIGHT - 8, - &bmp_bottom_middle_arrow_down); - } - if (history_index > 0) { - oledDrawBitmap(OLED_WIDTH / 4 - 8, OLED_HEIGHT - 8, - &bmp_bottom_middle_arrow_up); + account_name = get_account_name(&cred->credential.user); + if (validCredCount > 1) { + ctap_get_assertion_layout_credential_list(&GA); + } else { + truncate_pos = + get_truncate_position(account_name + current_position, &is_end); + strncpy(account_name_buf, account_name + current_position, + truncate_pos); + account_name_buf[truncate_pos] = '\0'; + layoutDialogAdapterEx(_(FIDO_2_AUTHENTICATE), &bmp_bottom_left_close, + NULL, &bmp_bottom_right_confirm, NULL, NULL, + _(GLOBAL_APP_NAME), cred->credential.rp.id, + _(GLOBAL_ACCOUNT), account_name_buf); + if (!is_end) { + oledDrawBitmap(3 * OLED_WIDTH / 4, OLED_HEIGHT - 8, + &bmp_bottom_middle_arrow_down); + } + if (history_index > 0) { + oledDrawBitmap(OLED_WIDTH / 4 - 8, OLED_HEIGHT - 8, + &bmp_bottom_middle_arrow_up); + } } oledRefresh(); } @@ -1896,6 +1978,10 @@ uint8_t ctap_get_assertion(CborEncoder *encoder, uint8_t *request, int length) { bool yes_up = false; while (1) { usbPoll(); + if (ctap_hid_cancel_is_requested()) { + layoutHome(); + return CTAP2_ERR_KEEPALIVE_CANCEL; + } buttonUpdate(); if (button.YesUp) { getAssertionState.user_verified = true; @@ -1904,13 +1990,23 @@ uint8_t ctap_get_assertion(CborEncoder *encoder, uint8_t *request, int length) { } else if (button.NoUp) { break; } else if (button.UpUp) { - if (history_index > 0) { + if (validCredCount > 1 && cred->type == PUB_KEY_CRED_PUB_KEY) { + ctap_get_assertion_select_credential(&GA, false); + current_position = 0; + history_index = 0; + continue; + } else if (history_index > 0) { history_index--; current_position -= position_history[history_index]; goto refresh; } } else if (button.DownUp) { - if (!is_end) { + if (validCredCount > 1 && cred->type == PUB_KEY_CRED_PUB_KEY) { + ctap_get_assertion_select_credential(&GA, true); + current_position = 0; + history_index = 0; + continue; + } else if (!is_end) { if (history_index < 10 - 1) { position_history[history_index++] = truncate_pos; } @@ -1929,6 +2025,7 @@ uint8_t ctap_get_assertion(CborEncoder *encoder, uint8_t *request, int length) { } } + cred = &GA.creds[getAssertionState.index]; uint32_t auth_data_buf_sz = sizeof(CTAP_authDataHeader); uint32_t counter = config_nextU2FCounter(); @@ -1955,16 +2052,14 @@ uint8_t ctap_get_assertion(CborEncoder *encoder, uint8_t *request, int length) { if (validCredCount > 1) { ret = cbor_encode_int(&map, RESP_numberOfCredentials); // 5 check_ret(ret); - ret = cbor_encode_int(&map, validCredCount); + ret = cbor_encode_int(&map, 1); check_ret(ret); } ret = cbor_encoder_close_container(encoder, &map); check_ret(ret); - ret = save_credential_list(GA.clientDataHash, - GA.creds + 1 /* skip first credential*/, - validCredCount - 1, &GA.extensions); + ret = save_credential_list(GA.clientDataHash, NULL, 0, &GA.extensions); check_retr(ret); return 0; diff --git a/legacy/firmware/fido2/ctap_trans.c b/legacy/firmware/fido2/ctap_trans.c index 53c9d7564..5804cc406 100644 --- a/legacy/firmware/fido2/ctap_trans.c +++ b/legacy/firmware/fido2/ctap_trans.c @@ -109,6 +109,7 @@ typedef enum { } TRANSPORT_TYPE; static uint8_t transport_type = 0; static uint8_t poll_nest = 0; +static volatile bool ctap_hid_cancel_requested = false; typedef struct { uint8_t reserved; @@ -134,6 +135,10 @@ typedef struct { static DIALOG_MANAGER dialog_manager = { .dialog_timer_start = 0, .is_busy = false, .last_req_state = INIT}; +static void ctap_hid_cancel_clear(void) { ctap_hid_cancel_requested = false; } + +bool ctap_hid_cancel_is_requested(void) { return ctap_hid_cancel_requested; } + uint32_t next_cid(void) { // extremely unlikely but hey do { @@ -178,19 +183,37 @@ void dialog_update_state(bool busy, uint32_t timer_start) { dialog_manager.dialog_timer_start = timer_start; } +static void ctap_hid_cancel_cmd(const U2FHID_FRAME *f) { + if (reader == 0 || cid != f->cid || !dialog_is_busy() || + reader->cmd != U2FHID_CBOR) { + return; + } + + ctap_hid_cancel_requested = true; + reader->cmd = 0; + reader->len = 0; + reader->seq = 255; +} + void u2fhid_read(char tiny, const U2FHID_FRAME *f) { (void)tiny; // Always handle init packets directly - if (f->init.cmd == U2FHID_INIT) { - u2f_init_command = true; - u2fhid_init(f); - if (usb_hid_tiny && reader && f->cid == cid) { - // abort current channel - reader->cmd = 0; - reader->len = 0; - reader->seq = 255; - } - return; + switch (f->init.cmd) { + case U2FHID_INIT: + u2f_init_command = true; + u2fhid_init(f); + if (usb_hid_tiny && reader && f->cid == cid) { + // abort current channel + reader->cmd = 0; + reader->len = 0; + reader->seq = 255; + } + return; + case CTAPHID_CANCEL: + ctap_hid_cancel_cmd(f); + return; + default: + break; } if (usb_hid_tiny || dialog_is_busy()) { @@ -423,7 +446,7 @@ void u2fhid_init(const U2FHID_FRAME *in) { resp.versionMajor = VERSION_MAJOR; resp.versionMinor = VERSION_MINOR; resp.versionBuild = VERSION_PATCH; - resp.capFlags = CAPFLAG_WINK; + resp.capFlags = CAPFLAG_WINK | CAPFLAG_CBOR; memcpy(&f.init.data, &resp, sizeof(resp)); queue_u2f_pkt(&f); @@ -1214,6 +1237,7 @@ uint8_t ctap_cbor_cmd(const uint8_t *data, const uint32_t len) { return 0; } + ctap_hid_cancel_clear(); dialog_manager.is_busy = true; switch (cmd) { @@ -1236,7 +1260,11 @@ uint8_t ctap_cbor_cmd(const uint8_t *data, const uint32_t len) { ctap_hid_keepalive_register(); status = ctap_make_credential(&encoder, (uint8_t *)(data + 1), len - 1); ctap_hid_keepalive_unregister(); - ctap_hid_keepalive_process(); + if (ctap_hid_cancel_is_requested()) { + status = CTAP2_ERR_KEEPALIVE_CANCEL; + } else { + ctap_hid_keepalive_process(); + } if (status == CTAP1_ERR_SUCCESS) { *ctap_status = CTAP1_ERR_SUCCESS; resp.length = cbor_encoder_get_buffer_size(&encoder, ctap_data) + 1; @@ -1249,7 +1277,11 @@ uint8_t ctap_cbor_cmd(const uint8_t *data, const uint32_t len) { ctap_hid_keepalive_register(); status = ctap_get_assertion(&encoder, (uint8_t *)(data + 1), len - 1); ctap_hid_keepalive_unregister(); - ctap_hid_keepalive_process(); + if (ctap_hid_cancel_is_requested()) { + status = CTAP2_ERR_KEEPALIVE_CANCEL; + } else { + ctap_hid_keepalive_process(); + } if (status == CTAP1_ERR_SUCCESS) { *ctap_status = CTAP1_ERR_SUCCESS; resp.length = cbor_encoder_get_buffer_size(&encoder, ctap_data) + 1; @@ -1291,6 +1323,7 @@ uint8_t ctap_cbor_cmd(const uint8_t *data, const uint32_t len) { ctap_printf("hid send response\n"); send_u2fhid_msg(U2FHID_CBOR, resp.data, resp.length); } + ctap_hid_cancel_clear(); return 0; } diff --git a/legacy/firmware/fido2/ctap_trans.h b/legacy/firmware/fido2/ctap_trans.h index 6a920d568..2644a0f26 100755 --- a/legacy/firmware/fido2/ctap_trans.h +++ b/legacy/firmware/fido2/ctap_trans.h @@ -67,6 +67,7 @@ void send_u2fhid_error(uint32_t fcid, uint8_t err); // FIDO2 uint8_t ctap_cbor_cmd(const uint8_t *data, const uint32_t len); +bool ctap_hid_cancel_is_requested(void); // ble void ctap_ble_cmd(void); diff --git a/legacy/firmware/fido2/u2f_hid.h b/legacy/firmware/fido2/u2f_hid.h index eb593bc09..033348f37 100755 --- a/legacy/firmware/fido2/u2f_hid.h +++ b/legacy/firmware/fido2/u2f_hid.h @@ -89,6 +89,7 @@ typedef struct __attribute__((packed)) { #define INIT_NONCE_SIZE 8 // Size of channel initialization challenge #define CAPFLAG_WINK 0x01 // Device supports WINK command #define CAPFLAG_LOCK 0x02 // Device supports LOCK command +#define CAPFLAG_CBOR 0x04 // Device supports CBOR command typedef struct __attribute__((packed)) { uint8_t nonce[INIT_NONCE_SIZE]; // Client application nonce @@ -129,6 +130,7 @@ typedef struct __attribute__((packed)) { // FIDO2 #define U2FHID_CBOR (TYPE_INIT | 0x10) // Send CBOR message +#define CTAPHID_CANCEL (TYPE_INIT | 0x11) // Cancel current transaction #define CTAPHID_KEEPALIVE (TYPE_INIT | 0x3b) // Keepalive #define CTAPHID_STATUS_IDLE 0 From 8d48d1773e8ab078ea15df5759e149d94d4e278b Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Mon, 29 Jun 2026 15:58:55 +0800 Subject: [PATCH 2/2] chore: bump version to 3.20.0. --- legacy/firmware/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/legacy/firmware/version.h b/legacy/firmware/version.h index cc1f2697f..b78aa5366 100755 --- a/legacy/firmware/version.h +++ b/legacy/firmware/version.h @@ -7,10 +7,10 @@ #define FIX_VERSION_PATCH 99 #define ONEKEY_VER_MAJOR 3 -#define ONEKEY_VER_MINOR 19 +#define ONEKEY_VER_MINOR 20 #define ONEKEY_VER_PATCH 0 -#define ONEKEY_VERSION "3.19.0" +#define ONEKEY_VERSION "3.20.0" // Deprecated #define ONEKEY_VERSION_HEX 0x3F00