From 9042de815af8cea7a762829d51aa085d69e91608 Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Fri, 31 Jul 2026 11:25:15 +0800 Subject: [PATCH 01/11] fix: keep bootloader header at 0x400 bytes --- core/embed/bootloader/header.S | 10 +++++++++- core/embed/bootloader/memory.ld | 4 +++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/embed/bootloader/header.S b/core/embed/bootloader/header.S index 9487ccb033..f1a1afcb97 100644 --- a/core/embed/bootloader/header.S +++ b/core/embed/bootloader/header.S @@ -23,7 +23,15 @@ g_header: . = . + 8 // reserved . = . + 512 // hash1 ... hash16 . = . + 399 // reserved - .string BUILD_COMMIT // commit_id + // Keep this field aligned with image_header.build_id[16]. +g_build_id: + .string BUILD_COMMIT // NUL-terminated commit ID +g_build_id_end: + .if (g_build_id_end - g_build_id) > 16 + .error "BUILD_COMMIT must be at most 15 characters" + .else + .fill 16 - (g_build_id_end - g_build_id), 1, 0 + .endif .byte 0 // sigmask . = . + 64 // sig g_header_end: diff --git a/core/embed/bootloader/memory.ld b/core/embed/bootloader/memory.ld index 110fc10f15..733e775ff1 100644 --- a/core/embed/bootloader/memory.ld +++ b/core/embed/bootloader/memory.ld @@ -31,6 +31,8 @@ SECTIONS { .header : ALIGN(4) { KEEP(*(.header)); } >FLASH AT>FLASH + ASSERT(SIZEOF(.header) == 0x400, + "bootloader header must be exactly 0x400 bytes") .flash : ALIGN(512) { KEEP(*(.vector_table)); @@ -51,7 +53,7 @@ SECTIONS { .padding : { KEEP(*(.padding)); FILL(0xDEADBEEF); /* fill pattern */ - . = LENGTH(FLASH) - SIZEOF(.header) - SIZEOF(.flash) - SIZEOF(.data) - 1 - 8; + . = LENGTH(FLASH) - SIZEOF(.header) - SIZEOF(.flash) - SIZEOF(.data) - 1; BYTE(0x00); /* needed to keep this section without references in .s or .c */ } >FLASH AT>FLASH From ab5a6280be1153adcc782c511ea1834e137f9cfd Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Mon, 3 Aug 2026 12:51:40 +0800 Subject: [PATCH 02/11] feat: upgrade THD89 secure channel protocol. --- core/SConscript.bootloader | 2 + core/SConscript.firmware | 1 + .../extmod/modtrezorutils/modtrezorutils.c | 2 +- core/embed/trezorhal/se_thd89.c | 967 +++++++++++------- core/embed/trezorhal/se_thd89.h | 6 +- core/embed/trezorhal/se_thd89_v2.c | 174 ++++ core/embed/trezorhal/se_thd89_v2.h | 41 + core/embed/trezorhal/thd89.c | 68 +- core/embed/trezorhal/thd89.h | 3 + 9 files changed, 901 insertions(+), 363 deletions(-) create mode 100644 core/embed/trezorhal/se_thd89_v2.c create mode 100644 core/embed/trezorhal/se_thd89_v2.h diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader index a980acb30c..c68d391de7 100644 --- a/core/SConscript.bootloader +++ b/core/SConscript.bootloader @@ -40,6 +40,7 @@ SOURCE_MOD += [ 'vendor/trezor-crypto/secp256k1.c', 'vendor/trezor-crypto/memzero.c', 'vendor/trezor-crypto/rand.c', + 'vendor/trezor-crypto/hmac.c', 'vendor/trezor-crypto/sha2.c', 'vendor/trezor-crypto/aes/aes_modes.c', 'vendor/trezor-crypto/aes/aescrypt.c', @@ -178,6 +179,7 @@ SOURCE_TREZORHAL = [ 'embed/trezorhal/thd89.c', 'embed/trezorhal/thd89_boot.c', 'embed/trezorhal/se_thd89.c', + 'embed/trezorhal/se_thd89_v2.c', 'embed/trezorhal/util.s', 'embed/trezorhal/vectortable.s', 'embed/trezorhal/camera.c', diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 591f7e6462..4646f244f8 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -495,6 +495,7 @@ if PRODUCTION_MODEL == 'H': 'embed/trezorhal/usbd_ulpi.c', 'embed/trezorhal/thd89.c', 'embed/trezorhal/se_thd89.c', + 'embed/trezorhal/se_thd89_v2.c', 'embed/trezorhal/trans_fifo.c', 'embed/trezorhal/usart.c', 'embed/trezorhal/motor.c', diff --git a/core/embed/extmod/modtrezorutils/modtrezorutils.c b/core/embed/extmod/modtrezorutils/modtrezorutils.c index 212e6f8ee6..2737d00083 100644 --- a/core/embed/extmod/modtrezorutils/modtrezorutils.c +++ b/core/embed/extmod/modtrezorutils/modtrezorutils.c @@ -461,7 +461,7 @@ STATIC mp_obj_t mod_trezorutils_se_boot_build_id(mp_obj_t se_addr) { return mp_obj_new_str_copy(&mp_type_str, (const uint8_t *)"EMULATOR", 8); #else int addr = mp_obj_get_int(se_addr); - char str[8] = {0}; + char str[16] = {0}; se_get_boot_build_id(addr, str, sizeof(str)); return mp_obj_new_str_copy(&mp_type_str, (const uint8_t *)str, strlen(str)); diff --git a/core/embed/trezorhal/se_thd89.c b/core/embed/trezorhal/se_thd89.c index 3d0b0b878c..1c1cd95d7e 100644 --- a/core/embed/trezorhal/se_thd89.c +++ b/core/embed/trezorhal/se_thd89.c @@ -13,6 +13,7 @@ #include "rand.h" #include "se_thd89.h" +#include "se_thd89_v2.h" #include "secp256k1.h" #include "thd89.h" @@ -37,8 +38,12 @@ #define SE_INS_HASHR 0xED #define SE_INS_HASHRAM 0xEE #define SE_INS_FINGERPRINT 0xEF +#define SE_INS_GET_STATE 0xCA +#define SE_INS_COMPONENT_VERSION 0xF3 #define SE_INS_FIDO 0xF9 +#define SE_COMPONENT_VERSION_SLOT_COUNT 10 + typedef enum { SE_FIDO_GEN_SEED = 0x00, SE_FIDO_U2F_REGISTER, @@ -53,16 +58,39 @@ typedef enum { SE_FIDO_ATT_SIGN, } SE_FIDO_P2; -#define SE_PIN_RETRY_MAX 10 +#define SE_PIN_RETRY_MAX 5 +#define SE_SW_PIN_RETRY_LIMIT_REACHED 0x6983 #define SE_DATA_MAX_LEN (1024) #define SE_BUF_MAX_LEN (1024 + 64) static uint8_t se_session_key[SESSION_KEYLEN]; +static uint8_t se_session_mac_key[SESSION_KEYLEN]; static uint8_t se_fp_session_key[SESSION_KEYLEN]; +static uint8_t se_fp_session_mac_key[SESSION_KEYLEN]; static bool se_session_init = false; static bool se_fp_session_init = false; +typedef enum { + SE_LONG_OPERATION_NONE = 0, + SE_LONG_OPERATION_SET_PASSPHRASE_PIN, + SE_LONG_OPERATION_SESSION_SEED, + SE_LONG_OPERATION_CARDANO_SEED, + SE_LONG_OPERATION_FIDO_SEED, +} se_long_operation_t; + +typedef enum { + SE_SECURE_RESPONSE_OK = 0, + SE_SECURE_RESPONSE_AUTHENTICATED_ERROR, + SE_SECURE_RESPONSE_NO_MAC_6C, + SE_SECURE_RESPONSE_INVALID, +} se_secure_response_result_t; + +static se_long_operation_t se_pending_operation = SE_LONG_OPERATION_NONE; +static se_long_operation_t se_fp_pending_operation = SE_LONG_OPERATION_NONE; + +static secbool se_query_progress_percent_ex(uint8_t addr, uint8_t *percent); + static pin_result_t pin_result_type = PIN_FAILED; static pin_result_t pin_passphrase_ret = PIN_FAILED; @@ -81,13 +109,36 @@ static uint16_t se_recv_len; static UI_WAIT_CALLBACK ui_callback = NULL; -static void xor_cal(uint8_t *data1, uint8_t *data2, uint16_t len, - uint8_t * xor) { - uint16_t i; +static se_long_operation_t *se_get_pending_operation(uint8_t addr) { + return addr == THD89_FINGER_ADDRESS ? &se_fp_pending_operation + : &se_pending_operation; +} + +static void se_invalidate_session(uint8_t addr) { + if (addr == THD89_FINGER_ADDRESS) { + memzero(se_fp_session_key, sizeof(se_fp_session_key)); + memzero(se_fp_session_mac_key, sizeof(se_fp_session_mac_key)); + se_fp_session_init = false; + se_fp_pending_operation = SE_LONG_OPERATION_NONE; + } else { + memzero(se_session_key, sizeof(se_session_key)); + memzero(se_session_mac_key, sizeof(se_session_mac_key)); + se_session_init = false; + se_pending_operation = SE_LONG_OPERATION_NONE; + } +} - for (i = 0; i < len; i++) { - xor[i] = data1[i] ^ data2[i]; +static secbool se_session_is_initialized(uint8_t addr, + const uint8_t *session_key) { + if (addr == THD89_MASTER_ADDRESS && session_key == se_session_key && + se_session_init) { + return sectrue; } + if (addr == THD89_FINGER_ADDRESS && session_key == se_fp_session_key && + se_fp_session_init) { + return sectrue; + } + return secfalse; } void se_set_ui_callback(UI_WAIT_CALLBACK callback) { ui_callback = callback; } @@ -111,8 +162,9 @@ secbool se_fp_get_rand(uint8_t *rand, uint16_t rand_len) { static secbool se_reset_se_ex(uint8_t addr) { uint8_t cmd[5] = {0x00, 0xF0, 0x00, 0x00, 0x00}; - uint16_t resp_len; + uint16_t resp_len = 0; + se_invalidate_session(addr); secbool result = thd89_transmit_ex(addr, cmd, sizeof(cmd), NULL, &resp_len); hal_delay(400); // time for se to power up @@ -124,8 +176,18 @@ secbool se_reset_se(void) { return se_reset_se_ex(THD89_MASTER_ADDRESS); } secbool se_fp_reset_se(void) { return se_reset_se_ex(THD89_FINGER_ADDRESS); } -static void cal_mac(uint8_t *session_key, uint8_t *data, uint32_t len, - uint8_t *mac) { +static uint8_t *se_get_session_mac_key(uint8_t *session_key) { + if (session_key == se_session_key) { + return se_session_mac_key; + } + if (session_key == se_fp_session_key) { + return se_fp_session_mac_key; + } + return NULL; +} + +static void cal_mac(uint8_t *session_key, const uint8_t *nonce, uint8_t *data, + uint32_t len, uint8_t *mac) { uint8_t pad_buf[16], mac_buf[16], iv[16]; uint32_t pad_len, res_len; aes_encrypt_ctx ctxe; @@ -142,6 +204,10 @@ static void cal_mac(uint8_t *session_key, uint8_t *data, uint32_t len, pad_buf[res_len] = 0x80; aes_encrypt_key128(session_key, &ctxe); + if (nonce) { + aes_cbc_encrypt(nonce, mac_buf, AES_BLOCK_SIZE, iv, &ctxe); + memcpy(iv, mac_buf, AES_BLOCK_SIZE); + } len += pad_len; for (uint32_t i = 0; i < (len - AES_BLOCK_SIZE); i += AES_BLOCK_SIZE) { aes_cbc_encrypt(data + i, mac_buf, AES_BLOCK_SIZE, iv, &ctxe); @@ -149,117 +215,183 @@ static void cal_mac(uint8_t *session_key, uint8_t *data, uint32_t len, } aes_cbc_encrypt(pad_buf, mac_buf, AES_BLOCK_SIZE, iv, &ctxe); memcpy(mac, mac_buf, 4); + memzero(&ctxe, sizeof(ctxe)); + memzero(iv, sizeof(iv)); + memzero(pad_buf, sizeof(pad_buf)); + memzero(mac_buf, sizeof(mac_buf)); } -static secbool se_transmit_mac_ex(uint8_t addr, uint8_t *session_key, - uint8_t ins, uint8_t p1, uint8_t p2, - uint8_t *data, uint16_t data_len, - uint8_t *recv, uint16_t *recv_len) { - uint8_t mac[4], iv_random[16]; - uint16_t pad_len; +static se_secure_response_result_t se_transmit_mac_result_ex( + uint8_t addr, uint8_t *session_key, uint8_t ins, uint8_t p1, uint8_t p2, + uint8_t *data, uint16_t data_len, uint8_t *recv, uint16_t *recv_len, + uint16_t *response_status) { + uint8_t *mac_key = NULL; + uint8_t mac[4] = {0}; + uint8_t iv_random[16] = {0}; + uint8_t request_header[4] = {0}; + uint16_t pad_len = 0; + uint16_t sw1sw2 = 0; + se_secure_response_result_t result = SE_SECURE_RESPONSE_INVALID; + + if (response_status != NULL) { + *response_status = 0; + } + if (se_session_is_initialized(addr, session_key) != sectrue) { + goto cleanup; + } + mac_key = se_get_session_mac_key(session_key); + if (mac_key == NULL) { + goto cleanup; + } + APDU_CLA = 0x84; APDU_INS = ins; APDU_P1 = p1; APDU_P2 = p2; APDU_P3 = 0x00; - - memset(iv_random, 0x00, sizeof(iv_random)); + memcpy(request_header, APDU, sizeof(request_header)); if (!se_random_encrypted_ex(addr, session_key, iv_random, 16)) { - ensure(secfalse, "se_random_encrypted_ex failed"); - } - - if (data != NULL && data_len != 0) { - pad_len = AES_BLOCK_SIZE - (data_len % AES_BLOCK_SIZE); - memset(APDU_DATA + data_len, 0x00, pad_len); - APDU_DATA[data_len] = 0x80; - data_len += pad_len; - // header + data + mac - if (data_len > SE_BUF_MAX_LEN - 7 - 4) { - ensure(secfalse, "data_len too long"); - } - - memmove(APDU_DATA, data, data_len - pad_len); - - aes_encrypt_ctx ctxe; - uint8_t iv[16]; - memcpy(iv, iv_random, 16); - aes_encrypt_key128(session_key, &ctxe); - aes_cbc_encrypt(APDU_DATA, se_recv_buffer, data_len, iv, &ctxe); - - if (data_len > 255) { - APDU_P3 = 0x00; - APDU_DATA[0] = (data_len >> 8) & 0xFF; - APDU_DATA[1] = data_len & 0xFF; - data_len += 7; - memcpy(APDU_DATA + 2, se_recv_buffer, data_len); - - } else { - APDU_P3 = data_len & 0xFF; - data_len += 5; - memcpy(APDU_DATA, se_recv_buffer, data_len); + if ((thd89_last_error() & 0xff00) != 0x6c00) { + se_invalidate_session(addr); } + goto cleanup; + } + + uint16_t plaintext_len = data_len; + pad_len = AES_BLOCK_SIZE - (plaintext_len % AES_BLOCK_SIZE); + data_len = plaintext_len + pad_len; + // header + data + mac + if (data_len > SE_BUF_MAX_LEN - 7 - 4) { + goto cleanup; + } + + if (data != NULL && plaintext_len != 0) { + memmove(APDU_DATA, data, plaintext_len); + } else if (plaintext_len != 0) { + goto cleanup; + } + memset(APDU_DATA + plaintext_len, 0x00, pad_len); + APDU_DATA[plaintext_len] = 0x80; + + aes_encrypt_ctx ctxe = {0}; + uint8_t iv[16] = {0}; + memcpy(iv, iv_random, 16); + if (aes_encrypt_key128(session_key, &ctxe) != EXIT_SUCCESS || + aes_cbc_encrypt(APDU_DATA, se_recv_buffer, data_len, iv, &ctxe) != + EXIT_SUCCESS) { + memzero(&ctxe, sizeof(ctxe)); + memzero(iv, sizeof(iv)); + se_invalidate_session(addr); + goto cleanup; + } + memzero(&ctxe, sizeof(ctxe)); + memzero(iv, sizeof(iv)); + uint16_t enc_data_len = data_len; + + if (enc_data_len > 255) { + APDU_P3 = 0x00; + APDU_DATA[0] = (enc_data_len >> 8) & 0xFF; + APDU_DATA[1] = enc_data_len & 0xFF; + memcpy(APDU_DATA + 2, se_recv_buffer, enc_data_len); + data_len = enc_data_len + 7; - cal_mac(session_key, APDU, data_len, mac); - memcpy(APDU + data_len, mac, 4); - data_len += 4; } else { - data_len = 5; + APDU_P3 = enc_data_len & 0xFF; + memcpy(APDU_DATA, se_recv_buffer, enc_data_len); + data_len = enc_data_len + 5; } + + cal_mac(mac_key, iv_random, APDU, data_len, mac); + memcpy(APDU + data_len, mac, 4); + data_len += 4; se_recv_len = sizeof(se_recv_buffer); - if (!thd89_transmit_ex(addr, APDU, data_len, se_recv_buffer, &se_recv_len)) { - memset(APDU, 0x00, sizeof(APDU)); - return secfalse; + if (thd89_transmit_raw_ex(addr, APDU, data_len, se_recv_buffer, &se_recv_len, + &sw1sw2) != sectrue) { + se_invalidate_session(addr); + goto cleanup; + } + if (response_status != NULL) { + *response_status = sw1sw2; } - if (se_recv_len) { - if ((se_recv_len - 4) % AES_BLOCK_SIZE) { - ensure(secfalse, "se_recv_len error"); - } - cal_mac(session_key, se_recv_buffer, se_recv_len - 4, mac); - if (memcmp(mac, se_recv_buffer + se_recv_len - 4, 4) != 0) { - ensure(secfalse, "se_recv_buffer mac error"); - } + thd89_v2_response_shape_t shape = + thd89_v2_classify_response(se_recv_len, sw1sw2); + if (shape == THD89_V2_RESPONSE_NO_MAC_6C) { + result = SE_SECURE_RESPONSE_NO_MAC_6C; + goto cleanup; + } + if (shape != THD89_V2_RESPONSE_MAC_REQUIRED) { + se_invalidate_session(addr); + goto cleanup; + } - se_recv_len -= 4; - - aes_decrypt_ctx dtxe; - uint8_t iv[16]; - memcpy(iv, iv_random, 16); - aes_decrypt_key128(session_key, &dtxe); - aes_cbc_decrypt(se_recv_buffer, APDU, se_recv_len, iv, &dtxe); - pad_len = 1; - for (uint8_t i = 0; i < 16; i++) { - if (APDU[se_recv_len - 1 - i] == 0x80) { - break; - } else if (APDU[se_recv_len - 1 - i] == 0x00) { - pad_len++; - } else { - memset(APDU, 0x00, sizeof(APDU)); - ensure(secfalse, "se_recv_buffer pad error"); - } - } - se_recv_len -= pad_len; + uint16_t ciphertext_len = se_recv_len - 4; + thd89_v2_calculate_response_mac(mac_key, request_header, iv_random, + se_recv_buffer, ciphertext_len, sw1sw2, mac); + if (!thd89_v2_constant_time_equal(mac, se_recv_buffer + ciphertext_len, 4)) { + se_invalidate_session(addr); + goto cleanup; + } - if (recv_len == NULL) { - ensure(secfalse, "recv_len is NULL"); - } + if (sw1sw2 != 0x9000) { + result = SE_SECURE_RESPONSE_AUTHENTICATED_ERROR; + goto cleanup; + } - if (*recv_len < se_recv_len) { - memset(APDU, 0x00, sizeof(APDU)); - ensure(secfalse, "recv_len too short"); - } - *recv_len = se_recv_len; - if (recv) { - memcpy(recv, APDU, *recv_len); - } - } else { + if (ciphertext_len == 0) { if (recv_len != NULL) { *recv_len = 0; } - } - memset(APDU, 0x00, sizeof(APDU)); - return sectrue; + result = SE_SECURE_RESPONSE_OK; + goto cleanup; + } + + aes_decrypt_ctx dtxe = {0}; + memcpy(iv, iv_random, sizeof(iv)); + if (aes_decrypt_key128(session_key, &dtxe) != EXIT_SUCCESS || + aes_cbc_decrypt(se_recv_buffer, APDU, ciphertext_len, iv, &dtxe) != + EXIT_SUCCESS) { + memzero(&dtxe, sizeof(dtxe)); + memzero(iv, sizeof(iv)); + se_invalidate_session(addr); + goto cleanup; + } + memzero(&dtxe, sizeof(dtxe)); + memzero(iv, sizeof(iv)); + + uint16_t unpadded_len = 0; + if (!thd89_v2_unpad_iso7816_4(APDU, ciphertext_len, &unpadded_len)) { + se_invalidate_session(addr); + goto cleanup; + } + if (recv_len == NULL || (unpadded_len != 0 && recv == NULL) || + *recv_len < unpadded_len) { + goto cleanup; + } + *recv_len = unpadded_len; + if (unpadded_len != 0) { + memcpy(recv, APDU, unpadded_len); + } + result = SE_SECURE_RESPONSE_OK; + +cleanup: + memzero(mac, sizeof(mac)); + memzero(iv_random, sizeof(iv_random)); + memzero(request_header, sizeof(request_header)); + memzero(se_send_buffer, sizeof(se_send_buffer)); + memzero(se_recv_buffer, sizeof(se_recv_buffer)); + se_recv_len = 0; + return result; +} + +static secbool se_transmit_mac_ex(uint8_t addr, uint8_t *session_key, + uint8_t ins, uint8_t p1, uint8_t p2, + uint8_t *data, uint16_t data_len, + uint8_t *recv, uint16_t *recv_len) { + return sectrue * (se_transmit_mac_result_ex(addr, session_key, ins, p1, p2, + data, data_len, recv, recv_len, + NULL) == SE_SECURE_RESPONSE_OK); } secbool se_transmit_mac(uint8_t ins, uint8_t p1, uint8_t p2, uint8_t *data, @@ -303,115 +435,94 @@ secbool se_random_encrypted(uint8_t *rand, uint16_t len) { secbool se_random_encrypted_ex(uint8_t addr, uint8_t *session_key, uint8_t *rand, uint16_t len) { + uint8_t *mac_key = NULL; uint16_t recv_len = SE_BUF_MAX_LEN; uint8_t cmd[7] = {0xa4, 0x84, 0x00, 0x00, 0x02}; - uint8_t mac[4]; - uint8_t pad_len; - secbool ret; - cmd[5] = (len >> 8) & 0xff; - cmd[6] = len & 0xff; + uint8_t mac[4] = {0}; + uint8_t transaction[16] = {0}; + uint16_t sw1sw2 = 0; + secbool ret = secfalse; - for (int retry = 0; retry < 3; retry++) { - recv_len = SE_BUF_MAX_LEN; - ret = thd89_transmit_ex(addr, cmd, sizeof(cmd), se_recv_buffer, &recv_len); - if (ret == sectrue) { - break; - } + if (se_session_is_initialized(addr, session_key) != sectrue) { + return secfalse; + } + mac_key = se_get_session_mac_key(session_key); + if (mac_key == NULL) { + return secfalse; } - ensure(ret, "thd89_transmit_ex failed"); - - if (recv_len) { - if ((recv_len - 4) % AES_BLOCK_SIZE) { - ensure(secfalse, "recv_len error"); - } + if (rand == NULL && len != 0) { + return secfalse; + } + cmd[5] = (len >> 8) & 0xff; + cmd[6] = len & 0xff; - cal_mac(session_key, se_recv_buffer, recv_len - 4, mac); - if (memcmp(mac, se_recv_buffer + recv_len - 4, 4) != 0) { - ensure(secfalse, "mac error"); - } + if (thd89_transmit_raw_ex(addr, cmd, sizeof(cmd), se_recv_buffer, &recv_len, + &sw1sw2) != sectrue) { + se_invalidate_session(addr); + goto cleanup; + } - recv_len -= 4; - - aes_decrypt_ctx dtxe; - aes_decrypt_key128(session_key, &dtxe); - aes_ecb_decrypt(se_recv_buffer, se_recv_buffer, recv_len, &dtxe); - pad_len = 1; - for (uint8_t i = 0; i < 16; i++) { - if (se_recv_buffer[recv_len - 1 - i] == 0x80) { - break; - } else if (se_recv_buffer[recv_len - 1 - i] == 0x00) { - pad_len++; - } else { - ensure(secfalse, "pad error"); - } - } - recv_len -= pad_len; + thd89_v2_response_shape_t shape = + thd89_v2_classify_response(recv_len, sw1sw2); + if (shape == THD89_V2_RESPONSE_NO_MAC_6C) { + goto cleanup; + } + if (shape != THD89_V2_RESPONSE_MAC_REQUIRED) { + se_invalidate_session(addr); + goto cleanup; + } - if (recv_len != len) { - ensure(secfalse, "recv_len error"); + uint16_t ciphertext_len = recv_len - 4; + thd89_v2_calculate_response_mac(mac_key, cmd, transaction, se_recv_buffer, + ciphertext_len, sw1sw2, mac); + if (!thd89_v2_constant_time_equal(mac, se_recv_buffer + ciphertext_len, 4)) { + se_invalidate_session(addr); + goto cleanup; + } + if (sw1sw2 != 0x9000) { + goto cleanup; + } + if (ciphertext_len == 0) { + if (len == 0) { + ret = sectrue; } + goto cleanup; } - memcpy(rand, se_recv_buffer, recv_len); - - return sectrue; -} - -secbool se_sync_session_key_ex_old(uint8_t addr, uint8_t *session_key) { - uint8_t r1[16], r2[16], r3[32]; - uint8_t default_key[16] = {0xff}; - - memset(default_key, 0xff, 16); - uint8_t data_buf[64], hash_buf[32]; - uint8_t sync_cmd[5 + 48] = {0x00, 0xfa, 0x00, 0x00, 0x30}; - uint16_t recv_len = sizeof(data_buf); - aes_encrypt_ctx en_ctxe; - aes_decrypt_ctx de_ctxe; - memzero(data_buf, sizeof(data_buf)); - ensure(flash_otp_read(FLASH_OTP_BLOCK_THD89_SESSION_KEY, 0, default_key, 16), - NULL); - - // get random from se - se_get_rand_ex(addr, r1, 16); - // get random itself - random_buffer(r2, 16); - // organization data1 - memcpy(r3, r1, sizeof(r1)); - memcpy(r3 + sizeof(r1), r2, sizeof(r2)); - aes_init(); - aes_encrypt_key128(default_key, &en_ctxe); - aes_ecb_encrypt(r3, data_buf, sizeof(r1) + sizeof(r2), &en_ctxe); - - // cal tmp sessionkey with x hash256 - memzero(r3, sizeof(r3)); - xor_cal(r1, r2, sizeof(r1), r3); - memcpy(r3 + 16, default_key, 16); - sha256_Raw(r3, 32, hash_buf); - // use session key organization data2 - memcpy(session_key, hash_buf, 16); - aes_encrypt_key128(session_key, &en_ctxe); - aes_ecb_encrypt(r1, data_buf + 32, sizeof(r1), &en_ctxe); - // send data1 + data2 to se and recv returned result - memcpy(sync_cmd + 5, data_buf, 48); - if (!thd89_transmit_ex(addr, sync_cmd, sizeof(sync_cmd), data_buf, - &recv_len)) { - memset(session_key, 0x00, SESSION_KEYLEN); - return secfalse; + aes_decrypt_ctx dtxe = {0}; + if (aes_decrypt_key128(session_key, &dtxe) != EXIT_SUCCESS || + aes_ecb_decrypt(se_recv_buffer, se_recv_buffer, ciphertext_len, &dtxe) != + EXIT_SUCCESS) { + memzero(&dtxe, sizeof(dtxe)); + se_invalidate_session(addr); + goto cleanup; } + memzero(&dtxe, sizeof(dtxe)); - // handle the returned data - aes_decrypt_key128(session_key, &de_ctxe); - aes_ecb_decrypt(data_buf, r3, recv_len, &de_ctxe); - if (memcmp(r2, r3, sizeof(r2)) != 0) { - memset(session_key, 0x00, SESSION_KEYLEN); - return secfalse; + uint16_t plaintext_len = 0; + if (!thd89_v2_unpad_iso7816_4(se_recv_buffer, ciphertext_len, + &plaintext_len)) { + se_invalidate_session(addr); + goto cleanup; } + if (plaintext_len != len) { + se_invalidate_session(addr); + goto cleanup; + } + if (len != 0) { + memcpy(rand, se_recv_buffer, len); + } + ret = sectrue; - return sectrue; +cleanup: + memzero(mac, sizeof(mac)); + memzero(transaction, sizeof(transaction)); + memzero(se_recv_buffer, sizeof(se_recv_buffer)); + return ret; } -static void get_pubkey(uint8_t addr, uint8_t *pubkey) { +static secbool get_pubkey(uint8_t addr, uint8_t *pubkey) { uint8_t otp_pubkey_1, otp_pubkey_2; switch (addr) { case THD89_MASTER_ADDRESS: @@ -431,65 +542,130 @@ static void get_pubkey(uint8_t addr, uint8_t *pubkey) { otp_pubkey_2 = FLASH_OTP_BLOCK_THD89_4_PUBKEY2; break; default: - return; + return secfalse; + } + if (flash_otp_read(otp_pubkey_1, 0, pubkey, 32) != sectrue || + flash_otp_read(otp_pubkey_2, 0, pubkey + 32, 32) != sectrue) { + return secfalse; } - ensure(flash_otp_read(otp_pubkey_1, 0, pubkey, 32), NULL); - ensure(flash_otp_read(otp_pubkey_2, 0, pubkey + 32, 32), NULL); + return sectrue; } -static secbool se_sync_session_key_ex(uint8_t addr, uint8_t *session_key) { - uint8_t pubkey[65], session_tmp[65]; - uint8_t prikey_tmp[32], pubkey_tmp[65]; - uint8_t r1[16], r2[16], r2_enc[16]; - uint8_t digest[32]; - uint16_t recv_len = 64; - aes_encrypt_ctx en_ctxe; - - pubkey[0] = 0x04; - get_pubkey(addr, pubkey + 1); - - random_buffer(r1, 16); - // get random from se - se_get_rand_ex(addr, r2, 16); +static secbool se_get_session_random_ex(uint8_t addr, uint8_t se_random[16]) { + uint8_t cmd[7] = {0x00, 0x84, 0x00, 0x00, 0x02, 0x00, 0x10}; + uint16_t recv_len = 16; + uint16_t sw1sw2 = 0; - random_buffer(prikey_tmp, sizeof(prikey_tmp)); - ecdsa_get_public_key65(&secp256k1, prikey_tmp, pubkey_tmp); - - if (ecdh_multiply(&secp256k1, prikey_tmp, pubkey, session_tmp) != 0) { + if (thd89_transmit_raw_ex(addr, cmd, sizeof(cmd), se_random, &recv_len, + &sw1sw2) != sectrue) { return secfalse; } + return sectrue * (sw1sw2 == 0x9000 && recv_len == 16); +} - memcpy(session_key, session_tmp + 1, 16); - - aes_init(); - aes_encrypt_key128(session_key, &en_ctxe); - aes_ecb_encrypt(r2, r2_enc, sizeof(r2), &en_ctxe); - - uint8_t sync_cmd[5 + 16 + 16 + 64] = {0x00, 0xfa, 0x00, 0x00, 0x60}; - uint8_t signature[64]; +static secbool se_sync_session_key_ex(uint8_t addr, uint8_t *session_key, + uint8_t *session_mac_key) { + uint8_t se_public_key[65] = {0}; + uint8_t ephemeral_private_key[32] = {0}; + uint8_t ephemeral_public_key[65] = {0}; + uint8_t shared_point[65] = {0}; + uint8_t se_random[16] = {0}; + uint8_t mcu_random[16] = {0}; + uint8_t candidate_enc_key[16] = {0}; + uint8_t candidate_mac_key[16] = {0}; + uint8_t confirm_key[32] = {0}; + uint8_t encrypted_challenge[16] = {0}; + uint8_t request_data[96] = {0}; + uint8_t sync_cmd[5 + 96] = {0x00, 0xfa, 0x01, 0x00, 0x60}; + uint8_t confirmation[32] = {0}; + uint8_t expected_confirmation[32] = {0}; + uint16_t recv_len = sizeof(confirmation); + uint16_t sw1sw2 = 0; + aes_encrypt_ctx en_ctxe = {0}; + secbool success = secfalse; - memcpy(sync_cmd + 5, r1, 16); - memcpy(sync_cmd + 5 + 16, r2_enc, 16); - memcpy(sync_cmd + 5 + 32, pubkey_tmp + 1, 64); - if (!thd89_transmit_ex(addr, sync_cmd, sizeof(sync_cmd), signature, - &recv_len)) { - memset(session_key, 0x00, SESSION_KEYLEN); - return secfalse; + se_invalidate_session(addr); + se_public_key[0] = 0x04; + if (get_pubkey(addr, se_public_key + 1) != sectrue || + se_get_session_random_ex(addr, se_random) != sectrue) { + goto cleanup; } - if (recv_len != 64) { - memset(session_key, 0x00, SESSION_KEYLEN); - return secfalse; + + random_buffer(mcu_random, sizeof(mcu_random)); + for (uint8_t attempt = 0; attempt < 16; attempt++) { + random_buffer(ephemeral_private_key, sizeof(ephemeral_private_key)); + if (ecdsa_get_public_key65(&secp256k1, ephemeral_private_key, + ephemeral_public_key) == 0) { + break; + } + memzero(ephemeral_private_key, sizeof(ephemeral_private_key)); } - sha256_Raw(r1, 16, digest); - if (ecdsa_verify_digest(&secp256k1, pubkey, signature, digest) != 0) { - return secfalse; + if (ephemeral_public_key[0] != 0x04 || + ecdh_multiply(&secp256k1, ephemeral_private_key, se_public_key, + shared_point) != 0 || + shared_point[0] != 0x04) { + goto cleanup; } - return sectrue; + thd89_v2_derive_session_keys(shared_point + 1, se_random, mcu_random, + candidate_enc_key, candidate_mac_key, + confirm_key); + + aes_init(); + if (aes_encrypt_key128(candidate_enc_key, &en_ctxe) != EXIT_SUCCESS || + aes_ecb_encrypt(se_random, encrypted_challenge, + sizeof(encrypted_challenge), &en_ctxe) != EXIT_SUCCESS) { + goto cleanup; + } + + memcpy(request_data, mcu_random, sizeof(mcu_random)); + memcpy(request_data + 16, encrypted_challenge, sizeof(encrypted_challenge)); + memcpy(request_data + 32, ephemeral_public_key + 1, 64); + memcpy(sync_cmd + 5, request_data, sizeof(request_data)); + + if (thd89_transmit_raw_ex(addr, sync_cmd, sizeof(sync_cmd), confirmation, + &recv_len, &sw1sw2) != sectrue || + sw1sw2 != 0x9000 || recv_len != sizeof(confirmation)) { + goto cleanup; + } + + thd89_v2_calculate_confirmation(confirm_key, se_random, request_data, + expected_confirmation); + if (!thd89_v2_constant_time_equal(confirmation, expected_confirmation, + sizeof(confirmation))) { + goto cleanup; + } + + memcpy(session_key, candidate_enc_key, SESSION_KEYLEN); + memcpy(session_mac_key, candidate_mac_key, SESSION_KEYLEN); + success = sectrue; + +cleanup: + memzero(&en_ctxe, sizeof(en_ctxe)); + memzero(se_public_key, sizeof(se_public_key)); + memzero(ephemeral_private_key, sizeof(ephemeral_private_key)); + memzero(ephemeral_public_key, sizeof(ephemeral_public_key)); + memzero(shared_point, sizeof(shared_point)); + memzero(se_random, sizeof(se_random)); + memzero(mcu_random, sizeof(mcu_random)); + memzero(candidate_enc_key, sizeof(candidate_enc_key)); + memzero(candidate_mac_key, sizeof(candidate_mac_key)); + memzero(confirm_key, sizeof(confirm_key)); + memzero(encrypted_challenge, sizeof(encrypted_challenge)); + memzero(request_data, sizeof(request_data)); + memzero(sync_cmd, sizeof(sync_cmd)); + memzero(confirmation, sizeof(confirmation)); + memzero(expected_confirmation, sizeof(expected_confirmation)); + if (success != sectrue) { + se_invalidate_session(addr); + } + return success; } static secbool _se_sync_session_key(void) { - if (sectrue == se_sync_session_key_ex(THD89_MASTER_ADDRESS, se_session_key)) { + se_session_init = false; + if (sectrue == se_sync_session_key_ex(THD89_MASTER_ADDRESS, se_session_key, + se_session_mac_key)) { se_session_init = true; return sectrue; } @@ -497,8 +673,9 @@ static secbool _se_sync_session_key(void) { } static secbool _se_fp_sync_session_key(void) { - if (sectrue == - se_sync_session_key_ex(THD89_FINGER_ADDRESS, se_fp_session_key)) { + se_fp_session_init = false; + if (sectrue == se_sync_session_key_ex(THD89_FINGER_ADDRESS, se_fp_session_key, + se_fp_session_mac_key)) { se_fp_session_init = true; return sectrue; } @@ -507,7 +684,10 @@ static secbool _se_fp_sync_session_key(void) { secbool se_sync_session_key(void) { ensure(_se_sync_session_key(), "se sync session key failed"); - ensure(_se_fp_sync_session_key(), "se fp sync session key failed"); + if (_se_fp_sync_session_key() != sectrue) { + se_invalidate_session(THD89_MASTER_ADDRESS); + ensure(secfalse, "se fp sync session key failed"); + } return sectrue; } @@ -752,7 +932,7 @@ char *se01_get_boot_version(void) { } char *se01_get_boot_build_id(void) { - static char build_id[8] = {0}; + static char build_id[16] = {0}; if (strlen(build_id) > 0) { return build_id; } @@ -826,7 +1006,7 @@ char *se02_get_boot_version(void) { } char *se02_get_boot_build_id(void) { - static char build_id[8] = {0}; + static char build_id[16] = {0}; if (strlen(build_id) > 0) { return build_id; } @@ -900,7 +1080,7 @@ char *se03_get_boot_version(void) { } char *se03_get_boot_build_id(void) { - static char build_id[8] = {0}; + static char build_id[16] = {0}; if (strlen(build_id) > 0) { return build_id; } @@ -974,7 +1154,7 @@ char *se04_get_boot_version(void) { } char *se04_get_boot_build_id(void) { - static char build_id[8] = {0}; + static char build_id[16] = {0}; if (strlen(build_id) > 0) { return build_id; } @@ -1000,13 +1180,13 @@ uint8_t *se04_get_boot_hash(void) { } secbool se_get_ecdh_pubkey(uint8_t addr, uint8_t *key) { - uint8_t cmd[6] = {0x00, 0xF5, 0x00, 0x05, 0x01, 0x01}; + uint8_t cmd[5] = {0x00, 0xF5, 0x00, 0x05, 0x00}; uint16_t resp_len = 64; return thd89_transmit_ex(addr, cmd, sizeof(cmd), key, &resp_len); } secbool se_lock_ecdh_pubkey(uint8_t addr) { - uint8_t cmd[6] = {0x00, 0xF5, 0x00, 0x05, 0x01, 0x02}; + uint8_t cmd[5] = {0x00, 0xF5, 0x00, 0x06, 0x00}; return thd89_transmit_ex(addr, cmd, sizeof(cmd), NULL, NULL); } @@ -1174,11 +1354,17 @@ secbool se_setPin(const char *pin) { } static secbool se_verifyPin_ex(uint8_t addr, uint8_t *session_key, - const char *pin, pin_type_t pin_type) { + const char *pin, pin_type_t pin_type, + uint16_t *response_status) { uint8_t pin_buf[50 + 2] = {0}; uint8_t resp[1] = {0}; uint16_t resp_len = 1; uint8_t data_len = 0; + uint16_t status = 0; + + if (response_status != NULL) { + *response_status = 0; + } if (strlen(pin) > PIN_MAX_LEN) { return secfalse; @@ -1196,10 +1382,17 @@ static secbool se_verifyPin_ex(uint8_t addr, uint8_t *session_key, pin_buf[pin_buf[0] + 1] = pin_type; data_len++; - if (!se_transmit_mac_ex(addr, session_key, SE_INS_PIN, 0x00, 0x03, pin_buf, - data_len, resp, &resp_len)) { + se_secure_response_result_t transmit_result = + se_transmit_mac_result_ex(addr, session_key, SE_INS_PIN, 0x00, 0x03, + pin_buf, data_len, resp, &resp_len, &status); + if (transmit_result != SE_SECURE_RESPONSE_OK) { memset(pin_buf, 0, sizeof(pin_buf)); - if (0x6f80 == thd89_last_error()) { + if (transmit_result == SE_SECURE_RESPONSE_AUTHENTICATED_ERROR && + response_status != NULL) { + *response_status = status; + } + if (transmit_result == SE_SECURE_RESPONSE_AUTHENTICATED_ERROR && + status == 0x6f80) { error_reset("You have entered the", "wipe code. All private", "data has been erased.", NULL); } @@ -1220,7 +1413,7 @@ static secbool se_verifyPin_ex(uint8_t addr, uint8_t *session_key, static secbool se_fp_verifyPin(const char *pin) { return se_verifyPin_ex(THD89_FINGER_ADDRESS, se_fp_session_key, pin, - PIN_TYPE_USER); + PIN_TYPE_USER, NULL); } static void reset_storage_and_restart(void) { error_pin_max_prompt(); @@ -1231,15 +1424,21 @@ static void reset_storage_and_restart(void) { restart(); } secbool se_verifyPin(const char *pin, pin_type_t pin_type) { - secbool result = - se_verifyPin_ex(THD89_MASTER_ADDRESS, se_session_key, pin, pin_type); + uint16_t response_status = 0; + secbool result = se_verifyPin_ex(THD89_MASTER_ADDRESS, se_session_key, pin, + pin_type, &response_status); if (result == sectrue) { if (pin_type != PIN_TYPE_PASSPHRASE_PIN_CHECK) { - secbool fp_result = se_verifyPin_ex(THD89_FINGER_ADDRESS, - se_fp_session_key, pin, pin_type); + uint16_t fp_response_status = 0; + secbool fp_result = + se_verifyPin_ex(THD89_FINGER_ADDRESS, se_fp_session_key, pin, + pin_type, &fp_response_status); if (fp_result == sectrue) { return sectrue; } + if (fp_response_status == SE_SW_PIN_RETRY_LIMIT_REACHED) { + reset_storage_and_restart(); + } // else { // if (se_fp_hasPin()) { // ensure(se_fp_reset_storage(), "reset fp storage failed"); @@ -1253,6 +1452,9 @@ secbool se_verifyPin(const char *pin, pin_type_t pin_type) { return sectrue; } } else { + if (response_status == SE_SW_PIN_RETRY_LIMIT_REACHED) { + reset_storage_and_restart(); + } uint8_t retry_cnts = 0; ensure(se_getRetryTimes(&retry_cnts), "get retry times failed"); if (retry_cnts == 0) { @@ -1367,6 +1569,7 @@ static secbool se_set_pin_passphrase_ex(uint8_t addr, uint8_t *session_key, uint8_t buf[2 * PIN_MAX_LENGTH + PASSPHRASE_MAX_LENGTH + 3]; uint8_t resp[2]; uint16_t resp_len = 2; + uint16_t sw1sw2 = 0; uint32_t offset = 0; buf[offset++] = strlen(pin); @@ -1379,14 +1582,18 @@ static secbool se_set_pin_passphrase_ex(uint8_t addr, uint8_t *session_key, memcpy(buf + offset, (uint8_t *)passphrase, strlen(passphrase)); offset += strlen(passphrase); - if (!se_transmit_mac_ex(addr, session_key, SE_INS_PIN, 0x00, 0x09, buf, - offset, resp, &resp_len)) { - if (thd89_last_error() == 0x6c00) { - percent = 0; - resp[0] = PIN_SUCCESS; - } else { - return secfalse; - } + *se_get_pending_operation(addr) = SE_LONG_OPERATION_NONE; + se_secure_response_result_t result = + se_transmit_mac_result_ex(addr, session_key, SE_INS_PIN, 0x00, 0x09, buf, + offset, resp, &resp_len, &sw1sw2); + if (result == SE_SECURE_RESPONSE_NO_MAC_6C && (sw1sw2 & 0xff) <= 100) { + *se_get_pending_operation(addr) = SE_LONG_OPERATION_SET_PASSPHRASE_PIN; + percent = (sw1sw2 & 0xff) == 100 ? 99 : (sw1sw2 & 0xff); + resp[0] = PIN_SUCCESS; + } else if (result == SE_SECURE_RESPONSE_OK) { + percent = 100; + } else { + return secfalse; } if (resp[0] != PIN_SUCCESS) { pin_passphrase_ret = resp[0]; @@ -1397,16 +1604,18 @@ static secbool se_set_pin_passphrase_ex(uint8_t addr, uint8_t *session_key, if (ui_callback) { ui_callback(0, percent * 10, NULL); } - if (!session_generate_seed_percent(&percent)) { + if (!se_query_progress_percent_ex(addr, &percent)) { return secfalse; } hal_delay(100); } resp_len = 1; - *override = true; - if (se_transmit_mac(SE_INS_PIN, 0x00, 0x0D, NULL, 0, resp, &resp_len)) { - *override = resp[0] ? true : false; + if (se_transmit_mac_ex(addr, session_key, SE_INS_PIN, 0x00, 0x0D, NULL, 0, + resp, &resp_len) != sectrue || + resp_len != 1u) { + return secfalse; } + *override = resp[0] ? true : false; return sectrue; } @@ -1570,12 +1779,9 @@ secbool se_getSecsta(void) { } secbool se_set_u2f_counter(uint32_t u2fcounter) { - uint8_t cmd[9] = {0x00, SE_INS_FIDO, 0x00, SE_FIDO_SET_COUNTER, 0x04}; - uint16_t recv_len = 0; - - memcpy(cmd + 5, &u2fcounter, 4); - - if (!thd89_transmit(cmd, sizeof(cmd), NULL, &recv_len)) { + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SET_COUNTER, + (uint8_t *)&u2fcounter, sizeof(u2fcounter), NULL, + NULL)) { return secfalse; } @@ -1583,9 +1789,9 @@ secbool se_set_u2f_counter(uint32_t u2fcounter) { } secbool se_get_u2f_counter(uint32_t *u2fcounter) { - uint8_t cmd[5] = {0x00, SE_INS_FIDO, 0x00, SE_FIDO_NEXT_COUNTER, 0x00}; uint16_t recv_len = 4; - if (!thd89_transmit(cmd, sizeof(cmd), (uint8_t *)u2fcounter, &recv_len)) { + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_NEXT_COUNTER, NULL, 0, + (uint8_t *)u2fcounter, &recv_len)) { return secfalse; } return sectrue; @@ -1937,12 +2143,18 @@ secbool se_session_is_open() { } secbool session_generate_master_seed(const char *passphrase, uint8_t *percent) { - if (!se_transmit_mac(SE_INS_SESSION, 0x00, 0x05, (uint8_t *)passphrase, - strlen(passphrase), NULL, NULL)) { - if (thd89_last_error() == 0x6c00) { - *percent = 0; - return sectrue; - } + uint16_t sw1sw2 = 0; + se_pending_operation = SE_LONG_OPERATION_NONE; + se_secure_response_result_t result = se_transmit_mac_result_ex( + THD89_MASTER_ADDRESS, se_session_key, SE_INS_SESSION, 0x00, 0x05, + (uint8_t *)passphrase, strlen(passphrase), NULL, NULL, &sw1sw2); + + if (result == SE_SECURE_RESPONSE_NO_MAC_6C && (sw1sw2 & 0xff) <= 100) { + se_pending_operation = SE_LONG_OPERATION_SESSION_SEED; + *percent = (sw1sw2 & 0xff) == 100 ? 99 : (sw1sw2 & 0xff); + return sectrue; + } + if (result != SE_SECURE_RESPONSE_OK) { return secfalse; } *percent = 100; @@ -1951,34 +2163,56 @@ secbool session_generate_master_seed(const char *passphrase, uint8_t *percent) { secbool session_generate_cardano_seed(const char *passphrase, uint8_t *percent) { - if (!se_transmit_mac(SE_INS_SESSION, 0x00, 0x06, (uint8_t *)passphrase, - strlen(passphrase), NULL, NULL)) { - if (thd89_last_error() == 0x6c00) { - *percent = 0; - return sectrue; - } + uint16_t sw1sw2 = 0; + se_pending_operation = SE_LONG_OPERATION_NONE; + se_secure_response_result_t result = se_transmit_mac_result_ex( + THD89_MASTER_ADDRESS, se_session_key, SE_INS_SESSION, 0x00, 0x06, + (uint8_t *)passphrase, strlen(passphrase), NULL, NULL, &sw1sw2); + + if (result == SE_SECURE_RESPONSE_NO_MAC_6C && (sw1sw2 & 0xff) <= 100) { + se_pending_operation = SE_LONG_OPERATION_CARDANO_SEED; + *percent = (sw1sw2 & 0xff) == 100 ? 99 : (sw1sw2 & 0xff); + return sectrue; + } + if (result != SE_SECURE_RESPONSE_OK) { return secfalse; } *percent = 100; return sectrue; } -secbool session_generate_seed_percent(uint8_t *percent) { - uint8_t cmd[5] = {0x80, SE_INS_SESSION, 0x00, 0x08, 0x00}; - uint16_t recv_len; - uint16_t sw1sw2; +static secbool se_query_progress_percent_ex(uint8_t addr, uint8_t *percent) { + uint8_t cmd[5] = {0x80, SE_INS_GET_STATE, 0x00, 0x08, 0x00}; + uint16_t recv_len = 0; + uint16_t sw1sw2 = 0; + se_long_operation_t *pending_operation = se_get_pending_operation(addr); - if (!thd89_transmit(cmd, sizeof(cmd), percent, &recv_len)) { - sw1sw2 = thd89_last_error(); - if ((sw1sw2 & 0xff00) == 0x6c00) { - *percent = sw1sw2 & 0xff; - *percent = *percent == 100 ? 99 : *percent; - return sectrue; - } + if (percent == NULL || *pending_operation == SE_LONG_OPERATION_NONE) { return secfalse; } - *percent = 100; - return sectrue; + + if (thd89_transmit_raw_ex(addr, cmd, sizeof(cmd), NULL, &recv_len, &sw1sw2) != + sectrue || + recv_len != 0) { + *pending_operation = SE_LONG_OPERATION_NONE; + return secfalse; + } + if (sw1sw2 == 0x9000) { + *percent = 100; + *pending_operation = SE_LONG_OPERATION_NONE; + return sectrue; + } + if ((sw1sw2 & 0xff00) == 0x6c00 && (sw1sw2 & 0xff) <= 100) { + *percent = (sw1sw2 & 0xff) == 100 ? 99 : (sw1sw2 & 0xff); + return sectrue; + } + + *pending_operation = SE_LONG_OPERATION_NONE; + return secfalse; +} + +secbool se_query_progress_percent(uint8_t *percent) { + return se_query_progress_percent_ex(THD89_MASTER_ADDRESS, percent); } uint8_t *se_session_startSession(const uint8_t *received_session_id) { @@ -2056,7 +2290,7 @@ secbool se_gen_session_seed(const char *passphrase, bool cardano) { if (ui_callback) { ui_callback(0, percent * 10, NULL); } - if (!session_generate_seed_percent(&percent)) { + if (!se_query_progress_percent(&percent)) { return secfalse; } hal_delay(100); @@ -2078,7 +2312,7 @@ secbool se_gen_session_seed(const char *passphrase, bool cardano) { if (ui_callback) { ui_callback(0, percent * 10, NULL); } - if (!session_generate_seed_percent(&percent)) { + if (!se_query_progress_percent(&percent)) { return secfalse; } hal_delay(100); @@ -2502,36 +2736,49 @@ secbool se_fp_read(uint32_t offset, void *val_dest, uint32_t len, uint8_t index, } secbool se_gen_fido_seed(uint8_t *percent) { - uint8_t cmd[5] = {0x00, 0xf9, 0x00, 0x00, 0x00}; - uint16_t recv_len = 0; - uint16_t sw1sw2; + if (percent == NULL) { + return secfalse; + } - if (!thd89_transmit(cmd, sizeof(cmd), NULL, &recv_len)) { - sw1sw2 = thd89_last_error(); - if ((sw1sw2 & 0xff00) == 0x6c00) { - *percent = sw1sw2 & 0xff; - if (ui_callback) { - ui_callback(0, *percent * 10, NULL); - } + if (se_pending_operation != SE_LONG_OPERATION_FIDO_SEED) { + if (se_pending_operation != SE_LONG_OPERATION_NONE) { + return secfalse; + } + uint16_t sw1sw2 = 0; + se_secure_response_result_t result = se_transmit_mac_result_ex( + THD89_MASTER_ADDRESS, se_session_key, SE_INS_FIDO, 0x00, + SE_FIDO_GEN_SEED, NULL, 0, NULL, NULL, &sw1sw2); + if (result == SE_SECURE_RESPONSE_OK) { + *percent = 100; return sectrue; } + if (result != SE_SECURE_RESPONSE_NO_MAC_6C || (sw1sw2 & 0xff) > 100) { + return secfalse; + } + se_pending_operation = SE_LONG_OPERATION_FIDO_SEED; + *percent = (sw1sw2 & 0xff) == 100 ? 99 : (sw1sw2 & 0xff); + } + + if (!se_query_progress_percent(percent)) { return secfalse; } - *percent = 100; + if (ui_callback) { + ui_callback(0, *percent * 10, NULL); + } return sectrue; } secbool se_u2f_register(const uint8_t app_id[32], const uint8_t challenge[32], uint8_t key_handle[64], uint8_t pub_key[65], uint8_t sign[64]) { - uint8_t cmd[128] = {0x00, SE_INS_FIDO, 0x00, SE_FIDO_U2F_REGISTER}; + uint8_t data[64]; uint8_t recv[256]; uint16_t recv_len = sizeof(recv); - memcpy(cmd + 5, app_id, 32); - memcpy(cmd + 5 + 32, challenge, 32); + memcpy(data, app_id, 32); + memcpy(data + 32, challenge, 32); - cmd[4] = 64; - if (!thd89_transmit(cmd, 5 + 64, (uint8_t *)recv, &recv_len)) { + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_U2F_REGISTER, data, + sizeof(data), (uint8_t *)recv, &recv_len)) { return secfalse; } @@ -2547,13 +2794,11 @@ secbool se_u2f_register(const uint8_t app_id[32], const uint8_t challenge[32], secbool se_u2f_gen_handle_and_node(const uint8_t app_id[32], uint8_t key_handle[64], HDNode *out) { - uint8_t cmd[128] = {0x00, SE_INS_FIDO, 0x00, SE_FIDO_U2F_GEN_HANDLE}; uint8_t recv[256]; uint16_t recv_len = sizeof(recv); - memcpy(cmd + 5, app_id, 32); - cmd[4] = 32; - if (!thd89_transmit(cmd, 5 + 32, (uint8_t *)recv, &recv_len)) { + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_U2F_GEN_HANDLE, + (uint8_t *)app_id, 32, (uint8_t *)recv, &recv_len)) { return secfalse; } @@ -2569,13 +2814,13 @@ secbool se_u2f_gen_handle_and_node(const uint8_t app_id[32], secbool se_u2f_validate_handle(const uint8_t app_id[32], const uint8_t key_handle[64]) { - uint8_t cmd[128] = {0x00, SE_INS_FIDO, 0x00, SE_FIDO_U2F_VALIDATE_HANDLE}; + uint8_t data[96]; - memcpy(cmd + 5, app_id, 32); - memcpy(cmd + 5 + 32, key_handle, 64); + memcpy(data, app_id, 32); + memcpy(data + 32, key_handle, 64); - cmd[4] = 32 + 64; - if (!thd89_transmit(cmd, 5 + 96, NULL, NULL)) { + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_U2F_VALIDATE_HANDLE, data, + sizeof(data), NULL, NULL)) { return secfalse; } return sectrue; @@ -2585,15 +2830,15 @@ secbool se_u2f_authenticate(const uint8_t app_id[32], const uint8_t key_handle[64], const uint8_t challenge[32], uint8_t *u2f_counter, uint8_t sign[64]) { - uint8_t cmd[256] = {0x00, SE_INS_FIDO, 0x00, SE_FIDO_U2F_AUTHENTICATE}; + uint8_t data[128]; uint8_t recv[128]; uint16_t recv_len = sizeof(recv); - memcpy(cmd + 5, app_id, 32); - memcpy(cmd + 5 + 32, key_handle, 64); - memcpy(cmd + 5 + 32 + 64, challenge, 32); + memcpy(data, app_id, 32); + memcpy(data + 32, key_handle, 64); + memcpy(data + 32 + 64, challenge, 32); - cmd[4] = 128; - if (!thd89_transmit(cmd, 5 + 128, (uint8_t *)recv, &recv_len)) { + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_U2F_AUTHENTICATE, data, + sizeof(data), (uint8_t *)recv, &recv_len)) { return secfalse; } @@ -2609,21 +2854,19 @@ secbool se_u2f_authenticate(const uint8_t app_id[32], secbool se_derive_fido_keys(HDNode *out, const char *curve, const uint32_t *address_n, size_t address_n_count, uint32_t *fingerprint) { - uint8_t cmd[128] = {0x00, SE_INS_FIDO, 0x00, SE_FIDO_DERIVE_NODE}; uint8_t resp[256]; uint16_t resp_len = sizeof(resp); uint8_t len = strlen(curve); - cmd[5] = len; - memcpy(cmd + 6, curve, len); + APDU_DATA[0] = len; + memcpy(APDU_DATA + 1, curve, len); len += 1; - memcpy(cmd + 5 + len, (uint8_t *)address_n, address_n_count * 4); + memcpy(APDU_DATA + len, (uint8_t *)address_n, address_n_count * 4); len += address_n_count * 4; - cmd[4] = len; - - if (!thd89_transmit(cmd, 5 + len, (uint8_t *)resp, &resp_len)) { + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_DERIVE_NODE, APDU_DATA, len, + (uint8_t *)resp, &resp_len)) { return secfalse; } out->curve = get_curve_by_name(curve); @@ -2636,13 +2879,11 @@ secbool se_derive_fido_keys(HDNode *out, const char *curve, } secbool se_fido_hdnode_sign_digest(const uint8_t *hash, uint8_t *sig) { - uint8_t cmd[37] = {0x00, SE_INS_FIDO, 0x00, SE_FIDO_NODE_SIGN, 0x20}; uint8_t resp[64]; uint16_t resp_len = sizeof(resp); - memcpy(cmd + 5, hash, 32); - - if (!thd89_transmit(cmd, 37, (uint8_t *)resp, &resp_len)) { + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_NODE_SIGN, (uint8_t *)hash, + 32, (uint8_t *)resp, &resp_len)) { return secfalse; } memcpy(sig, resp, resp_len); @@ -2650,13 +2891,11 @@ secbool se_fido_hdnode_sign_digest(const uint8_t *hash, uint8_t *sig) { } secbool se_fido_att_sign_digest(const uint8_t *hash, uint8_t *sig) { - uint8_t cmd[37] = {0x00, SE_INS_FIDO, 0x00, SE_FIDO_ATT_SIGN, 0x20}; uint8_t resp[64]; uint16_t resp_len = sizeof(resp); - memcpy(cmd + 5, hash, 32); - - if (!thd89_transmit(cmd, 37, (uint8_t *)resp, &resp_len)) { + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_ATT_SIGN, (uint8_t *)hash, 32, + (uint8_t *)resp, &resp_len)) { return secfalse; } memcpy(sig, resp, resp_len); @@ -2740,3 +2979,41 @@ secbool se_delete_all_fido2_credentials(void) { } return sectrue; } + +secbool se_get_component_version(uint8_t slot, uint32_t *version) { + uint8_t resp[4] = {0}; + uint16_t resp_len = sizeof(resp); + + if (slot >= SE_COMPONENT_VERSION_SLOT_COUNT || version == NULL) { + return secfalse; + } + + if (!se_transmit_mac(SE_INS_COMPONENT_VERSION, 0x00, 0x00, &slot, 1, resp, + &resp_len)) { + return secfalse; + } + if (resp_len != sizeof(resp)) { + return secfalse; + } + + *version = (uint32_t)resp[0] | ((uint32_t)resp[1] << 8) | + ((uint32_t)resp[2] << 16) | ((uint32_t)resp[3] << 24); + return sectrue; +} + +secbool se_set_component_version(uint8_t slot, uint32_t version) { + uint8_t data[5] = {0}; + + if (slot >= SE_COMPONENT_VERSION_SLOT_COUNT) { + return secfalse; + } + + data[0] = slot; + data[1] = version & 0xFF; + data[2] = (version >> 8) & 0xFF; + data[3] = (version >> 16) & 0xFF; + data[4] = (version >> 24) & 0xFF; + + return se_transmit_mac(SE_INS_COMPONENT_VERSION, 0x00, 0x01, data, + sizeof(data), NULL, NULL); +} diff --git a/core/embed/trezorhal/se_thd89.h b/core/embed/trezorhal/se_thd89.h index 3c53c96694..718d7bf6bb 100644 --- a/core/embed/trezorhal/se_thd89.h +++ b/core/embed/trezorhal/se_thd89.h @@ -256,5 +256,9 @@ secbool se_set_fido2_resident_credentials(uint32_t index, const uint8_t *src, secbool se_delete_fido2_resident_credentials(uint32_t index); secbool se_delete_all_fido2_credentials(void); -secbool session_generate_seed_percent(uint8_t *percent); +secbool se_query_progress_percent(uint8_t *percent); + +secbool se_get_component_version(uint8_t slot, uint32_t *version); +secbool se_set_component_version(uint8_t slot, uint32_t version); + #endif diff --git a/core/embed/trezorhal/se_thd89_v2.c b/core/embed/trezorhal/se_thd89_v2.c new file mode 100644 index 0000000000..99ea4a82a1 --- /dev/null +++ b/core/embed/trezorhal/se_thd89_v2.c @@ -0,0 +1,174 @@ +#include "se_thd89_v2.h" + +#include + +#include "aes/aes.h" +#include "hmac.h" +#include "memzero.h" +#include "sha2.h" + +static const uint8_t SESSION_ENC_LABEL[] = "THD89 SESSION ENC"; +static const uint8_t SESSION_MAC_LABEL[] = "THD89 SESSION MAC"; +static const uint8_t SESSION_CONFIRM_LABEL[] = "THD89 SESSION CONFIRM"; +static const uint8_t SESSION_RESPONSE_LABEL[] = "THD89 SESSION RESPONSE V2"; +static const uint8_t SESSION_APDU_HEADER[5] = {0x00, 0xfa, 0x01, 0x00, 0x60}; +static const uint8_t RESPONSE_MAC_DOMAIN[16] = "THD89-RSP-MAC-V1"; + +static void derive_session_digest(const uint8_t shared_point[64], + const uint8_t se_random[16], + const uint8_t mcu_random[16], + const uint8_t *label, size_t label_len, + uint8_t digest[32]) { + SHA256_CTX ctx = {0}; + + sha256_Init(&ctx); + sha256_Update(&ctx, shared_point, 64); + sha256_Update(&ctx, se_random, 16); + sha256_Update(&ctx, mcu_random, 16); + sha256_Update(&ctx, label, label_len); + sha256_Final(&ctx, digest); + memzero(&ctx, sizeof(ctx)); +} + +void thd89_v2_derive_session_keys(const uint8_t shared_point[64], + const uint8_t se_random[16], + const uint8_t mcu_random[16], + uint8_t enc_key[16], uint8_t mac_key[16], + uint8_t confirm_key[32]) { + uint8_t digest[32] = {0}; + + derive_session_digest(shared_point, se_random, mcu_random, SESSION_ENC_LABEL, + sizeof(SESSION_ENC_LABEL) - 1, digest); + memcpy(enc_key, digest, 16); + + derive_session_digest(shared_point, se_random, mcu_random, SESSION_MAC_LABEL, + sizeof(SESSION_MAC_LABEL) - 1, digest); + memcpy(mac_key, digest, 16); + + derive_session_digest(shared_point, se_random, mcu_random, + SESSION_CONFIRM_LABEL, + sizeof(SESSION_CONFIRM_LABEL) - 1, confirm_key); + memzero(digest, sizeof(digest)); +} + +void thd89_v2_calculate_confirmation(const uint8_t confirm_key[32], + const uint8_t se_random[16], + const uint8_t request_data[96], + uint8_t confirmation[32]) { + HMAC_SHA256_CTX ctx = {0}; + + hmac_sha256_Init(&ctx, confirm_key, 32); + hmac_sha256_Update(&ctx, SESSION_RESPONSE_LABEL, + sizeof(SESSION_RESPONSE_LABEL) - 1); + hmac_sha256_Update(&ctx, SESSION_APDU_HEADER, sizeof(SESSION_APDU_HEADER)); + hmac_sha256_Update(&ctx, se_random, 16); + hmac_sha256_Update(&ctx, request_data, 96); + hmac_sha256_Final(&ctx, confirmation); + memzero(&ctx, sizeof(ctx)); +} + +static void response_mac_block(const aes_encrypt_ctx *ctx, uint8_t state[16], + const uint8_t block[16]) { + uint8_t input[16] = {0}; + uint8_t output[16] = {0}; + + for (size_t i = 0; i < sizeof(input); i++) { + input[i] = state[i] ^ block[i]; + } + if (aes_ecb_encrypt(input, output, sizeof(output), ctx) == EXIT_SUCCESS) { + memcpy(state, output, sizeof(output)); + } else { + memzero(state, 16); + } + memzero(input, sizeof(input)); + memzero(output, sizeof(output)); +} + +void thd89_v2_calculate_response_mac(const uint8_t mac_key[16], + const uint8_t header[4], + const uint8_t transaction[16], + const uint8_t *ciphertext, + uint16_t ciphertext_len, uint16_t sw1sw2, + uint8_t response_mac[4]) { + aes_encrypt_ctx ctx = {0}; + uint8_t state[16] = {0}; + uint8_t context[16] = {0}; + uint8_t status_block[16] = {0}; + + context[0] = header[0]; + context[1] = header[1]; + context[2] = header[2]; + context[3] = header[3]; + context[4] = (uint8_t)(ciphertext_len >> 8); + context[5] = (uint8_t)ciphertext_len; + context[6] = 0x80; + + status_block[0] = (uint8_t)(sw1sw2 >> 8); + status_block[1] = (uint8_t)sw1sw2; + status_block[2] = 0x80; + + if (aes_encrypt_key128(mac_key, &ctx) != EXIT_SUCCESS) { + memzero(response_mac, 4); + goto cleanup; + } + + response_mac_block(&ctx, state, RESPONSE_MAC_DOMAIN); + response_mac_block(&ctx, state, transaction); + response_mac_block(&ctx, state, context); + for (uint16_t offset = 0; offset < ciphertext_len; offset += 16) { + response_mac_block(&ctx, state, ciphertext + offset); + } + response_mac_block(&ctx, state, status_block); + memcpy(response_mac, state, 4); + +cleanup: + memzero(&ctx, sizeof(ctx)); + memzero(state, sizeof(state)); + memzero(context, sizeof(context)); + memzero(status_block, sizeof(status_block)); +} + +thd89_v2_response_shape_t thd89_v2_classify_response(uint16_t response_data_len, + uint16_t sw1sw2) { + if ((sw1sw2 >> 8) == 0x6c) { + return response_data_len == 0 ? THD89_V2_RESPONSE_NO_MAC_6C + : THD89_V2_RESPONSE_INVALID; + } + if (response_data_len < 4 || ((response_data_len - 4) % 16) != 0) { + return THD89_V2_RESPONSE_INVALID; + } + return THD89_V2_RESPONSE_MAC_REQUIRED; +} + +bool thd89_v2_constant_time_equal(const uint8_t *left, const uint8_t *right, + size_t len) { + uint8_t diff = 0; + + if ((left == NULL || right == NULL) && len != 0) { + return false; + } + for (size_t i = 0; i < len; i++) { + diff |= left[i] ^ right[i]; + } + return diff == 0; +} + +bool thd89_v2_unpad_iso7816_4(const uint8_t *padded, uint16_t padded_len, + uint16_t *plaintext_len) { + if (padded == NULL || plaintext_len == NULL || padded_len < 16 || + (padded_len % 16) != 0) { + return false; + } + + for (uint16_t offset = 0; offset < 16; offset++) { + uint8_t value = padded[padded_len - 1 - offset]; + if (value == 0x80) { + *plaintext_len = padded_len - 1 - offset; + return true; + } + if (value != 0x00) { + return false; + } + } + return false; +} diff --git a/core/embed/trezorhal/se_thd89_v2.h b/core/embed/trezorhal/se_thd89_v2.h new file mode 100644 index 0000000000..38f82f1524 --- /dev/null +++ b/core/embed/trezorhal/se_thd89_v2.h @@ -0,0 +1,41 @@ +#ifndef _TREZORHAL_SE_THD89_V2_H_ +#define _TREZORHAL_SE_THD89_V2_H_ + +#include +#include +#include + +typedef enum { + THD89_V2_RESPONSE_MAC_REQUIRED = 0, + THD89_V2_RESPONSE_NO_MAC_6C, + THD89_V2_RESPONSE_INVALID, +} thd89_v2_response_shape_t; + +void thd89_v2_derive_session_keys(const uint8_t shared_point[64], + const uint8_t se_random[16], + const uint8_t mcu_random[16], + uint8_t enc_key[16], uint8_t mac_key[16], + uint8_t confirm_key[32]); + +void thd89_v2_calculate_confirmation(const uint8_t confirm_key[32], + const uint8_t se_random[16], + const uint8_t request_data[96], + uint8_t confirmation[32]); + +void thd89_v2_calculate_response_mac(const uint8_t mac_key[16], + const uint8_t header[4], + const uint8_t transaction[16], + const uint8_t *ciphertext, + uint16_t ciphertext_len, uint16_t sw1sw2, + uint8_t response_mac[4]); + +thd89_v2_response_shape_t thd89_v2_classify_response(uint16_t response_data_len, + uint16_t sw1sw2); + +bool thd89_v2_constant_time_equal(const uint8_t *left, const uint8_t *right, + size_t len); + +bool thd89_v2_unpad_iso7816_4(const uint8_t *padded, uint16_t padded_len, + uint16_t *plaintext_len); + +#endif diff --git a/core/embed/trezorhal/thd89.c b/core/embed/trezorhal/thd89.c index 1b1087d2ac..bf91fb5fe5 100644 --- a/core/embed/trezorhal/thd89.c +++ b/core/embed/trezorhal/thd89.c @@ -407,14 +407,16 @@ HAL_StatusTypeDef i2c_master_send(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, #define I2C_RECV_BUFFER_TOO_SMALL (0x80) #define I2C_RECV_TIMEOUT (5 * 1000) // 5s +#define I2C_RECV_MAX_FRAME_LEN (2u + 1024u + 64u) int i2c_master_recive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t *Size, uint32_t Timeout) { // uint32_t tickstart, tickstart1; uint8_t data[4]; - uint16_t temp_len, data_len; + uint16_t frame_len, temp_len, data_len; uint8_t *data_ptr = pData; uint8_t xor = 0x00; + bool buffer_too_small = false; sw1 = sw2 = 0; if (hi2c->State == HAL_I2C_STATE_READY) { @@ -468,12 +470,19 @@ int i2c_master_recive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, } data[1] = (uint8_t)hi2c->Instance->RXDR; - temp_len = (data[0] << 8) + data[1] - 2; + frame_len = ((uint16_t)data[0] << 8) | data[1]; + if (frame_len < 2u || frame_len > I2C_RECV_MAX_FRAME_LEN) { + *Size = 0; + SET_BIT(hi2c->Instance->CR2, I2C_CR2_STOP); + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + __HAL_UNLOCK(hi2c); + return HAL_ERROR; + } + temp_len = frame_len - 2u; data_len = temp_len; - if (data_len > *Size) { - return I2C_RECV_BUFFER_TOO_SMALL; - } + buffer_too_small = data_len > *Size; xor = xor_check(0, data, 2); while (temp_len > 0) { @@ -485,7 +494,11 @@ int i2c_master_recive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, if (I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, 0) != HAL_OK) { return HAL_ERROR; } - *data_ptr++ = (uint8_t)hi2c->Instance->RXDR; + uint8_t received = (uint8_t)hi2c->Instance->RXDR; + xor ^= received; + if (!buffer_too_small) { + *data_ptr++ = received; + } } temp_len -= data_len_tmp; } @@ -521,7 +534,6 @@ int i2c_master_recive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, /* Process Unlocked */ __HAL_UNLOCK(hi2c); - xor = xor_check(xor, pData, data_len); xor = xor_check(xor, data, 2); if (xor != data[2]) { *Size = 0; @@ -532,14 +544,15 @@ int i2c_master_recive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, *Size = data_len; - return HAL_OK; + return buffer_too_small ? I2C_RECV_BUFFER_TOO_SMALL : HAL_OK; } else { return HAL_BUSY; } } -static secbool _thd89_transmit_ex(uint8_t addr, uint8_t *cmd, uint16_t len, - uint8_t *resp, uint16_t *resp_len) { +static secbool _thd89_transmit_raw_ex(uint8_t addr, uint8_t *cmd, uint16_t len, + uint8_t *resp, uint16_t *resp_len, + uint16_t *sw1sw2) { int ret = 0; char err_info[64] = {0}; uint32_t irq = disable_irq(); @@ -569,27 +582,50 @@ static secbool _thd89_transmit_ex(uint8_t addr, uint8_t *cmd, uint16_t len, } return secfalse; } - if ((0x90 != sw1) || (0x00 != sw2)) { - return secfalse; + if (sw1sw2 != NULL) { + *sw1sw2 = ((uint16_t)sw1 << 8) | sw2; } - return sectrue; } int thd89_irq_nest = 0; -secbool thd89_transmit_ex(uint8_t addr, uint8_t *cmd, uint16_t len, - uint8_t *resp, uint16_t *resp_len) { +secbool thd89_transmit_raw_ex(uint8_t addr, uint8_t *cmd, uint16_t len, + uint8_t *resp, uint16_t *resp_len, + uint16_t *sw1sw2) { + uint16_t empty_resp_len = 0; + uint16_t *io_resp_len = resp_len; + + if (resp == NULL) { + io_resp_len = &empty_resp_len; + } else if (resp_len == NULL) { + return secfalse; + } + uint32_t irq = disable_irq(); thd89_irq_nest++; - secbool result = _thd89_transmit_ex(addr, cmd, len, resp, resp_len); + secbool result = + _thd89_transmit_raw_ex(addr, cmd, len, resp, io_resp_len, sw1sw2); thd89_irq_nest--; if (thd89_irq_nest == 0) { enable_irq(irq); } + if (resp == NULL && resp_len != NULL) { + *resp_len = empty_resp_len; + } return result; } +secbool thd89_transmit_ex(uint8_t addr, uint8_t *cmd, uint16_t len, + uint8_t *resp, uint16_t *resp_len) { + uint16_t sw1sw2 = 0; + if (thd89_transmit_raw_ex(addr, cmd, len, resp, resp_len, &sw1sw2) != + sectrue) { + return secfalse; + } + return sectrue * (sw1sw2 == 0x9000); +} + secbool thd89_transmit(uint8_t *cmd, uint16_t len, uint8_t *resp, uint16_t *resp_len) { return thd89_transmit_ex(THD89_MASTER_ADDRESS, cmd, len, resp, resp_len); diff --git a/core/embed/trezorhal/thd89.h b/core/embed/trezorhal/thd89.h index a1b991cba9..275a8babae 100644 --- a/core/embed/trezorhal/thd89.h +++ b/core/embed/trezorhal/thd89.h @@ -23,6 +23,9 @@ secbool thd89_fp_transmit(uint8_t *cmd, uint16_t len, uint8_t *resp, uint16_t *resp_len); secbool thd89_transmit_ex(uint8_t addr, uint8_t *cmd, uint16_t len, uint8_t *resp, uint16_t *resp_len); +secbool thd89_transmit_raw_ex(uint8_t addr, uint8_t *cmd, uint16_t len, + uint8_t *resp, uint16_t *resp_len, + uint16_t *sw1sw2); uint16_t thd89_last_error(); #endif From 50fb43433f031d001592f768b24e4aa8f64d468e Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Tue, 4 Aug 2026 00:48:37 +0800 Subject: [PATCH 03/11] fix: replace THD89 SLIP21 key export APIs --- .../modtrezorcrypto-se-thd89.h | 188 +++++++++++++++--- core/embed/trezorhal/se_thd89.c | 157 ++++++++++++++- core/embed/trezorhal/se_thd89.h | 22 +- .../mocks/generated/trezorcrypto/se_thd89.pyi | 39 +++- 4 files changed, 356 insertions(+), 50 deletions(-) diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h index 6b87230aa3..cf70019ab8 100644 --- a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h @@ -529,35 +529,53 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( mod_trezorcrypto_se_thd89_aes256_decrypt_obj, 2, 3, mod_trezorcrypto_se_thd89_aes256_decrypt); -/// def slip21_node() -> bytes: -/// """ -/// Returns slip21 node. -/// """ -STATIC mp_obj_t mod_trezorcrypto_se_thd89_slip21_node(void) { - vstr_t vstr = {0}; - vstr_init_len(&vstr, 64); - if (se_slip21_node((uint8_t *)vstr.buf) != 0) { - mp_raise_ValueError("slip21_node failed"); - } - return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); +/// def slip21_ownership_id(script_pubkey: bytes) -> bytes: +/// """Return the SLIP-0019 ownership identifier for script_pubkey.""" +STATIC mp_obj_t +mod_trezorcrypto_se_thd89_slip21_ownership_id(mp_obj_t script_pubkey) { + mp_buffer_info_t script = {0}; + uint8_t out[32] = {0}; + mp_get_buffer_raise(script_pubkey, &script, MP_BUFFER_READ); + if (script.len == 0 || script.len > 1024 || + se_slip21_ownership_id(script.buf, script.len, out) != sectrue) { + mp_raise_ValueError("slip21 ownership id failed"); + } + return mp_obj_new_bytes(out, sizeof(out)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1( + mod_trezorcrypto_se_thd89_slip21_ownership_id_obj, + mod_trezorcrypto_se_thd89_slip21_ownership_id); + +/// def slip21_address_mac(slip44: int, address: bytes) -> bytes: +/// """Return the SLIP-0024 address MAC.""" +STATIC mp_obj_t mod_trezorcrypto_se_thd89_slip21_address_mac( + mp_obj_t slip44_obj, mp_obj_t address_obj) { + uint32_t slip44 = trezor_obj_get_uint(slip44_obj); + mp_buffer_info_t address = {0}; + uint8_t out[32] = {0}; + mp_get_buffer_raise(address_obj, &address, MP_BUFFER_READ); + if (address.len == 0 || address.len > 1018 || + se_slip21_address_mac(slip44, address.buf, address.len, out) != sectrue) { + mp_raise_ValueError("slip21 address MAC failed"); + } + return mp_obj_new_bytes(out, sizeof(out)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorcrypto_se_thd89_slip21_node_obj, - mod_trezorcrypto_se_thd89_slip21_node); +STATIC MP_DEFINE_CONST_FUN_OBJ_2( + mod_trezorcrypto_se_thd89_slip21_address_mac_obj, + mod_trezorcrypto_se_thd89_slip21_address_mac); -/// def slip21_fido_node() -> bytes: -/// """ -/// Returns slip21 fido node, seed without passphrase. -/// """ -STATIC mp_obj_t mod_trezorcrypto_se_thd89_slip21_fido_node(void) { - vstr_t vstr = {0}; - vstr_init_len(&vstr, 64); - if (se_slip21_fido_node((uint8_t *)vstr.buf) != 0) { - mp_raise_ValueError("slip21 fido node failed"); +/// def slip21_slip25_mac() -> bytes: +/// """Return the SLIP-0025 keychain authorization MAC.""" +STATIC mp_obj_t mod_trezorcrypto_se_thd89_slip21_slip25_mac(void) { + uint8_t out[32] = {0}; + if (se_slip21_slip25_mac(out) != sectrue) { + mp_raise_ValueError("slip21 SLIP-0025 MAC failed"); } - return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); + return mp_obj_new_bytes(out, sizeof(out)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorcrypto_se_thd89_slip21_fido_node_obj, - mod_trezorcrypto_se_thd89_slip21_fido_node); +STATIC MP_DEFINE_CONST_FUN_OBJ_0( + mod_trezorcrypto_se_thd89_slip21_slip25_mac_obj, + mod_trezorcrypto_se_thd89_slip21_slip25_mac); /// def authorization_set( /// authorization_type: int, @@ -974,6 +992,108 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1( mod_trezorcrypto_se_thd89_fido_att_sign_digest_obj, mod_trezorcrypto_se_thd89_fido_att_sign_digest); +/// def fido_credential_encrypt(rp_id_hash: bytes, plaintext: bytes) -> bytes: +/// """Encrypt a SLIP-0022 credential ID inside the secure element.""" +STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_credential_encrypt( + mp_obj_t rp_id_hash_obj, mp_obj_t plaintext_obj) { + mp_buffer_info_t rp_id_hash = {0}; + mp_buffer_info_t plaintext = {0}; + uint16_t credential_id_len = 0; + vstr_t credential_id = {0}; + mp_get_buffer_raise(rp_id_hash_obj, &rp_id_hash, MP_BUFFER_READ); + mp_get_buffer_raise(plaintext_obj, &plaintext, MP_BUFFER_READ); + if (rp_id_hash.len != 32 || plaintext.len == 0 || plaintext.len > 480) { + mp_raise_ValueError("invalid FIDO credential data"); + } + vstr_init_len(&credential_id, 512); + if (se_fido_credential_encrypt(rp_id_hash.buf, plaintext.buf, plaintext.len, + (uint8_t *)credential_id.buf, + &credential_id_len) != sectrue) { + mp_raise_ValueError("FIDO credential encryption failed"); + } + credential_id.len = credential_id_len; + return mp_obj_new_str_from_vstr(&mp_type_bytes, &credential_id); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2( + mod_trezorcrypto_se_thd89_fido_credential_encrypt_obj, + mod_trezorcrypto_se_thd89_fido_credential_encrypt); + +/// def fido_credential_peek(credential_id: bytes) -> bytes: +/// """Tentatively decrypt a credential for legacy RP-ID discovery.""" +STATIC mp_obj_t +mod_trezorcrypto_se_thd89_fido_credential_peek(mp_obj_t credential_id_obj) { + mp_buffer_info_t credential_id = {0}; + uint16_t plaintext_len = 0; + vstr_t plaintext = {0}; + mp_get_buffer_raise(credential_id_obj, &credential_id, MP_BUFFER_READ); + if (credential_id.len < 33 || credential_id.len > 512) { + mp_raise_ValueError("invalid FIDO credential ID"); + } + vstr_init_len(&plaintext, 480); + if (se_fido_credential_peek(credential_id.buf, credential_id.len, + (uint8_t *)plaintext.buf, + &plaintext_len) != sectrue) { + mp_raise_ValueError("FIDO credential peek failed"); + } + plaintext.len = plaintext_len; + return mp_obj_new_str_from_vstr(&mp_type_bytes, &plaintext); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1( + mod_trezorcrypto_se_thd89_fido_credential_peek_obj, + mod_trezorcrypto_se_thd89_fido_credential_peek); + +/// def fido_credential_decrypt( +/// rp_id_hash: bytes, credential_id: bytes +/// ) -> bytes: +/// """Authenticate and decrypt a SLIP-0022 credential ID.""" +STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_credential_decrypt( + mp_obj_t rp_id_hash_obj, mp_obj_t credential_id_obj) { + mp_buffer_info_t rp_id_hash = {0}; + mp_buffer_info_t credential_id = {0}; + uint16_t plaintext_len = 0; + vstr_t plaintext = {0}; + mp_get_buffer_raise(rp_id_hash_obj, &rp_id_hash, MP_BUFFER_READ); + mp_get_buffer_raise(credential_id_obj, &credential_id, MP_BUFFER_READ); + if (rp_id_hash.len != 32 || credential_id.len < 33 || + credential_id.len > 512) { + mp_raise_ValueError("invalid FIDO credential data"); + } + vstr_init_len(&plaintext, 480); + if (se_fido_credential_decrypt(rp_id_hash.buf, credential_id.buf, + credential_id.len, (uint8_t *)plaintext.buf, + &plaintext_len) != sectrue) { + mp_raise_ValueError("FIDO credential decryption failed"); + } + plaintext.len = plaintext_len; + return mp_obj_new_str_from_vstr(&mp_type_bytes, &plaintext); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2( + mod_trezorcrypto_se_thd89_fido_credential_decrypt_obj, + mod_trezorcrypto_se_thd89_fido_credential_decrypt); + +/// def fido_hmac_secret(credential_id: bytes, salt: bytes) -> bytes: +/// """Return the purpose-bound hmac-secret output for one or two salts.""" +STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_hmac_secret( + mp_obj_t credential_id_obj, mp_obj_t salt_obj) { + mp_buffer_info_t credential_id = {0}; + mp_buffer_info_t salt = {0}; + vstr_t out = {0}; + mp_get_buffer_raise(credential_id_obj, &credential_id, MP_BUFFER_READ); + mp_get_buffer_raise(salt_obj, &salt, MP_BUFFER_READ); + if (credential_id.len < 33 || credential_id.len > 512 || + (salt.len != 32 && salt.len != 64)) { + mp_raise_ValueError("invalid FIDO hmac-secret data"); + } + vstr_init_len(&out, salt.len); + if (se_fido_hmac_secret(credential_id.buf, credential_id.len, salt.buf, + salt.len, (uint8_t *)out.buf) != sectrue) { + mp_raise_ValueError("FIDO hmac-secret failed"); + } + return mp_obj_new_str_from_vstr(&mp_type_bytes, &out); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_se_thd89_fido_hmac_secret_obj, + mod_trezorcrypto_se_thd89_fido_hmac_secret); + /// def fido_delete_all_credentials() -> None: /// """ /// Delete all FIDO2 credentials. @@ -1186,10 +1306,12 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_se_thd89_globals_table[] = { MP_ROM_PTR(&mod_trezorcrypto_se_thd89_aes256_encrypt_obj)}, {MP_ROM_QSTR(MP_QSTR_aes256_decrypt), MP_ROM_PTR(&mod_trezorcrypto_se_thd89_aes256_decrypt_obj)}, - {MP_ROM_QSTR(MP_QSTR_slip21_node), - MP_ROM_PTR(&mod_trezorcrypto_se_thd89_slip21_node_obj)}, - {MP_ROM_QSTR(MP_QSTR_slip21_fido_node), - MP_ROM_PTR(&mod_trezorcrypto_se_thd89_slip21_fido_node_obj)}, + {MP_ROM_QSTR(MP_QSTR_slip21_ownership_id), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_slip21_ownership_id_obj)}, + {MP_ROM_QSTR(MP_QSTR_slip21_address_mac), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_slip21_address_mac_obj)}, + {MP_ROM_QSTR(MP_QSTR_slip21_slip25_mac), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_slip21_slip25_mac_obj)}, {MP_ROM_QSTR(MP_QSTR_authorization_set), MP_ROM_PTR(&mod_trezorcrypto_se_thd89_authorization_set_obj)}, {MP_ROM_QSTR(MP_QSTR_authorization_get_type), @@ -1218,6 +1340,14 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_se_thd89_globals_table[] = { MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_u2f_authenticate_obj)}, {MP_ROM_QSTR(MP_QSTR_fido_u2f_validate), MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_u2f_validate_obj)}, + {MP_ROM_QSTR(MP_QSTR_fido_credential_encrypt), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_credential_encrypt_obj)}, + {MP_ROM_QSTR(MP_QSTR_fido_credential_peek), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_credential_peek_obj)}, + {MP_ROM_QSTR(MP_QSTR_fido_credential_decrypt), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_credential_decrypt_obj)}, + {MP_ROM_QSTR(MP_QSTR_fido_hmac_secret), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_hmac_secret_obj)}, {MP_ROM_QSTR(MP_QSTR_fido_sign_digest), MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_sign_digest_obj)}, {MP_ROM_QSTR(MP_QSTR_fido_att_sign_digest), diff --git a/core/embed/trezorhal/se_thd89.c b/core/embed/trezorhal/se_thd89.c index 1c1cd95d7e..991acb90e0 100644 --- a/core/embed/trezorhal/se_thd89.c +++ b/core/embed/trezorhal/se_thd89.c @@ -56,8 +56,16 @@ typedef enum { SE_FIDO_DERIVE_NODE, SE_FIDO_NODE_SIGN, SE_FIDO_ATT_SIGN, + SE_FIDO_SLIP21_CREDENTIAL_ENCRYPT, + SE_FIDO_SLIP21_CREDENTIAL_PEEK, + SE_FIDO_SLIP21_CREDENTIAL_DECRYPT, + SE_FIDO_SLIP21_HMAC_SECRET, } SE_FIDO_P2; +#define SE_FIDO_CREDENTIAL_ID_MIN_LEN 33U +#define SE_FIDO_CREDENTIAL_ID_MAX_LEN 512U +#define SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN 480U + #define SE_PIN_RETRY_MAX 5 #define SE_SW_PIN_RETRY_LIMIT_REACHED 0x6983 @@ -2505,22 +2513,54 @@ int se_nem_aes256_decrypt(const uint8_t *ed25519_pubkey, const uint8_t *iv, return 0; } -int se_slip21_node(uint8_t *data) { - uint16_t resp_len = 64; +secbool se_slip21_ownership_id(const uint8_t *script_pubkey, + uint16_t script_pubkey_len, uint8_t out[32]) { + uint16_t resp_len = 32; - if (!se_transmit_mac(0xEB, 0x00, 0x00, NULL, 0, data, &resp_len)) { - return -1; + if (script_pubkey == NULL || script_pubkey_len == 0 || + script_pubkey_len > SE_DATA_MAX_LEN || out == NULL) { + return secfalse; } - return 0; + if (!se_transmit_mac(0xEB, 0x01, 0x00, (uint8_t *)script_pubkey, + script_pubkey_len, out, &resp_len) || + resp_len != 32) { + return secfalse; + } + return sectrue; } -// seed without passphrase -int se_slip21_fido_node(uint8_t *data) { - uint16_t resp_len = 64; - if (!se_transmit_mac(0xEB, 0x00, 0x01, NULL, 0, data, &resp_len)) { - return -1; +secbool se_slip21_address_mac(uint32_t slip44, const uint8_t *address, + uint16_t address_len, uint8_t out[32]) { + uint16_t resp_len = 32; + + if (address == NULL || address_len == 0 || + address_len > SE_DATA_MAX_LEN - 6U || out == NULL) { + return secfalse; } - return 0; + APDU_DATA[0] = (uint8_t)slip44; + APDU_DATA[1] = (uint8_t)(slip44 >> 8); + APDU_DATA[2] = (uint8_t)(slip44 >> 16); + APDU_DATA[3] = (uint8_t)(slip44 >> 24); + APDU_DATA[4] = (uint8_t)(address_len >> 8); + APDU_DATA[5] = (uint8_t)address_len; + memcpy(APDU_DATA + 6, address, address_len); + if (!se_transmit_mac(0xEB, 0x01, 0x01, APDU_DATA, address_len + 6U, out, + &resp_len) || + resp_len != 32) { + return secfalse; + } + return sectrue; +} + +secbool se_slip21_slip25_mac(uint8_t out[32]) { + uint16_t resp_len = 32; + + if (out == NULL || + !se_transmit_mac(0xEB, 0x01, 0x02, NULL, 0, out, &resp_len) || + resp_len != 32) { + return secfalse; + } + return sectrue; } secbool se_authorization_set(const uint32_t authorization_type, @@ -2902,6 +2942,101 @@ secbool se_fido_att_sign_digest(const uint8_t *hash, uint8_t *sig) { return sectrue; } +secbool se_fido_credential_encrypt(const uint8_t rp_id_hash[32], + const uint8_t *plaintext, + uint16_t plaintext_len, + uint8_t *credential_id, + uint16_t *credential_id_len) { + uint16_t resp_len = SE_FIDO_CREDENTIAL_ID_MAX_LEN; + + if (rp_id_hash == NULL || plaintext == NULL || plaintext_len == 0 || + plaintext_len > SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN || + credential_id == NULL || credential_id_len == NULL) { + return secfalse; + } + memcpy(APDU_DATA, rp_id_hash, 32); + memcpy(APDU_DATA + 32, plaintext, plaintext_len); + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_CREDENTIAL_ENCRYPT, + APDU_DATA, plaintext_len + 32U, credential_id, + &resp_len) || + resp_len < SE_FIDO_CREDENTIAL_ID_MIN_LEN || + resp_len > SE_FIDO_CREDENTIAL_ID_MAX_LEN) { + return secfalse; + } + *credential_id_len = resp_len; + return sectrue; +} + +secbool se_fido_credential_peek(const uint8_t *credential_id, + uint16_t credential_id_len, uint8_t *plaintext, + uint16_t *plaintext_len) { + uint16_t resp_len = SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN; + + if (credential_id == NULL || + credential_id_len < SE_FIDO_CREDENTIAL_ID_MIN_LEN || + credential_id_len > SE_FIDO_CREDENTIAL_ID_MAX_LEN || plaintext == NULL || + plaintext_len == NULL) { + return secfalse; + } + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_CREDENTIAL_PEEK, + (uint8_t *)credential_id, credential_id_len, plaintext, + &resp_len) || + resp_len == 0 || resp_len > SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN) { + return secfalse; + } + *plaintext_len = resp_len; + return sectrue; +} + +secbool se_fido_credential_decrypt(const uint8_t rp_id_hash[32], + const uint8_t *credential_id, + uint16_t credential_id_len, + uint8_t *plaintext, + uint16_t *plaintext_len) { + uint16_t resp_len = SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN; + + if (rp_id_hash == NULL || credential_id == NULL || + credential_id_len < SE_FIDO_CREDENTIAL_ID_MIN_LEN || + credential_id_len > SE_FIDO_CREDENTIAL_ID_MAX_LEN || plaintext == NULL || + plaintext_len == NULL) { + return secfalse; + } + memcpy(APDU_DATA, rp_id_hash, 32); + memcpy(APDU_DATA + 32, credential_id, credential_id_len); + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_CREDENTIAL_DECRYPT, + APDU_DATA, credential_id_len + 32U, plaintext, + &resp_len) || + resp_len == 0 || resp_len > SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN) { + return secfalse; + } + *plaintext_len = resp_len; + return sectrue; +} + +secbool se_fido_hmac_secret(const uint8_t *credential_id, + uint16_t credential_id_len, const uint8_t *salt, + uint16_t salt_len, uint8_t *out) { + uint16_t resp_len = salt_len; + + if (credential_id == NULL || + credential_id_len < SE_FIDO_CREDENTIAL_ID_MIN_LEN || + credential_id_len > SE_FIDO_CREDENTIAL_ID_MAX_LEN || salt == NULL || + (salt_len != 32 && salt_len != 64) || out == NULL) { + return secfalse; + } + APDU_DATA[0] = (uint8_t)(credential_id_len >> 8); + APDU_DATA[1] = (uint8_t)credential_id_len; + memcpy(APDU_DATA + 2, credential_id, credential_id_len); + APDU_DATA[2 + credential_id_len] = (uint8_t)salt_len; + memcpy(APDU_DATA + 3 + credential_id_len, salt, salt_len); + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_HMAC_SECRET, APDU_DATA, + credential_id_len + salt_len + 3U, out, &resp_len) || + resp_len != salt_len) { + return secfalse; + } + return sectrue; +} + secbool se_get_fido2_data(uint16_t offset, uint8_t *dest, uint16_t len) { uint8_t cmd[4] = {0}; uint16_t recv_len = len; diff --git a/core/embed/trezorhal/se_thd89.h b/core/embed/trezorhal/se_thd89.h index 718d7bf6bb..b197ef2989 100644 --- a/core/embed/trezorhal/se_thd89.h +++ b/core/embed/trezorhal/se_thd89.h @@ -210,8 +210,11 @@ int se_nem_aes256_encrypt(const uint8_t *ed25519_public_key, const uint8_t *iv, int se_nem_aes256_decrypt(const uint8_t *ed25519_public_key, const uint8_t *iv, const uint8_t *salt, uint8_t *payload, uint16_t size, uint8_t *out); -int se_slip21_node(uint8_t *data); -int se_slip21_fido_node(uint8_t *data); +secbool se_slip21_ownership_id(const uint8_t *script_pubkey, + uint16_t script_pubkey_len, uint8_t out[32]); +secbool se_slip21_address_mac(uint32_t slip44, const uint8_t *address, + uint16_t address_len, uint8_t out[32]); +secbool se_slip21_slip25_mac(uint8_t out[32]); secbool se_authorization_set(const uint32_t authorization_type, const uint8_t *authorization, @@ -249,6 +252,21 @@ secbool se_derive_fido_keys(HDNode *out, const char *curve, uint32_t *fingerprint); secbool se_fido_hdnode_sign_digest(const uint8_t *hash, uint8_t *sig); secbool se_fido_att_sign_digest(const uint8_t *hash, uint8_t *sig); +secbool se_fido_credential_encrypt(const uint8_t rp_id_hash[32], + const uint8_t *plaintext, + uint16_t plaintext_len, + uint8_t *credential_id, + uint16_t *credential_id_len); +secbool se_fido_credential_peek(const uint8_t *credential_id, + uint16_t credential_id_len, uint8_t *plaintext, + uint16_t *plaintext_len); +secbool se_fido_credential_decrypt(const uint8_t rp_id_hash[32], + const uint8_t *credential_id, + uint16_t credential_id_len, + uint8_t *plaintext, uint16_t *plaintext_len); +secbool se_fido_hmac_secret(const uint8_t *credential_id, + uint16_t credential_id_len, const uint8_t *salt, + uint16_t salt_len, uint8_t *out); secbool se_get_fido2_resident_credentials(uint32_t index, uint8_t *dest, uint16_t *dst_len); secbool se_set_fido2_resident_credentials(uint32_t index, const uint8_t *src, diff --git a/core/mocks/generated/trezorcrypto/se_thd89.pyi b/core/mocks/generated/trezorcrypto/se_thd89.pyi index 8b69011666..9b2c9f8d2f 100644 --- a/core/mocks/generated/trezorcrypto/se_thd89.pyi +++ b/core/mocks/generated/trezorcrypto/se_thd89.pyi @@ -152,17 +152,18 @@ bytes: # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h -def slip21_node() -> bytes: - """ - Returns slip21 node. - """ +def slip21_ownership_id(script_pubkey: bytes) -> bytes: + """Return the SLIP-0019 ownership identifier for script_pubkey.""" # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h -def slip21_fido_node() -> bytes: - """ - Returns slip21 fido node, seed without passphrase. - """ +def slip21_address_mac(slip44: int, address: bytes) -> bytes: + """Return the SLIP-0024 address MAC.""" + + +# extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +def slip21_slip25_mac() -> bytes: + """Return the SLIP-0025 keychain authorization MAC.""" # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h @@ -306,6 +307,28 @@ def fido_att_sign_digest( """ +# extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +def fido_credential_encrypt(rp_id_hash: bytes, plaintext: bytes) -> bytes: + """Encrypt a SLIP-0022 credential ID inside the secure element.""" + + +# extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +def fido_credential_peek(credential_id: bytes) -> bytes: + """Tentatively decrypt a credential for legacy RP-ID discovery.""" + + +# extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +def fido_credential_decrypt( + rp_id_hash: bytes, credential_id: bytes +) -> bytes: + """Authenticate and decrypt a SLIP-0022 credential ID.""" + + +# extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +def fido_hmac_secret(credential_id: bytes, salt: bytes) -> bytes: + """Return the purpose-bound hmac-secret output for one or two salts.""" + + # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h def fido_delete_all_credentials() -> None: """ From 9a8c9fa9d314a2900235f6da93dce793d405825e Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Tue, 4 Aug 2026 00:48:48 +0800 Subject: [PATCH 04/11] fix: route wallet SLIP21 operations through THD89 --- core/src/apps/base.py | 28 ++++++++++++++++------------ core/src/apps/bitcoin/ownership.py | 5 +++++ core/src/apps/common/address_mac.py | 5 +++++ core/src/apps/common/keychain.py | 4 +--- core/src/apps/common/seed.py | 8 ++------ 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/core/src/apps/base.py b/core/src/apps/base.py index 46f490fced..4beb4bcc5a 100644 --- a/core/src/apps/base.py +++ b/core/src/apps/base.py @@ -404,14 +404,9 @@ async def handle_DoPreauthorized( async def handle_UnlockPath(ctx: wire.Context, msg: UnlockPath) -> protobuf.MessageType: - from trezor.crypto import hmac from trezor.messages import UnlockedPathRequest from trezor.ui.layouts import confirm_action from apps.common.paths import SLIP25_PURPOSE - from apps.common.seed import Slip21Node, get_seed - from apps.common.writers import write_uint32_le - - _KEYCHAIN_MAC_KEY_PATH = [b"TREZOR", b"Keychain MAC key"] # UnlockPath is relevant only for SLIP-25 paths. # Note: Currently we only allow unlocking the entire SLIP-25 purpose subtree instead of @@ -419,13 +414,22 @@ async def handle_UnlockPath(ctx: wire.Context, msg: UnlockPath) -> protobuf.Mess if msg.address_n != [SLIP25_PURPOSE]: raise wire.DataError("Invalid path") - seed = await get_seed(ctx) - node = Slip21Node(seed) - node.derive_path(_KEYCHAIN_MAC_KEY_PATH) - mac = utils.HashWriter(hmac(hmac.SHA256, node.key())) - for i in msg.address_n: - write_uint32_le(mac, i) - expected_mac = mac.get_digest() + if utils.USE_THD89: + from trezor.crypto import se_thd89 + + expected_mac = se_thd89.slip21_slip25_mac() + else: + from trezor.crypto import hmac + from apps.common.seed import Slip21Node, get_seed + from apps.common.writers import write_uint32_le + + seed = await get_seed(ctx) + node = Slip21Node(seed) + node.derive_path([b"TREZOR", b"Keychain MAC key"]) + mac = utils.HashWriter(hmac(hmac.SHA256, node.key())) + for i in msg.address_n: + write_uint32_le(mac, i) + expected_mac = mac.get_digest() # Require confirmation to access SLIP25 paths unless already authorized. if msg.mac: diff --git a/core/src/apps/bitcoin/ownership.py b/core/src/apps/bitcoin/ownership.py index c98a1ad479..3a6ff27963 100644 --- a/core/src/apps/bitcoin/ownership.py +++ b/core/src/apps/bitcoin/ownership.py @@ -144,6 +144,11 @@ def read_scriptsig_witness(ownership_proof: bytes) -> tuple[memoryview, memoryvi def get_identifier(script_pubkey: bytes, keychain: Keychain) -> bytes: + if utils.USE_THD89: + from trezor.crypto import se_thd89 + + return se_thd89.slip21_ownership_id(script_pubkey) + # k = Key(m/"SLIP-0019"/"Ownership identification key") node = keychain.derive_slip21(_OWNERSHIP_ID_KEY_PATH) diff --git a/core/src/apps/common/address_mac.py b/core/src/apps/common/address_mac.py index 496395365b..35a9ff2cde 100644 --- a/core/src/apps/common/address_mac.py +++ b/core/src/apps/common/address_mac.py @@ -20,6 +20,11 @@ def check_address_mac( def get_address_mac(address: str, slip44: int, keychain: Keychain) -> bytes: + if utils.USE_THD89: + from trezor.crypto import se_thd89 + + return se_thd89.slip21_address_mac(slip44, address.encode()) + # k = Key(m/"SLIP-0024"/"Address MAC key") node = keychain.derive_slip21(_ADDRESS_MAC_KEY_PATH) diff --git a/core/src/apps/common/keychain.py b/core/src/apps/common/keychain.py index e7467755b2..02e8a5173a 100644 --- a/core/src/apps/common/keychain.py +++ b/core/src/apps/common/keychain.py @@ -169,9 +169,7 @@ def derive_slip21(self, path: paths.Slip21Path) -> Slip21Node: ): raise FORBIDDEN_KEY_PATH if utils.USE_THD89: - node = Slip21Node(seed=b"\x00" * 32) - node.derive_path(path) - return node + raise FORBIDDEN_KEY_PATH else: return self._derive_with_cache( prefix_len=1, diff --git a/core/src/apps/common/seed.py b/core/src/apps/common/seed.py index 1df6783db5..876ad0edd8 100644 --- a/core/src/apps/common/seed.py +++ b/core/src/apps/common/seed.py @@ -23,9 +23,7 @@ def __init__(self, seed: bytes | None = None, data: bytes | None = None) -> None if data is not None: self.data = data else: - from trezor.crypto import se_thd89 - - self.data = se_thd89.slip21_node() + raise ValueError("THD89 SLIP21 roots are not exportable") else: if seed is not None and data is not None: raise ValueError("Specify exactly one of: seed, data") @@ -199,9 +197,7 @@ def derive_fido_node_with_se( def derive_slip21_node_without_passphrase(path: Slip21Path) -> Slip21Node: if utils.USE_THD89: - from trezor.crypto import se_thd89 - - node = Slip21Node(data=se_thd89.slip21_fido_node()) + raise ValueError("THD89 SLIP21 roots are not exportable") else: seed = _get_seed_without_passphrase() node = Slip21Node(seed) From e38c6113019d34d28b62c537bf03a63f3e5696df Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Tue, 4 Aug 2026 00:48:55 +0800 Subject: [PATCH 05/11] fix: keep FIDO SLIP21 keys inside THD89 --- core/src/apps/webauthn/credential.py | 91 +++++++++++++++++++--------- core/src/apps/webauthn/fido2.py | 11 +--- 2 files changed, 65 insertions(+), 37 deletions(-) diff --git a/core/src/apps/webauthn/credential.py b/core/src/apps/webauthn/credential.py index 2265e0ea27..0ae17f3ae0 100644 --- a/core/src/apps/webauthn/credential.py +++ b/core/src/apps/webauthn/credential.py @@ -98,6 +98,9 @@ def bogus_signature(self) -> bytes: def hmac_secret_key(self) -> bytes | None: return None + def hmac_secret_output(self, salt: bytes) -> bytes | None: + return None + def next_signature_counter(self) -> int: return storage.device.next_u2f_counter() or 0 @@ -159,15 +162,19 @@ def generate_id(self) -> None: data[_CRED_ID_ALGORITHM] = self.algorithm data[_CRED_ID_CURVE] = self.curve - key = seed.derive_slip21_node_without_passphrase( - [b"SLIP-0022", _CRED_ID_VERSION, b"Encryption key"] - ).key() - iv = random.bytes(12) - ctx = chacha20poly1305(key, iv) - ctx.auth(self.rp_id_hash) - ciphertext = ctx.encrypt(cbor.encode(data)) - tag = ctx.finish() - self.id = _CRED_ID_VERSION + iv + ciphertext + tag + plaintext = cbor.encode(data) + if utils.USE_THD89: + self.id = se_thd89.fido_credential_encrypt(self.rp_id_hash, plaintext) + else: + key = seed.derive_slip21_node_without_passphrase( + [b"SLIP-0022", _CRED_ID_VERSION, b"Encryption key"] + ).key() + iv = random.bytes(12) + ctx = chacha20poly1305(key, iv) + ctx.auth(self.rp_id_hash) + ciphertext = ctx.encrypt(plaintext) + tag = ctx.finish() + self.id = _CRED_ID_VERSION + iv + ciphertext + tag if len(self.id) > CRED_ID_MAX_LENGTH: raise AssertionError @@ -178,28 +185,40 @@ def from_cred_id( ) -> "Fido2Credential": if len(cred_id) < CRED_ID_MIN_LENGTH or cred_id[0:4] != _CRED_ID_VERSION: raise ValueError # invalid length or version - key = seed.derive_slip21_node_without_passphrase( - [b"SLIP-0022", cred_id[0:4], b"Encryption key"] - ).key() - iv = cred_id[4:16] - ciphertext = cred_id[16:-16] - tag = cred_id[-16:] - - if rp_id_hash is None: + if utils.USE_THD89: + if rp_id_hash is None: + candidate_data = se_thd89.fido_credential_peek(cred_id) + try: + rp_id = cbor.decode(candidate_data)[_CRED_ID_RP_ID] + except Exception as e: + raise ValueError from e # CBOR decoding failed + rp_id_hash = hashlib.sha256(rp_id).digest() + del candidate_data + del rp_id + data = se_thd89.fido_credential_decrypt(rp_id_hash, cred_id) + else: + key = seed.derive_slip21_node_without_passphrase( + [b"SLIP-0022", cred_id[0:4], b"Encryption key"] + ).key() + iv = cred_id[4:16] + ciphertext = cred_id[16:-16] + tag = cred_id[-16:] + + if rp_id_hash is None: + ctx = chacha20poly1305(key, iv) + candidate_data = ctx.decrypt(ciphertext) + try: + rp_id = cbor.decode(candidate_data)[_CRED_ID_RP_ID] + except Exception as e: + raise ValueError from e # CBOR decoding failed + rp_id_hash = hashlib.sha256(rp_id).digest() + ctx = chacha20poly1305(key, iv) + ctx.auth(rp_id_hash) data = ctx.decrypt(ciphertext) - try: - rp_id = cbor.decode(data)[_CRED_ID_RP_ID] - except Exception as e: - raise ValueError from e # CBOR decoding failed - rp_id_hash = hashlib.sha256(rp_id).digest() - ctx = chacha20poly1305(key, iv) - ctx.auth(rp_id_hash) - data = ctx.decrypt(ciphertext) - - if not utils.consteq(ctx.finish(), tag): - raise ValueError # inauthentic ciphertext + if not utils.consteq(ctx.finish(), tag): + raise ValueError # inauthentic ciphertext try: data = cbor.decode(data) @@ -368,7 +387,7 @@ def bogus_signature(self) -> bytes: def hmac_secret_key(self) -> bytes | None: # Returns the symmetric key for the hmac-secret extension also known as CredRandom. - if not self.hmac_secret: + if not self.hmac_secret or utils.USE_THD89: return None node = seed.derive_slip21_node_without_passphrase( @@ -377,6 +396,20 @@ def hmac_secret_key(self) -> bytes | None: return node.key() + def hmac_secret_output(self, salt: bytes) -> bytes | None: + if not self.hmac_secret: + return None + if utils.USE_THD89: + return se_thd89.fido_hmac_secret(self.id, salt) + + cred_random = self.hmac_secret_key() + if cred_random is None: + return None + output = hmac(hmac.SHA256, cred_random, salt[:32]).digest() + if len(salt) == 64: + output += hmac(hmac.SHA256, cred_random, salt[32:]).digest() + return output + def next_signature_counter(self) -> int: if not self.use_sign_count: return 0 diff --git a/core/src/apps/webauthn/fido2.py b/core/src/apps/webauthn/fido2.py index 69d98381b3..515548f587 100644 --- a/core/src/apps/webauthn/fido2.py +++ b/core/src/apps/webauthn/fido2.py @@ -2073,17 +2073,12 @@ def cbor_get_assertion_hmac_secret(cred: Credential, hmac_secret: dict) -> bytes raise CborError(_ERR_EXTENSION_FIRST) salt = aes(aes.CBC, shared_secret).decrypt(salt_enc) - # Get cred_random - a constant symmetric key associated with the credential. - cred_random = cred.hmac_secret_key() - if cred_random is None: + # Compute hmac-secret without exposing CredRandom outside its key domain. + output = cred.hmac_secret_output(salt) + if output is None: # The credential does not have the hmac-secret extension enabled. return None - # Compute the hmac-secret output. - output = hmac(hmac.SHA256, cred_random, salt[:32]).digest() - if len(salt) == 64: - output += hmac(hmac.SHA256, cred_random, salt[32:]).digest() - # Encrypt the hmac-secret output. return aes(aes.CBC, shared_secret).encrypt(output) From 21a10746406a6661c9c076092d91e1ba7e6bc7b6 Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Tue, 4 Aug 2026 12:20:09 +0800 Subject: [PATCH 06/11] fix: initialize seed before THD89 SLIP25 MAC. --- core/src/apps/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/apps/base.py b/core/src/apps/base.py index 4beb4bcc5a..4c7bc453d3 100644 --- a/core/src/apps/base.py +++ b/core/src/apps/base.py @@ -416,7 +416,9 @@ async def handle_UnlockPath(ctx: wire.Context, msg: UnlockPath) -> protobuf.Mess if utils.USE_THD89: from trezor.crypto import se_thd89 + from apps.common.seed import get_seed + await get_seed(ctx) expected_mac = se_thd89.slip21_slip25_mac() else: from trezor.crypto import hmac From 553097cd73db6d4c282b0cd27e3a859348c6d53b Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Tue, 4 Aug 2026 11:08:12 +0800 Subject: [PATCH 07/11] fix: keep Monero spend keys inside THD89. --- .../modtrezorcrypto-se-thd89.h | 151 ++++++++++++++---- core/embed/trezorhal/se_thd89.c | 138 +++++++++++++--- core/embed/trezorhal/se_thd89.h | 15 +- .../mocks/generated/trezorcrypto/se_thd89.pyi | 39 +++-- core/src/apps/monero/live_refresh.py | 56 +++++-- core/src/apps/monero/misc.py | 73 +++++++++ .../apps/monero/signing/step_02_set_input.py | 50 ++++-- .../apps/monero/signing/step_09_sign_input.py | 71 ++++++-- core/src/apps/monero/xmr/clsag.py | 42 +++-- core/src/apps/monero/xmr/key_image.py | 109 +++++++++++-- core/src/apps/monero/xmr/monero.py | 24 ++- 11 files changed, 635 insertions(+), 133 deletions(-) diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h index cf70019ab8..2c42457050 100644 --- a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h @@ -20,6 +20,7 @@ #include "py/objstr.h" #include "py/runtime.h" +#include "memzero.h" #include "se_thd89.h" /// package: trezorcrypto.se_thd89 @@ -730,32 +731,6 @@ STATIC mp_obj_t mod_trezorcrypto_se_thd89_derive_xmr(mp_obj_t path) { STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_se_thd89_derive_xmr_obj, mod_trezorcrypto_se_thd89_derive_xmr); -/// def derive_xmr_privare( -/// deriv: bytes -/// index: int, -/// ) -> bytes: -/// """ -/// base + H_s(derivation || varint(output_index)) -/// """ -STATIC mp_obj_t mod_trezorcrypto_se_thd89_derive_xmr_private(mp_obj_t deriv, - mp_obj_t index) { - mp_buffer_info_t pub_key = {0}; - mp_get_buffer_raise(deriv, &pub_key, MP_BUFFER_READ); - - uint32_t idx = mp_obj_get_int(index); - - uint8_t out_pri[32]; - if (!se_derive_xmr_private_key(pub_key.buf, idx, out_pri)) { - mp_raise_ValueError("Failed to derive private key"); - } - - return mp_obj_new_str_copy(&mp_type_bytes, (const uint8_t *)out_pri, 32); -} - -STATIC MP_DEFINE_CONST_FUN_OBJ_2( - mod_trezorcrypto_se_thd89_derive_xmr_private_obj, - mod_trezorcrypto_se_thd89_derive_xmr_private); - /// def xmr_get_tx_key( /// rand: bytes /// hash: bytes, @@ -782,6 +757,122 @@ STATIC mp_obj_t mod_trezorcrypto_se_thd89_xmr_get_tx_key(mp_obj_t rand, STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_se_thd89_xmr_get_tx_key_obj, mod_trezorcrypto_se_thd89_xmr_get_tx_key); +/// def xmr_generate_key_image( +/// recv_deriv: bytes, +/// real_idx: int, +/// subaddr_sk: bytes, +/// out_key: bytes, +/// ) -> bytes: +/// """Generates a key image without exporting the one-time spend key.""" +STATIC mp_obj_t mod_trezorcrypto_se_thd89_xmr_generate_key_image( + size_t n_args, const mp_obj_t *args) { + mp_buffer_info_t recv_deriv = {0}; + mp_buffer_info_t subaddr_sk = {0}; + mp_buffer_info_t out_key = {0}; + uint8_t key_image[32] = {0}; + + mp_get_buffer_raise(args[0], &recv_deriv, MP_BUFFER_READ); + mp_get_buffer_raise(args[2], &subaddr_sk, MP_BUFFER_READ); + mp_get_buffer_raise(args[3], &out_key, MP_BUFFER_READ); + if (recv_deriv.len != 32 || subaddr_sk.len != 32 || out_key.len != 32) { + mp_raise_ValueError("Invalid XMR key data length"); + } + + uint32_t real_idx = trezor_obj_get_uint(args[1]); + if (se_xmr_generate_key_image(recv_deriv.buf, real_idx, subaddr_sk.buf, + out_key.buf, key_image) != sectrue) { + mp_raise_ValueError("Failed to generate XMR key image"); + } + + mp_obj_t result = mp_obj_new_bytes(key_image, sizeof(key_image)); + memzero(key_image, sizeof(key_image)); + return result; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( + mod_trezorcrypto_se_thd89_xmr_generate_key_image_obj, 4, 4, + mod_trezorcrypto_se_thd89_xmr_generate_key_image); + +/// def xmr_secret_nonce_begin( +/// recv_deriv: bytes, +/// real_idx: int, +/// subaddr_sk: bytes, +/// out_key: bytes, +/// ) -> tuple[bytes, bytes, bytes, int]: +/// """Starts a one-time XMR secret-response session.""" +STATIC mp_obj_t mod_trezorcrypto_se_thd89_xmr_secret_nonce_begin( + size_t n_args, const mp_obj_t *args) { + mp_buffer_info_t recv_deriv = {0}; + mp_buffer_info_t subaddr_sk = {0}; + mp_buffer_info_t out_key = {0}; + uint8_t response[97] = {0}; + + mp_get_buffer_raise(args[0], &recv_deriv, MP_BUFFER_READ); + mp_get_buffer_raise(args[2], &subaddr_sk, MP_BUFFER_READ); + mp_get_buffer_raise(args[3], &out_key, MP_BUFFER_READ); + if (recv_deriv.len != 32 || subaddr_sk.len != 32 || out_key.len != 32) { + mp_raise_ValueError("Invalid XMR key data length"); + } + + uint32_t real_idx = trezor_obj_get_uint(args[1]); + if (se_xmr_secret_nonce_begin(recv_deriv.buf, real_idx, subaddr_sk.buf, + out_key.buf, response) != sectrue) { + mp_raise_ValueError("Failed to start XMR secret response"); + } + + mp_obj_tuple_t *result = MP_OBJ_TO_PTR(mp_obj_new_tuple(4, NULL)); + result->items[0] = mp_obj_new_bytes(response, 32); + result->items[1] = mp_obj_new_bytes(response + 32, 32); + result->items[2] = mp_obj_new_bytes(response + 64, 32); + result->items[3] = MP_OBJ_NEW_SMALL_INT(response[96]); + memzero(response, sizeof(response)); + return MP_OBJ_FROM_PTR(result); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( + mod_trezorcrypto_se_thd89_xmr_secret_nonce_begin_obj, 4, 4, + mod_trezorcrypto_se_thd89_xmr_secret_nonce_begin); + +/// def xmr_secret_response_finish( +/// session_id: int, +/// c: bytes, +/// mu_p: bytes, +/// mu_c: bytes, +/// z: bytes, +/// ) -> bytes: +/// """Finishes a one-time XMR secret-response session.""" +STATIC mp_obj_t mod_trezorcrypto_se_thd89_xmr_secret_response_finish( + size_t n_args, const mp_obj_t *args) { + mp_buffer_info_t c = {0}; + mp_buffer_info_t mu_p = {0}; + mp_buffer_info_t mu_c = {0}; + mp_buffer_info_t z = {0}; + uint8_t response[32] = {0}; + + uint32_t session_id = trezor_obj_get_uint(args[0]); + if (session_id == 0 || session_id > 0xFF) { + mp_raise_ValueError("Invalid XMR session ID"); + } + + mp_get_buffer_raise(args[1], &c, MP_BUFFER_READ); + mp_get_buffer_raise(args[2], &mu_p, MP_BUFFER_READ); + mp_get_buffer_raise(args[3], &mu_c, MP_BUFFER_READ); + mp_get_buffer_raise(args[4], &z, MP_BUFFER_READ); + if (c.len != 32 || mu_p.len != 32 || mu_c.len != 32 || z.len != 32) { + mp_raise_ValueError("Invalid XMR scalar length"); + } + + if (se_xmr_secret_response_finish((uint8_t)session_id, c.buf, mu_p.buf, + mu_c.buf, z.buf, response) != sectrue) { + mp_raise_ValueError("Failed to finish XMR secret response"); + } + + mp_obj_t result = mp_obj_new_bytes(response, sizeof(response)); + memzero(response, sizeof(response)); + return result; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( + mod_trezorcrypto_se_thd89_xmr_secret_response_finish_obj, 5, 5, + mod_trezorcrypto_se_thd89_xmr_secret_response_finish); + /// def fido_seed( /// callback: Callable[[int, int], None] | None = None, /// ) -> bool: @@ -1326,10 +1417,14 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_se_thd89_globals_table[] = { MP_ROM_PTR(&mod_trezorcrypto_se_thd89_sign_message_obj)}, {MP_ROM_QSTR(MP_QSTR_derive_xmr), MP_ROM_PTR(&mod_trezorcrypto_se_thd89_derive_xmr_obj)}, - {MP_ROM_QSTR(MP_QSTR_derive_xmr_private), - MP_ROM_PTR(&mod_trezorcrypto_se_thd89_derive_xmr_private_obj)}, {MP_ROM_QSTR(MP_QSTR_xmr_get_tx_key), MP_ROM_PTR(&mod_trezorcrypto_se_thd89_xmr_get_tx_key_obj)}, + {MP_ROM_QSTR(MP_QSTR_xmr_generate_key_image), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_xmr_generate_key_image_obj)}, + {MP_ROM_QSTR(MP_QSTR_xmr_secret_nonce_begin), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_xmr_secret_nonce_begin_obj)}, + {MP_ROM_QSTR(MP_QSTR_xmr_secret_response_finish), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_xmr_secret_response_finish_obj)}, {MP_ROM_QSTR(MP_QSTR_fido_seed), MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_seed_obj)}, {MP_ROM_QSTR(MP_QSTR_fido_u2f_register), diff --git a/core/embed/trezorhal/se_thd89.c b/core/embed/trezorhal/se_thd89.c index 991acb90e0..55f989c1d2 100644 --- a/core/embed/trezorhal/se_thd89.c +++ b/core/embed/trezorhal/se_thd89.c @@ -750,25 +750,6 @@ secbool se_derive_xmr_key(const char *curve, const uint32_t *address_n, return sectrue; } -secbool se_derive_xmr_private_key(const uint8_t *pubkey, const uint32_t index, - uint8_t *prikey) { - uint8_t resp[32]; - uint16_t resp_len = sizeof(resp); - - uint8_t data[32 + 4]; - - memcpy(data, pubkey, 32); - memcpy(data + 32, &index, 4); - - if (!se_transmit_mac(SE_INS_DERIVE, 0x00, 0x02, data, sizeof(data), resp, - &resp_len)) { - return secfalse; - } - memcpy(prikey, resp, 32); - - return sectrue; -} - secbool se_xmr_get_tx_key(const uint8_t *rand, const uint8_t *hash, uint8_t *out) { uint8_t resp[32]; @@ -788,6 +769,125 @@ secbool se_xmr_get_tx_key(const uint8_t *rand, const uint8_t *hash, return sectrue; } +secbool se_xmr_generate_key_image(const uint8_t recv_deriv[32], + uint32_t real_idx, + const uint8_t subaddr_sk[32], + const uint8_t out_key[32], + uint8_t key_image[32]) { + uint8_t data[100] = {0}; + uint8_t response[32] = {0}; + uint16_t response_len = sizeof(response); + secbool result = secfalse; + + if (recv_deriv == NULL || subaddr_sk == NULL || out_key == NULL || + key_image == NULL) { + goto cleanup; + } + + memcpy(data, recv_deriv, 32); + data[32] = (uint8_t)real_idx; + data[33] = (uint8_t)(real_idx >> 8); + data[34] = (uint8_t)(real_idx >> 16); + data[35] = (uint8_t)(real_idx >> 24); + memcpy(data + 36, subaddr_sk, 32); + memcpy(data + 68, out_key, 32); + + if (se_transmit_mac(SE_INS_DERIVE, 0x00, 0x06, data, sizeof(data), + response, &response_len) != sectrue || + response_len != sizeof(response)) { + goto cleanup; + } + + memcpy(key_image, response, sizeof(response)); + result = sectrue; + +cleanup: + if (result != sectrue && key_image != NULL) { + memzero(key_image, 32); + } + memzero(data, sizeof(data)); + memzero(response, sizeof(response)); + return result; +} + +secbool se_xmr_secret_nonce_begin(const uint8_t recv_deriv[32], + uint32_t real_idx, + const uint8_t subaddr_sk[32], + const uint8_t out_key[32], uint8_t out[97]) { + uint8_t data[100] = {0}; + uint8_t response[97] = {0}; + uint16_t response_len = sizeof(response); + secbool result = secfalse; + + if (recv_deriv == NULL || subaddr_sk == NULL || out_key == NULL || + out == NULL) { + goto cleanup; + } + + memcpy(data, recv_deriv, 32); + data[32] = (uint8_t)real_idx; + data[33] = (uint8_t)(real_idx >> 8); + data[34] = (uint8_t)(real_idx >> 16); + data[35] = (uint8_t)(real_idx >> 24); + memcpy(data + 36, subaddr_sk, 32); + memcpy(data + 68, out_key, 32); + + if (se_transmit_mac(SE_INS_DERIVE, 0x00, 0x07, data, sizeof(data), + response, &response_len) != sectrue || + response_len != sizeof(response) || response[96] == 0) { + goto cleanup; + } + + memcpy(out, response, sizeof(response)); + result = sectrue; + +cleanup: + if (result != sectrue && out != NULL) { + memzero(out, 97); + } + memzero(data, sizeof(data)); + memzero(response, sizeof(response)); + return result; +} + +secbool se_xmr_secret_response_finish(uint8_t session_id, const uint8_t c[32], + const uint8_t mu_p[32], + const uint8_t mu_c[32], + const uint8_t z[32], uint8_t s[32]) { + uint8_t data[129] = {0}; + uint8_t response[32] = {0}; + uint16_t response_len = sizeof(response); + secbool result = secfalse; + + if (session_id == 0 || c == NULL || mu_p == NULL || mu_c == NULL || + z == NULL || s == NULL) { + goto cleanup; + } + + data[0] = session_id; + memcpy(data + 1, c, 32); + memcpy(data + 33, mu_p, 32); + memcpy(data + 65, mu_c, 32); + memcpy(data + 97, z, 32); + + if (se_transmit_mac(SE_INS_DERIVE, 0x00, 0x08, data, sizeof(data), + response, &response_len) != sectrue || + response_len != sizeof(response)) { + goto cleanup; + } + + memcpy(s, response, sizeof(response)); + result = sectrue; + +cleanup: + if (result != sectrue && s != NULL) { + memzero(s, 32); + } + memzero(data, sizeof(data)); + memzero(response, sizeof(response)); + return result; +} + static secbool _se_reset_storage(void) { uint8_t rand[16]; diff --git a/core/embed/trezorhal/se_thd89.h b/core/embed/trezorhal/se_thd89.h index b197ef2989..09c178e64c 100644 --- a/core/embed/trezorhal/se_thd89.h +++ b/core/embed/trezorhal/se_thd89.h @@ -176,10 +176,21 @@ secbool se_derive_keys(HDNode *out, const char *curve, secbool se_derive_xmr_key(const char *curve, const uint32_t *address_n, size_t address_n_count, uint8_t *pubkey, uint8_t *prikey_hash); -secbool se_derive_xmr_private_key(const uint8_t *pubkey, const uint32_t index, - uint8_t *prikey); secbool se_xmr_get_tx_key(const uint8_t *rand, const uint8_t *hash, uint8_t *out); +secbool se_xmr_generate_key_image(const uint8_t recv_deriv[32], + uint32_t real_idx, + const uint8_t subaddr_sk[32], + const uint8_t out_key[32], + uint8_t key_image[32]); +secbool se_xmr_secret_nonce_begin(const uint8_t recv_deriv[32], + uint32_t real_idx, + const uint8_t subaddr_sk[32], + const uint8_t out_key[32], uint8_t out[97]); +secbool se_xmr_secret_response_finish(uint8_t session_id, const uint8_t c[32], + const uint8_t mu_p[32], + const uint8_t mu_c[32], + const uint8_t z[32], uint8_t s[32]); secbool se_node_sign_digest(const uint8_t *hash, uint8_t *sig, uint8_t *by); int se_ecdsa_sign_digest(const uint8_t curve, const uint8_t canonical, const uint8_t *digest, uint8_t *sig, uint8_t *pby); diff --git a/core/mocks/generated/trezorcrypto/se_thd89.pyi b/core/mocks/generated/trezorcrypto/se_thd89.pyi index 9b2c9f8d2f..78f9b972bf 100644 --- a/core/mocks/generated/trezorcrypto/se_thd89.pyi +++ b/core/mocks/generated/trezorcrypto/se_thd89.pyi @@ -223,9 +223,9 @@ def derive_xmr( # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h -def derive_xmr_privare( - deriv: bytes - index: int, +def xmr_get_tx_key( + rand: bytes + hash: bytes, ) -> bytes: """ base + H_s(derivation || varint(output_index)) @@ -233,13 +233,34 @@ def derive_xmr_privare( # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h -def xmr_get_tx_key( - rand: bytes - hash: bytes, +def xmr_generate_key_image( + recv_deriv: bytes, + real_idx: int, + subaddr_sk: bytes, + out_key: bytes, ) -> bytes: - """ - base + H_s(derivation || varint(output_index)) - """ + """Generates a key image without exporting the one-time spend key.""" + + +# extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +def xmr_secret_nonce_begin( + recv_deriv: bytes, + real_idx: int, + subaddr_sk: bytes, + out_key: bytes, +) -> tuple[bytes, bytes, bytes, int]: + """Starts a one-time XMR secret-response session.""" + + +# extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +def xmr_secret_response_finish( + session_id: int, + c: bytes, + mu_p: bytes, + mu_c: bytes, + z: bytes, +) -> bytes: + """Finishes a one-time XMR secret-response session.""" # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h diff --git a/core/src/apps/monero/live_refresh.py b/core/src/apps/monero/live_refresh.py index 92bc9e9549..911bbb8f45 100644 --- a/core/src/apps/monero/live_refresh.py +++ b/core/src/apps/monero/live_refresh.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING import storage.cache -from trezor import log +from trezor import log, utils from trezor.enums import MessageType from trezor.messages import ( MoneroLiveRefreshFinalAck, @@ -83,21 +83,53 @@ async def _refresh_step( if __debug__: log.debug(__name__, "refresh, step i: %d", s.current_output) - # Compute spending secret key and the key image - # spend_priv = Hs(recv_deriv || real_out_idx) + spend_key_private - # If subaddr: - # spend_priv += Hs("SubAddr" || view_key_private || major || minor) - # out_key = spend_priv * G, KI: spend_priv * Hp(out_key) out_key = crypto_helpers.decodepoint(msg.out_key) recv_deriv = crypto_helpers.decodepoint(msg.recv_deriv) received_index = msg.sub_addr_major, msg.sub_addr_minor - spend_priv, ki = monero.generate_tx_spend_and_key_image( - s.creds, out_key, recv_deriv, msg.real_out_idx, received_index - ) - ki_enc = crypto_helpers.encodepoint(ki) - sig = key_image.generate_ring_signature(ki_enc, ki, [out_key], spend_priv, 0, False) - del spend_priv # spend_priv never leaves the device + if utils.USE_THD89: + from trezor.crypto import se_thd89 + + aG, aH, ki_enc, session_id = se_thd89.xmr_secret_nonce_begin( + msg.recv_deriv, + msg.real_out_idx, + misc.xmr_subaddress_secret_key( + s.creds.view_key_private, received_index + ), + msg.out_key, + ) + ki = crypto_helpers.decodepoint(ki_enc) + + def se_response(c: crypto.Scalar) -> bytes: + return se_thd89.xmr_secret_response_finish( + session_id, + crypto_helpers.encodeint(c), + crypto_helpers.encodeint(crypto.Scalar(1)), + crypto_helpers.encodeint(crypto.Scalar(0)), + crypto_helpers.encodeint(crypto.Scalar(0)), + ) + + sig = key_image.generate_ring_signature( + ki_enc, + ki, + [out_key], + crypto.Scalar(), + 0, + False, + (aG, aH, se_response), + ) + else: + # Compute spending secret key and the key image. + spend_priv, ki = monero.generate_tx_spend_and_key_image( + s.creds, out_key, recv_deriv, msg.real_out_idx, received_index + ) + if spend_priv is None: + raise RuntimeError("XMR spend key missing") + ki_enc = crypto_helpers.encodepoint(ki) + sig = key_image.generate_ring_signature( + ki_enc, ki, [out_key], spend_priv, 0, False + ) + del spend_priv # Serialize into buff buff[0:32] = ki_enc diff --git a/core/src/apps/monero/misc.py b/core/src/apps/monero/misc.py index 751981e783..d992e62889 100644 --- a/core/src/apps/monero/misc.py +++ b/core/src/apps/monero/misc.py @@ -9,6 +9,79 @@ from .xmr.crypto import Scalar from .xmr.credentials import AccountCreds +_XMR_SE_INPUT_TOKEN_MAGIC = b"XMRSE01" +_XMR_SE_INPUT_TOKEN_LEN = len(_XMR_SE_INPUT_TOKEN_MAGIC) + 32 + 4 + 32 + 32 + + +def encode_xmr_se_input_token( + recv_deriv: bytes, real_idx: int, subaddr_sk: bytes, out_key: bytes +) -> bytearray: + if len(recv_deriv) != 32 or len(subaddr_sk) != 32 or len(out_key) != 32: + raise ValueError("Invalid XMR SE token key length") + if real_idx < 0 or real_idx > 0xFFFFFFFF: + raise ValueError("Invalid XMR output index") + + token = bytearray(_XMR_SE_INPUT_TOKEN_LEN) + offset = 0 + token[offset : offset + len(_XMR_SE_INPUT_TOKEN_MAGIC)] = ( + _XMR_SE_INPUT_TOKEN_MAGIC + ) + offset += len(_XMR_SE_INPUT_TOKEN_MAGIC) + token[offset : offset + 32] = recv_deriv + offset += 32 + token[offset : offset + 4] = bytes( + ( + real_idx & 0xFF, + (real_idx >> 8) & 0xFF, + (real_idx >> 16) & 0xFF, + (real_idx >> 24) & 0xFF, + ) + ) + offset += 4 + token[offset : offset + 32] = subaddr_sk + offset += 32 + token[offset : offset + 32] = out_key + return token + + +def decode_xmr_se_input_token(token: bytes) -> tuple[bytes, int, bytes, bytes]: + if len(token) != _XMR_SE_INPUT_TOKEN_LEN: + raise ValueError("Invalid XMR SE token length") + if token[: len(_XMR_SE_INPUT_TOKEN_MAGIC)] != _XMR_SE_INPUT_TOKEN_MAGIC: + raise ValueError("Invalid XMR SE token magic") + + offset = len(_XMR_SE_INPUT_TOKEN_MAGIC) + recv_deriv = token[offset : offset + 32] + offset += 32 + real_idx = ( + token[offset] + | (token[offset + 1] << 8) + | (token[offset + 2] << 16) + | (token[offset + 3] << 24) + ) + offset += 4 + subaddr_sk = token[offset : offset + 32] + offset += 32 + out_key = token[offset : offset + 32] + return recv_deriv, real_idx, subaddr_sk, out_key + + +def xmr_subaddress_secret_key( + view_key_private: Scalar, received_index: tuple[int, int] +) -> bytes: + if received_index == (0, 0): + return b"\x00" * 32 + + from apps.monero.xmr import crypto_helpers, monero + + return crypto_helpers.encodeint( + monero.get_subaddress_secret_key( + view_key_private, + major=received_index[0], + minor=received_index[1], + ) + ) + def get_creds( keychain: Keychain, address_n: Bip32Path, network_type: MoneroNetworkType diff --git a/core/src/apps/monero/signing/step_02_set_input.py b/core/src/apps/monero/signing/step_02_set_input.py index 1f5c9a554e..8cc4277956 100644 --- a/core/src/apps/monero/signing/step_02_set_input.py +++ b/core/src/apps/monero/signing/step_02_set_input.py @@ -13,7 +13,7 @@ """ from typing import TYPE_CHECKING -from apps.monero import layout +from apps.monero import layout, misc from apps.monero.xmr import crypto, crypto_helpers, monero, serialize from .state import State @@ -57,15 +57,17 @@ async def set_input( # Calculates `derivation = Ra`, private spend key `x = H(Ra||i) + b` to be able # to spend the UTXO; and key image `I = x*H(P||i)` - xi, ki, _di = monero.generate_tx_spend_and_key_image_and_derivation( - state.creds, - state.subaddresses, - out_key, - tx_key, - additional_tx_pub_key, - src_entr.real_output_in_tx_index, - state.account_idx, - src_entr.subaddr_minor, + xi, ki, recv_derivation, received_index = ( + monero.generate_tx_spend_and_key_image_and_derivation( + state.creds, + state.subaddresses, + out_key, + tx_key, + additional_tx_pub_key, + src_entr.real_output_in_tx_index, + state.account_idx, + src_entr.subaddr_minor, + ) ) state.mem_trace(1, True) @@ -106,10 +108,30 @@ async def set_input( crypto_helpers.encodeint(alpha), ) - spend_enc = chacha_poly.encrypt_pack( - offloading_keys.enc_key_spend(state.key_enc, state.current_input_index), - crypto_helpers.encodeint(xi), - ) + if xi is None: + spend_plain = misc.encode_xmr_se_input_token( + crypto_helpers.encodepoint(recv_derivation), + src_entr.real_output_in_tx_index, + misc.xmr_subaddress_secret_key( + state.creds.view_key_private, received_index + ), + crypto_helpers.encodepoint(out_key), + ) + spend_enc = chacha_poly.encrypt_pack( + offloading_keys.enc_key_spend( + state.key_enc, state.current_input_index + ), + spend_plain, + ) + for i in range(len(spend_plain)): + spend_plain[i] = 0 + else: + spend_enc = chacha_poly.encrypt_pack( + offloading_keys.enc_key_spend( + state.key_enc, state.current_input_index + ), + crypto_helpers.encodeint(xi), + ) state.last_step = state.STEP_INP if state.current_input_index + 1 == state.input_count: diff --git a/core/src/apps/monero/signing/step_09_sign_input.py b/core/src/apps/monero/signing/step_09_sign_input.py index 4f401b4bfe..c2e3d05358 100644 --- a/core/src/apps/monero/signing/step_09_sign_input.py +++ b/core/src/apps/monero/signing/step_09_sign_input.py @@ -15,7 +15,7 @@ from trezor import utils -from apps.monero import layout +from apps.monero import layout, misc from apps.monero.xmr import crypto, crypto_helpers from .state import State @@ -79,6 +79,7 @@ async def sign_input( raise ValueError("Key image order invalid") state.last_ki = cur_ki if state.current_input_index < state.input_count else None + expected_ki = cur_ki del (cur_ki, vini_bin, vini_hmac, vini_hmac_comp) gc.collect() @@ -122,13 +123,17 @@ async def sign_input( state.mem_trace(2, True) - # Spending secret - spend_key = crypto_helpers.decodeint( - chacha_poly.decrypt_pack( - offloading_keys.enc_key_spend(state.key_enc, input_position), - bytes(spend_enc), - ) + spend_plain = chacha_poly.decrypt_pack( + offloading_keys.enc_key_spend(state.key_enc, input_position), + bytes(spend_enc), ) + if utils.USE_THD89: + recv_deriv, real_out_idx, subaddr_sk, se_out_key = ( + misc.decode_xmr_se_input_token(spend_plain) + ) + spend_key = crypto.Scalar() + else: + spend_key = crypto_helpers.decodeint(spend_plain) del ( offloading_keys, @@ -147,14 +152,21 @@ async def sign_input( index = src_entr.real_output input_secret_key = CtKey(spend_key, crypto_helpers.decodeint(src_entr.mask)) - # Private key correctness test - utils.ensure( - crypto.point_eq( - crypto_helpers.decodepoint(src_entr.outputs[src_entr.real_output].key.dest), - crypto.scalarmult_base_into(None, input_secret_key.dest), - ), - "Real source entry's destination does not equal spend key's", - ) + real_out_key = src_entr.outputs[src_entr.real_output].key.dest + if utils.USE_THD89: + utils.ensure( + crypto.ct_equals(real_out_key, se_out_key), + "Real source entry's destination does not equal SE token's", + ) + else: + # Private key correctness test + utils.ensure( + crypto.point_eq( + crypto_helpers.decodepoint(real_out_key), + crypto.scalarmult_base_into(None, input_secret_key.dest), + ), + "Real source entry's destination does not equal spend key's", + ) utils.ensure( crypto.point_eq( crypto_helpers.decodepoint( @@ -167,6 +179,34 @@ async def sign_input( state.mem_trace(4, True) + se_secret = None + if utils.USE_THD89: + from trezor.crypto import se_thd89 + + aG, aH, key_image, session_id = se_thd89.xmr_secret_nonce_begin( + recv_deriv, real_out_idx, subaddr_sk, se_out_key + ) + utils.ensure( + crypto.ct_equals(key_image, expected_ki), + "SE key image does not equal vini's", + ) + + def se_response( + c: crypto.Scalar, + mu_p: crypto.Scalar, + mu_c: crypto.Scalar, + z: crypto.Scalar, + ) -> bytes: + return se_thd89.xmr_secret_response_finish( + session_id, + crypto_helpers.encodeint(c), + crypto_helpers.encodeint(mu_p), + crypto_helpers.encodeint(mu_c), + crypto_helpers.encodeint(z), + ) + + se_secret = (aG, aH, key_image, se_response) + from apps.monero.xmr import clsag mg_buffer = [] @@ -187,6 +227,7 @@ async def sign_input( pseudo_out_c, index, mg_buffer, + se_secret, ) del (CtKey, input_secret_key, pseudo_out_alpha, clsag, ring_pubkeys) diff --git a/core/src/apps/monero/xmr/clsag.py b/core/src/apps/monero/xmr/clsag.py index c2daa86b3f..2ea98c8bf5 100644 --- a/core/src/apps/monero/xmr/clsag.py +++ b/core/src/apps/monero/xmr/clsag.py @@ -49,7 +49,7 @@ from apps.monero.xmr.serialize import int_serialize if TYPE_CHECKING: - from typing import Any, TypeGuard, TypeVar + from typing import Any, Callable, TypeGuard, TypeVar from .serialize_messages.tx_ct_key import CtKey from trezor.messages import MoneroRctKeyPublic @@ -59,6 +59,10 @@ def list_of_type(lst: list[Any], typ: type[T]) -> TypeGuard[list[T]]: ... + SecretResponse = Callable[ + [crypto.Scalar, crypto.Scalar, crypto.Scalar, crypto.Scalar], bytes + ] + _HASH_KEY_CLSAG_ROUND = b"CLSAG_round\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" _HASH_KEY_CLSAG_AGG_0 = b"CLSAG_agg_0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" @@ -73,6 +77,7 @@ def generate_clsag_simple( cout: crypto.Point, index: int, mg_buff: list[bytearray], + se_secret: tuple[bytes, bytes, bytes, "SecretResponse"] | None = None, ) -> list[bytes]: """ CLSAG for RctType.Simple @@ -105,7 +110,9 @@ def generate_clsag_simple( del pubs gc.collect() - return _generate_clsag(message, P, p, C_nonzero, z, cout, index, mg_buff) + return _generate_clsag( + message, P, p, C_nonzero, z, cout, index, mg_buff, se_secret + ) def _generate_clsag( @@ -117,11 +124,12 @@ def _generate_clsag( Cout: crypto.Point, index: int, mg_buff: list[bytearray], + se_secret: tuple[bytes, bytes, bytes, "SecretResponse"] | None = None, ) -> list[bytes]: sI = crypto.Point() # sig.I sD = crypto.Point() # sig.D sc1 = crypto.Scalar() # sig.c1 - a = crypto.random_scalar() + a = crypto.random_scalar() if se_secret is None else crypto.Scalar() H = crypto.Point() D = crypto.Point() Cout_bf = crypto_helpers.encodepoint(Cout) @@ -131,7 +139,10 @@ def _generate_clsag( tmp_bf = bytearray(32) crypto.hash_to_point_into(H, P[index]) - crypto.scalarmult_into(sI, H, p) # I = p*H + if se_secret is None: + crypto.scalarmult_into(sI, H, p) # I = p*H + else: + crypto.decodepoint_into(sI, se_secret[2]) crypto.scalarmult_into(D, H, z) # D = z*H crypto.sc_mul_into(tmp_sc, z, crypto_helpers.INV_EIGHT_SC) # 1/8*z crypto.scalarmult_into(sD, H, tmp_sc) # sig.D = 1/8*z*H @@ -170,10 +181,14 @@ def hsh_PC(x): c_to_hash.update(message) chasher = c_to_hash.copy() - crypto.scalarmult_base_into(tmp, a) - chasher.update(crypto.encodepoint_into(tmp_bf, tmp)) # aG - crypto.scalarmult_into(tmp, H, a) - chasher.update(crypto.encodepoint_into(tmp_bf, tmp)) # aH + if se_secret is None: + crypto.scalarmult_base_into(tmp, a) + chasher.update(crypto.encodepoint_into(tmp_bf, tmp)) # aG + crypto.scalarmult_into(tmp, H, a) + chasher.update(crypto.encodepoint_into(tmp_bf, tmp)) # aH + else: + chasher.update(se_secret[0]) + chasher.update(se_secret[1]) c = crypto_helpers.decodeint(chasher.digest()) del (chasher, H) @@ -224,10 +239,13 @@ def hsh_PC(x): gc.collect() # Final scalar = a - c * (mu_P * p + mu_c * Z) - crypto.sc_mul_into(tmp_sc, mu_P, p) - crypto.sc_muladd_into(tmp_sc, mu_C, z, tmp_sc) - crypto.sc_mulsub_into(tmp_sc, c, tmp_sc, a) - crypto.encodeint_into(mg_buff[index + 1], tmp_sc) + if se_secret is None: + crypto.sc_mul_into(tmp_sc, mu_P, p) + crypto.sc_muladd_into(tmp_sc, mu_C, z, tmp_sc) + crypto.sc_mulsub_into(tmp_sc, c, tmp_sc, a) + crypto.encodeint_into(mg_buff[index + 1], tmp_sc) + else: + mg_buff[index + 1][:] = se_secret[3](c, mu_P, mu_C, z) if TYPE_CHECKING: assert list_of_type(mg_buff, bytes) diff --git a/core/src/apps/monero/xmr/key_image.py b/core/src/apps/monero/xmr/key_image.py index 305b6c7150..028b60be80 100644 --- a/core/src/apps/monero/xmr/key_image.py +++ b/core/src/apps/monero/xmr/key_image.py @@ -4,11 +4,14 @@ from apps.monero.xmr.serialize.int_serialize import dump_uvarint_b if TYPE_CHECKING: + from typing import Callable + from apps.monero.xmr.credentials import AccountCreds from trezor.messages import MoneroTransferDetails Subaddresses = dict[bytes, tuple[int, int]] Sig = list[list[crypto.Scalar]] + SecretResponse = Callable[[crypto.Scalar], bytes] def compute_hash(rr: MoneroTransferDetails) -> bytes: @@ -66,6 +69,67 @@ def _export_key_image( """ Generates key image for the TXO + signature for the key image """ + from trezor import utils + + if utils.USE_THD89: + from apps.monero import misc + from trezor.crypto import se_thd89 + + recv_derivation = crypto_helpers.generate_key_derivation( + tx_pub_key, creds.view_key_private + ) + additional_recv_derivation = ( + crypto_helpers.generate_key_derivation( + additional_tx_pub_key, creds.view_key_private + ) + if additional_tx_pub_key + else None + ) + subaddr_recv_info = monero.is_out_to_account( + subaddresses, + pkey, + recv_derivation, + additional_recv_derivation, + out_idx, + creds, + sub_addr_major, + sub_addr_minor, + ) + if subaddr_recv_info is None: + raise monero.XmrNoSuchAddressException("No such addr") + + received_index, derivation = subaddr_recv_info + out_key = crypto_helpers.encodepoint(pkey) + aG, aH, key_image, session_id = se_thd89.xmr_secret_nonce_begin( + crypto_helpers.encodepoint(derivation), + out_idx, + misc.xmr_subaddress_secret_key( + creds.view_key_private, received_index + ), + out_key, + ) + ki = crypto_helpers.decodepoint(key_image) + + def se_response(c: crypto.Scalar) -> bytes: + return se_thd89.xmr_secret_response_finish( + session_id, + crypto_helpers.encodeint(c), + crypto_helpers.encodeint(crypto.Scalar(1)), + crypto_helpers.encodeint(crypto.Scalar(0)), + crypto_helpers.encodeint(crypto.Scalar(0)), + ) + + sig = generate_ring_signature( + key_image, + ki, + [pkey], + crypto.Scalar(), + 0, + test, + (aG, aH, se_response), + ) + return ki, sig + r = monero.generate_tx_spend_and_key_image_and_derivation( creds, subaddresses, @@ -77,6 +141,8 @@ def _export_key_image( sub_addr_minor, ) xi, ki, _ = r[:3] + if xi is None: + raise RuntimeError("XMR spend key missing") phash = crypto_helpers.encodepoint(ki) sig = generate_ring_signature(phash, ki, [pkey], xi, 0, test) @@ -91,6 +157,7 @@ def generate_ring_signature( sec: crypto.Scalar, sec_idx: int, test: bool = False, + se_secret: tuple[bytes, bytes, "SecretResponse"] | None = None, ) -> Sig: """ Generates ring signature with key image. @@ -99,15 +166,18 @@ def generate_ring_signature( from trezor.utils import memcpy if test: - t = crypto.scalarmult_base_into(None, sec) - if not crypto.point_eq(t, pubs[sec_idx]): - raise ValueError("Invalid sec key") - - k_i = monero.generate_key_image(crypto_helpers.encodepoint(pubs[sec_idx]), sec) - if not crypto.point_eq(k_i, image): - raise ValueError("Key image invalid") for k in pubs: crypto.ge25519_check(k) + if se_secret is None: + t = crypto.scalarmult_base_into(None, sec) + if not crypto.point_eq(t, pubs[sec_idx]): + raise ValueError("Invalid sec key") + + k_i = monero.generate_key_image( + crypto_helpers.encodepoint(pubs[sec_idx]), sec + ) + if not crypto.point_eq(k_i, image): + raise ValueError("Key image invalid") buff_off = len(prefix_hash) buff = bytearray(buff_off + 2 * 32 * len(pubs)) @@ -123,14 +193,22 @@ def generate_ring_signature( for i in range(len(pubs)): if i == sec_idx: - k = crypto.random_scalar() - tmp3 = crypto.scalarmult_base_into(None, k) - crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp3) + if se_secret is None: + k = crypto.random_scalar() + tmp3 = crypto.scalarmult_base_into(None, k) + crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp3) + else: + mvbuff[buff_off : buff_off + 32] = se_secret[0] buff_off += 32 - tmp3 = crypto.hash_to_point_into(None, crypto_helpers.encodepoint(pubs[i])) - tmp2 = crypto.scalarmult_into(None, tmp3, k) - crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2) + if se_secret is None: + tmp3 = crypto.hash_to_point_into( + None, crypto_helpers.encodepoint(pubs[i]) + ) + tmp2 = crypto.scalarmult_into(None, tmp3, k) + crypto.encodepoint_into(mvbuff[buff_off : buff_off + 32], tmp2) + else: + mvbuff[buff_off : buff_off + 32] = se_secret[1] buff_off += 32 else: @@ -151,5 +229,8 @@ def generate_ring_signature( h = crypto.hash_to_scalar_into(None, buff) sig[sec_idx][0] = crypto.sc_sub_into(None, h, sum) - sig[sec_idx][1] = crypto.sc_mulsub_into(None, sig[sec_idx][0], sec, k) + if se_secret is None: + sig[sec_idx][1] = crypto.sc_mulsub_into(None, sig[sec_idx][0], sec, k) + else: + sig[sec_idx][1] = crypto_helpers.decodeint(se_secret[2](sig[sec_idx][0])) return sig diff --git a/core/src/apps/monero/xmr/monero.py b/core/src/apps/monero/xmr/monero.py index f50446db62..4bfaa0d399 100644 --- a/core/src/apps/monero/xmr/monero.py +++ b/core/src/apps/monero/xmr/monero.py @@ -131,7 +131,7 @@ def generate_tx_spend_and_key_image( recv_derivation: crypto.Point, real_output_index: int, received_index: tuple[int, int], -) -> tuple[crypto.Scalar, crypto.Point]: +) -> tuple[crypto.Scalar | None, crypto.Point]: """ Generates UTXO spending key and key image. Corresponds to generate_key_image_helper_precomp() in the Monero codebase. @@ -147,12 +147,18 @@ def generate_tx_spend_and_key_image( from trezor import utils if utils.USE_THD89: + from apps.monero import misc from trezor.crypto import se_thd89 - recv_derivation = crypto_helpers.encodepoint(recv_derivation) - # derive secret key with subaddress - step 1: original CN derivation - scalar_step1 = se_thd89.derive_xmr_private(recv_derivation, real_output_index) - scalar_step1 = crypto_helpers.decodeint(scalar_step1) + key_image = se_thd89.xmr_generate_key_image( + crypto_helpers.encodepoint(recv_derivation), + real_output_index, + misc.xmr_subaddress_secret_key( + ack.view_key_private, received_index + ), + crypto_helpers.encodepoint(out_key), + ) + return None, crypto_helpers.decodepoint(key_image) else: if crypto.sc_iszero(ack.spend_key_private): raise ValueError("Watch-only wallet not supported") @@ -206,9 +212,11 @@ def generate_tx_spend_and_key_image_and_derivation( real_output_index: int | None, sub_addr_major: int | None, sub_addr_minor: int | None, -) -> tuple[crypto.Scalar, crypto.Point, crypto.Point]: +) -> tuple[ + crypto.Scalar | None, crypto.Point, crypto.Point, tuple[int, int] +]: """ - Generates UTXO spending key and key image and corresponding derivation. + Generates a UTXO spending key, key image, derivation and received index. Supports subaddresses. Corresponds to generate_key_image_helper() in the Monero codebase. @@ -250,7 +258,7 @@ def generate_tx_spend_and_key_image_and_derivation( xi, ki = generate_tx_spend_and_key_image( creds, out_key, subaddr_recv_info[1], real_output_index, subaddr_recv_info[0] ) - return xi, ki, recv_derivation + return xi, ki, subaddr_recv_info[1], subaddr_recv_info[0] def compute_subaddresses( From 6b1be578218f7a9a77429bb3f6d11855a963004e Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Sat, 22 Aug 2026 12:48:32 +0800 Subject: [PATCH 08/11] fix: synchronize eMMC progress UI state. --- core/embed/bootloader/bootui.c | 4 +++ core/embed/bootloader/bootui.h | 1 + core/embed/emmc_wrapper/emmc_commands.c | 29 ++++++++++------ core/embed/emmc_wrapper/emmc_ui_progress.c | 39 ++++++++++++++++++++++ core/embed/emmc_wrapper/emmc_ui_progress.h | 28 ++++++++++++++++ 5 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 core/embed/emmc_wrapper/emmc_ui_progress.c create mode 100644 core/embed/emmc_wrapper/emmc_ui_progress.h diff --git a/core/embed/bootloader/bootui.c b/core/embed/bootloader/bootui.c index 9a9673a5a6..e807e2d221 100644 --- a/core/embed/bootloader/bootui.c +++ b/core/embed/bootloader/bootui.c @@ -365,6 +365,7 @@ static bool ui_progress_bar_visible = false; void ui_screen_progress_bar_init(char* title, char* notes, int progress) { ui_statusbar_update(); ui_logo_onekey(); + ui_progress_bar_visible = true; if (title != NULL) { display_text_center(DISPLAY_RESX / 2, TITLE_OFFSET_Y, title, -1, FONT_PJKS_BOLD_38, COLOR_BL_FG, COLOR_BL_BG); @@ -407,6 +408,8 @@ void ui_screen_progress_bar_update(char* title, char* notes, int progress) { } } +bool ui_progress_bar_is_visible(void) { return ui_progress_bar_visible; } + void ui_progress_bar_visible_clear(void) { ui_progress_bar_visible = false; } void ui_screen_install_start(void) { @@ -541,6 +544,7 @@ void ui_screen_fail(void) { void ui_fadein(void) { display_fade(0, BACKLIGHT_NORMAL, 200); } void ui_fadeout(void) { + ui_progress_bar_visible_clear(); display_fade(BACKLIGHT_NORMAL, 0, 200); display_clear(); } diff --git a/core/embed/bootloader/bootui.h b/core/embed/bootloader/bootui.h index 398e53fcca..24bd635f10 100644 --- a/core/embed/bootloader/bootui.h +++ b/core/embed/bootloader/bootui.h @@ -55,6 +55,7 @@ void ui_screen_confirm(char* title, char* note_l1, char* note_l2, char* note_l3, void ui_screen_progress_bar_init(char* title, char* notes, int progress); void ui_screen_progress_bar_prepare(char* title, char* notes); void ui_screen_progress_bar_update(char* msg_status, char* notes, int progress); +bool ui_progress_bar_is_visible(void); void ui_progress_bar_visible_clear(void); void ui_screen_wipe_confirm(void); diff --git a/core/embed/emmc_wrapper/emmc_commands.c b/core/embed/emmc_wrapper/emmc_commands.c index eb7b68b38d..1d6c75d5a8 100644 --- a/core/embed/emmc_wrapper/emmc_commands.c +++ b/core/embed/emmc_wrapper/emmc_commands.c @@ -1,5 +1,6 @@ #include "emmc_commands.h" #include "emmc_commands_macros.h" +#include "emmc_ui_progress.h" #include "bootui.h" #include "fw_keys.h" @@ -10,28 +11,30 @@ // SDRAM BUFFER bootloader_buffer* bl_buffer = (bootloader_buffer*)FMC_SDRAM_BOOLOADER_BUFFER_ADDRESS; +static emmc_ui_progress_state ui_progress_state = EMMC_UI_PROGRESS_STATE_INIT; + static int ui_progress_bar_handle_update(int percentage, const char* title) { - static uint32_t ui_percentage_last = 0xff; char* progress_title = (char*)((title != NULL) ? title : "Transferring Data"); + emmc_ui_progress_action action = emmc_ui_progress_next_action( + &ui_progress_state, true, percentage, ui_progress_bar_is_visible() + ); - if ( (percentage < 0) || (percentage > 100) ) + if ( action == EMMC_UI_PROGRESS_ACTION_INVALID ) { return -1; } - if ( ui_percentage_last == (uint32_t)percentage ) + if ( action == EMMC_UI_PROGRESS_ACTION_NONE ) { return 0; } - ui_percentage_last = (uint32_t)percentage; - - if ( percentage < 100 ) + if ( action == EMMC_UI_PROGRESS_ACTION_UPDATE ) { ui_screen_progress_bar_update(progress_title, NULL, percentage); } - else + else if ( action == EMMC_UI_PROGRESS_ACTION_COMPLETE ) { ui_screen_progress_bar_update(progress_title, NULL, percentage); ui_fadeout(); @@ -44,9 +47,15 @@ static int ui_progress_bar_handle_update(int percentage, const char* title) static void ui_progress_bar_handle_clear(void) { - ui_progress_bar_visible_clear(); - display_clear(); - ui_bootloader_first(NULL); + emmc_ui_progress_action action = emmc_ui_progress_next_action( + &ui_progress_state, false, 0, ui_progress_bar_is_visible() + ); + + if ( action == EMMC_UI_PROGRESS_ACTION_CLEAR ) + { + display_clear(); + ui_bootloader_first(NULL); + } } static void packet_generate_first( diff --git a/core/embed/emmc_wrapper/emmc_ui_progress.c b/core/embed/emmc_wrapper/emmc_ui_progress.c new file mode 100644 index 0000000000..edc6430718 --- /dev/null +++ b/core/embed/emmc_wrapper/emmc_ui_progress.c @@ -0,0 +1,39 @@ +#include "emmc_ui_progress.h" + +static void emmc_ui_progress_state_reset(emmc_ui_progress_state* state) +{ + state->last_percentage = 0; + state->has_last_percentage = false; +} + +emmc_ui_progress_action emmc_ui_progress_next_action( + emmc_ui_progress_state* state, bool has_percentage, int percentage, bool progress_visible +) +{ + if ( !has_percentage ) + { + emmc_ui_progress_state_reset(state); + return progress_visible ? EMMC_UI_PROGRESS_ACTION_CLEAR : EMMC_UI_PROGRESS_ACTION_NONE; + } + + if ( (percentage < 0) || (percentage > 100) ) + { + return EMMC_UI_PROGRESS_ACTION_INVALID; + } + + if ( progress_visible && state->has_last_percentage && state->last_percentage == (uint32_t)percentage ) + { + return EMMC_UI_PROGRESS_ACTION_NONE; + } + + state->last_percentage = (uint32_t)percentage; + state->has_last_percentage = true; + + if ( percentage == 100 ) + { + emmc_ui_progress_state_reset(state); + return EMMC_UI_PROGRESS_ACTION_COMPLETE; + } + + return EMMC_UI_PROGRESS_ACTION_UPDATE; +} diff --git a/core/embed/emmc_wrapper/emmc_ui_progress.h b/core/embed/emmc_wrapper/emmc_ui_progress.h new file mode 100644 index 0000000000..0fbe6eb8fb --- /dev/null +++ b/core/embed/emmc_wrapper/emmc_ui_progress.h @@ -0,0 +1,28 @@ +#ifndef __EMMC_UI_PROGRESS_H__ +#define __EMMC_UI_PROGRESS_H__ + +#include +#include + +typedef enum +{ + EMMC_UI_PROGRESS_ACTION_NONE = 0, + EMMC_UI_PROGRESS_ACTION_UPDATE, + EMMC_UI_PROGRESS_ACTION_COMPLETE, + EMMC_UI_PROGRESS_ACTION_CLEAR, + EMMC_UI_PROGRESS_ACTION_INVALID, +} emmc_ui_progress_action; + +typedef struct +{ + uint32_t last_percentage; + bool has_last_percentage; +} emmc_ui_progress_state; + +#define EMMC_UI_PROGRESS_STATE_INIT {0, false} + +emmc_ui_progress_action emmc_ui_progress_next_action( + emmc_ui_progress_state* state, bool has_percentage, int percentage, bool progress_visible +); + +#endif From 801d1af585af7c7094778a30eb9b402c5693d0b7 Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Sat, 22 Aug 2026 13:17:29 +0800 Subject: [PATCH 09/11] feat: support THD89 SE 1.3.0. --- core/embed/bootloader/bootui.c | 15 ++ core/embed/bootloader/bootui.h | 1 + core/embed/bootloader/main.c | 31 ++- .../extmod/modtrezorconfig/modtrezorconfig.c | 66 +++--- core/embed/firmware/main.c | 22 ++ core/embed/firmware/version.h | 4 +- core/embed/trezorhal/se_thd89.c | 221 ++++++++++++++++-- core/embed/trezorhal/se_thd89.h | 12 +- core/mocks/generated/trezorconfig.pyi | 10 +- core/src/apps/base.py | 4 +- core/src/apps/debug/load_device.py | 1 - core/src/apps/homescreen/homescreen.py | 2 - core/src/apps/management/backup_device.py | 23 +- .../management/recovery_device/homescreen.py | 1 - .../apps/management/reset_device/__init__.py | 1 - core/src/storage/device.py | 29 +-- 16 files changed, 322 insertions(+), 121 deletions(-) diff --git a/core/embed/bootloader/bootui.c b/core/embed/bootloader/bootui.c index e807e2d221..ac6c2617b3 100644 --- a/core/embed/bootloader/bootui.c +++ b/core/embed/bootloader/bootui.c @@ -1045,6 +1045,21 @@ void ui_bootloader_first(const image_header* const hdr) { } } +void ui_bootloader_se_version_required(const image_header* const hdr) { + ui_bootloader_first(hdr); + ui_logo_warning(); + display_bar(0, SUBTITLE_OFFSET_Y - 10, DISPLAY_RESX, 150, COLOR_BL_BG); + display_text_center(DISPLAY_RESX / 2, SUBTITLE_OFFSET_Y, + "SE firmware update required", -1, FONT_NORMAL, + COLOR_BL_FG, COLOR_BL_BG); + display_text_center(DISPLAY_RESX / 2, SUBTITLE_OFFSET_Y + 36, + "Version 1.3.0 or later is required", -1, FONT_NORMAL, + COLOR_BL_SUBTITLE, COLOR_BL_BG); + display_text_center(DISPLAY_RESX / 2, SUBTITLE_OFFSET_Y + 72, + "Install an SE firmware update", -1, FONT_NORMAL, + COLOR_BL_SUBTITLE, COLOR_BL_BG); +} + void ui_bootloader_main_menu(const image_header* const hdr) { ui_bootloader_page_current = 5; diff --git a/core/embed/bootloader/bootui.h b/core/embed/bootloader/bootui.h index 24bd635f10..814ccf903f 100644 --- a/core/embed/bootloader/bootui.h +++ b/core/embed/bootloader/bootui.h @@ -93,6 +93,7 @@ int ui_input_poll(int zones, bool poll); int ui_input_match(int zones, uint32_t evt); void ui_bootloader_simple(void); void ui_bootloader_first(const image_header* const hdr); +void ui_bootloader_se_version_required(const image_header* const hdr); void ui_bootloader_view_details(const image_header* const hdr); void ui_wipe_confirm(const image_header* const hdr); void ui_show_version_info(int y, char* current_ver, char* new_ver); diff --git a/core/embed/bootloader/main.c b/core/embed/bootloader/main.c index b34da1b896..e81e81e938 100644 --- a/core/embed/bootloader/main.c +++ b/core/embed/bootloader/main.c @@ -17,6 +17,7 @@ * along with this program. If not, see . */ +#include #include #include @@ -59,6 +60,10 @@ #define MSG_NAME_TO_ID(x) MessageType_MessageType_##x +#define REQUIRED_SE_VERSION_MAJOR 1 +#define REQUIRED_SE_VERSION_MINOR 3 +#define REQUIRED_SE_VERSION_PATCH 0 + #if defined(STM32H747xx) #include "stm32h7xx_hal.h" #endif @@ -755,9 +760,15 @@ int main(void) { thd89_init(); uint8_t se_mode = se_get_state(); - // all se in app mode + secbool se_version_supported = sectrue; + if (se_mode == 0) { - device_para_init(); + se_version_supported = se_all_versions_at_least( + REQUIRED_SE_VERSION_MAJOR, REQUIRED_SE_VERSION_MINOR, + REQUIRED_SE_VERSION_PATCH); + if (se_version_supported == sectrue) { + device_para_init(); + } } if ((!device_serial_set() || !se_has_cerrificate()) && se_mode == 0) { @@ -812,18 +823,30 @@ int main(void) { BOOT_TARGET boot_target = decide_boot_target(&vhdr, &hdr, &vhdr_valid, &hdr_valid, &code_valid); + if (boot_target == BOOT_TARGET_NORMAL && + se_version_supported != sectrue) { + boot_target = BOOT_TARGET_BOOTLOADER; + } // boot_target = BOOT_TARGET_BOOTLOADER; if (boot_target == BOOT_TARGET_BOOTLOADER) { display_clear(); if (sectrue == vhdr_valid && sectrue == hdr_valid) { - ui_bootloader_first(&hdr); + if (se_version_supported == sectrue) { + ui_bootloader_first(&hdr); + } else { + ui_bootloader_se_version_required(&hdr); + } if (bootloader_usb_loop(&vhdr, &hdr) != sectrue) { return 1; } } else { - ui_bootloader_first(NULL); + if (se_version_supported == sectrue) { + ui_bootloader_first(NULL); + } else { + ui_bootloader_se_version_required(NULL); + } if (bootloader_usb_loop(NULL, NULL) != sectrue) { return 1; } diff --git a/core/embed/extmod/modtrezorconfig/modtrezorconfig.c b/core/embed/extmod/modtrezorconfig/modtrezorconfig.c index ec87553676..7b96fa885c 100644 --- a/core/embed/extmod/modtrezorconfig/modtrezorconfig.c +++ b/core/embed/extmod/modtrezorconfig/modtrezorconfig.c @@ -339,37 +339,42 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( mod_trezorconfig_change_wipe_code_obj, 3, 3, mod_trezorconfig_change_wipe_code); -/// def get_needs_backup() -> bool: +/// def get_mnemonic_export_enabled() -> bool: /// """ -/// Returns needs_backup. +/// Returns whether mnemonic export is enabled in the SE. /// """ -STATIC mp_obj_t mod_trezorconfig_get_needs_backup(void) { - bool needs_backup = false; - if (sectrue != se_get_needs_backup(&needs_backup)) { +STATIC mp_obj_t mod_trezorconfig_get_mnemonic_export_enabled(void) { + bool enabled = false; + if (sectrue != se_get_mnemonic_export_enabled(&enabled)) { return mp_const_false; } - - return needs_backup ? mp_const_true : mp_const_false; + return enabled ? mp_const_true : mp_const_false; } -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorconfig_get_needs_backup_obj, - mod_trezorconfig_get_needs_backup); +STATIC MP_DEFINE_CONST_FUN_OBJ_0( + mod_trezorconfig_get_mnemonic_export_enabled_obj, + mod_trezorconfig_get_mnemonic_export_enabled); -/// def set_needs_backup(needs_backup: bool = False) -> bool: +/// def set_mnemonic_export_enabled(enabled: bool, pin: str) -> bool: /// """ -/// Set needs_backup. +/// Enable or disable mnemonic export in the SE. /// """ -STATIC mp_obj_t mod_trezorconfig_set_needs_backup(mp_obj_t needs_backup) { - bool needs_backup_b = mp_obj_is_true(needs_backup); +STATIC mp_obj_t mod_trezorconfig_set_mnemonic_export_enabled( + mp_obj_t enabled, mp_obj_t pin) { + mp_buffer_info_t pin_b = {0}; + mp_get_buffer_raise(pin, &pin_b, MP_BUFFER_READ); + if (pin_b.len < 4 || pin_b.len > PIN_MAX_LENGTH) { + mp_raise_ValueError("Invalid PIN length"); + } - if (sectrue != se_set_needs_backup(needs_backup_b)) { + if (sectrue != se_set_mnemonic_export_enabled( + mp_obj_is_true(enabled), pin_b.buf, pin_b.len)) { return mp_const_false; } - return mp_const_true; } - -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorconfig_set_needs_backup_obj, - mod_trezorconfig_set_needs_backup); +STATIC MP_DEFINE_CONST_FUN_OBJ_2( + mod_trezorconfig_set_mnemonic_export_enabled_obj, + mod_trezorconfig_set_mnemonic_export_enabled); /// def get_val_len(app: int, key: int, public: bool = False) -> int: /// """ @@ -641,14 +646,21 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( mod_trezorconfig_se_import_slip39_obj, 4, 4, mod_trezorconfig_se_import_slip39); -/// def se_export_mnemonic() -> bytes: +/// def se_export_mnemonic(pin: str) -> bytes: /// """ /// Export mnemonic from SE. /// """ -STATIC mp_obj_t mod_trezorconfig_se_export_mnemonic(void) { - char mnemonic[MAX_MNEMONIC_LEN + 1]; +STATIC mp_obj_t mod_trezorconfig_se_export_mnemonic(mp_obj_t pin) { + mp_buffer_info_t pin_b = {0}; + char mnemonic[MAX_MNEMONIC_LEN + 1] = {0}; + mp_get_buffer_raise(pin, &pin_b, MP_BUFFER_READ); + if (pin_b.len < 4 || pin_b.len > PIN_MAX_LENGTH) { + mp_raise_ValueError("Invalid PIN length"); + } - if (sectrue != se_exportMnemonic(mnemonic, sizeof(mnemonic))) { + if (sectrue != + se_exportMnemonic(pin_b.buf, pin_b.len, mnemonic, sizeof(mnemonic))) { + memzero(mnemonic, sizeof(mnemonic)); mp_raise_ValueError("Get se mnemonic"); } @@ -658,7 +670,7 @@ STATIC mp_obj_t mod_trezorconfig_se_export_mnemonic(void) { return res; } -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorconfig_se_export_mnemonic_obj, +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorconfig_se_export_mnemonic_obj, mod_trezorconfig_se_export_mnemonic); /// def fingerprint_is_unlocked() -> bool: @@ -834,10 +846,10 @@ STATIC const mp_rom_map_elem_t mp_module_trezorconfig_globals_table[] = { MP_ROM_PTR(&mod_trezorconfig_get_serial_obj)}, {MP_ROM_QSTR(MP_QSTR_get_capacity), MP_ROM_PTR(&mod_trezorconfig_get_capacity_obj)}, - {MP_ROM_QSTR(MP_QSTR_get_needs_backup), - MP_ROM_PTR(&mod_trezorconfig_get_needs_backup_obj)}, - {MP_ROM_QSTR(MP_QSTR_set_needs_backup), - MP_ROM_PTR(&mod_trezorconfig_set_needs_backup_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_mnemonic_export_enabled), + MP_ROM_PTR(&mod_trezorconfig_get_mnemonic_export_enabled_obj)}, + {MP_ROM_QSTR(MP_QSTR_set_mnemonic_export_enabled), + MP_ROM_PTR(&mod_trezorconfig_set_mnemonic_export_enabled_obj)}, {MP_ROM_QSTR(MP_QSTR_fingerprint_is_unlocked), MP_ROM_PTR(&mod_trezorcrypto_se_fingerprint_is_unlocked_obj)}, {MP_ROM_QSTR(MP_QSTR_fingerprint_lock), diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c index 37df6eae7c..cc325f03b5 100644 --- a/core/embed/firmware/main.c +++ b/core/embed/firmware/main.c @@ -79,6 +79,23 @@ // from util.s extern void shutdown_privileged(void); +static void __attribute__((noreturn)) show_se_version_required(void) { + display_orientation(0); + display_clear(); + display_backlight(255); + display_image(9, 50, 46, 40, toi_icon_warning + 12, + sizeof(toi_icon_warning) - 12); + display_text(8, 140, "SE firmware update required.", -1, FONT_NORMAL, + COLOR_WHITE, COLOR_BLACK); + display_text(8, 720, "Version 1.3.0 or later is required.", -1, + FONT_NORMAL, RGB16(0x69, 0x69, 0x69), COLOR_BLACK); + display_text(8, 784, "Tap to enter Update Mode.", -1, FONT_NORMAL, + COLOR_WHITE, COLOR_BLACK); + while (!touch_click()) { + } + reboot_to_bootloader(); +} + static void copyflash2sdram(void) { extern int _flash2_load_addr, _flash2_start, _flash2_end; volatile uint32_t *dst = (volatile uint32_t *)&_flash2_start; @@ -150,6 +167,11 @@ int main(void) { motor_init(); thd89_init(); + if (se_all_versions_at_least(SE_MINIMUM_VERSION_MAJOR, + SE_MINIMUM_VERSION_MINOR, + SE_MINIMUM_VERSION_PATCH) != sectrue) { + show_se_version_required(); + } camera_init(); fingerprint_init(); nfc_init(); diff --git a/core/embed/firmware/version.h b/core/embed/firmware/version.h index 3c52c9bcb6..b830f8c6b4 100644 --- a/core/embed/firmware/version.h +++ b/core/embed/firmware/version.h @@ -15,8 +15,8 @@ // Minimum SE version required for firmware upgrade #define SE_MINIMUM_VERSION_MAJOR 1 -#define SE_MINIMUM_VERSION_MINOR 1 -#define SE_MINIMUM_VERSION_PATCH 7 +#define SE_MINIMUM_VERSION_MINOR 3 +#define SE_MINIMUM_VERSION_PATCH 0 #define SE_MINIMUM_VERSION_UINT32 \ (SE_MINIMUM_VERSION_MAJOR | (SE_MINIMUM_VERSION_MINOR << 8) | \ (SE_MINIMUM_VERSION_PATCH << 16) | (0 << 24)) diff --git a/core/embed/trezorhal/se_thd89.c b/core/embed/trezorhal/se_thd89.c index 55f989c1d2..5e610220ae 100644 --- a/core/embed/trezorhal/se_thd89.c +++ b/core/embed/trezorhal/se_thd89.c @@ -18,6 +18,7 @@ #include "thd89.h" #define PIN_MAX_LEN (50) +#define MNEMONIC_EXPORT_PIN_MIN_LEN (4) #define CURVE_NIST256P1 (0x00) #define CURVE_SECP256K1 (0x01) @@ -94,8 +95,15 @@ typedef enum { SE_SECURE_RESPONSE_INVALID, } se_secure_response_result_t; +typedef enum { + SE_FATAL_STATUS_NONE = 0, + SE_FATAL_STATUS_SECURITY_ALERT, + SE_FATAL_STATUS_CONFIGURATION_ERROR, +} se_fatal_status_t; + static se_long_operation_t se_pending_operation = SE_LONG_OPERATION_NONE; static se_long_operation_t se_fp_pending_operation = SE_LONG_OPERATION_NONE; +static se_fatal_status_t se_pending_fatal_status = SE_FATAL_STATUS_NONE; static secbool se_query_progress_percent_ex(uint8_t addr, uint8_t *percent); @@ -136,6 +144,49 @@ static void se_invalidate_session(uint8_t addr) { } } +static void se_record_fatal_status(uint16_t sw1sw2) { + se_fatal_status_t status = SE_FATAL_STATUS_NONE; + if (sw1sw2 == 0x6601) { + status = SE_FATAL_STATUS_SECURITY_ALERT; + } else if (sw1sw2 == 0x6f01) { + status = SE_FATAL_STATUS_CONFIGURATION_ERROR; + } + if (status == SE_FATAL_STATUS_NONE) { + return; + } + se_invalidate_session(THD89_MASTER_ADDRESS); + se_invalidate_session(THD89_FINGER_ADDRESS); + if (se_pending_fatal_status == SE_FATAL_STATUS_NONE) { + se_pending_fatal_status = status; + } +} + +static void se_halt_for_pending_fatal_status(void) { + se_fatal_status_t status = se_pending_fatal_status; + if (status == SE_FATAL_STATUS_NONE) { + return; + } + + se_pending_fatal_status = SE_FATAL_STATUS_NONE; + memzero(se_send_buffer, sizeof(se_send_buffer)); + memzero(se_recv_buffer, sizeof(se_recv_buffer)); + se_recv_len = 0; + pin_result_type = PIN_FAILED; + pin_passphrase_ret = PIN_FAILED; + + if (status == SE_FATAL_STATUS_SECURITY_ALERT) { + error_shutdown("Security alert", "Secure element authentication", + "failed.", "Please restart."); + } + error_shutdown("SE configuration error", "Secure element configuration", + "failed.", "Please restart."); +} + +void se_handle_status(uint16_t sw1sw2) { + se_record_fatal_status(sw1sw2); + se_halt_for_pending_fatal_status(); +} + static secbool se_session_is_initialized(uint8_t addr, const uint8_t *session_key) { if (addr == THD89_MASTER_ADDRESS && session_key == se_session_key && @@ -342,6 +393,8 @@ static se_secure_response_result_t se_transmit_mac_result_ex( goto cleanup; } + se_record_fatal_status(sw1sw2); + if (sw1sw2 != 0x9000) { result = SE_SECURE_RESPONSE_AUTHENTICATED_ERROR; goto cleanup; @@ -390,6 +443,9 @@ static se_secure_response_result_t se_transmit_mac_result_ex( memzero(se_send_buffer, sizeof(se_send_buffer)); memzero(se_recv_buffer, sizeof(se_recv_buffer)); se_recv_len = 0; + if (thd89_irq_nest == 0) { + se_halt_for_pending_fatal_status(); + } return result; } @@ -412,6 +468,7 @@ secbool se_transmit_mac(uint8_t ins, uint8_t p1, uint8_t p2, uint8_t *data, if (thd89_irq_nest == 0) { enable_irq(irq); } + se_halt_for_pending_fatal_status(); return result; } @@ -427,6 +484,7 @@ secbool se_fp_transmit_mac(uint8_t ins, uint8_t p1, uint8_t p2, uint8_t *data, if (thd89_irq_nest == 0) { enable_irq(irq); } + se_halt_for_pending_fatal_status(); return result; } @@ -488,6 +546,7 @@ secbool se_random_encrypted_ex(uint8_t addr, uint8_t *session_key, se_invalidate_session(addr); goto cleanup; } + se_record_fatal_status(sw1sw2); if (sw1sw2 != 0x9000) { goto cleanup; } @@ -527,6 +586,9 @@ secbool se_random_encrypted_ex(uint8_t addr, uint8_t *session_key, memzero(mac, sizeof(mac)); memzero(transaction, sizeof(transaction)); memzero(se_recv_buffer, sizeof(se_recv_buffer)); + if (thd89_irq_nest == 0) { + se_halt_for_pending_fatal_status(); + } return ret; } @@ -568,6 +630,7 @@ static secbool se_get_session_random_ex(uint8_t addr, uint8_t se_random[16]) { &sw1sw2) != sectrue) { return secfalse; } + se_handle_status(sw1sw2); return sectrue * (sw1sw2 == 0x9000 && recv_len == 16); } @@ -632,8 +695,11 @@ static secbool se_sync_session_key_ex(uint8_t addr, uint8_t *session_key, memcpy(sync_cmd + 5, request_data, sizeof(request_data)); if (thd89_transmit_raw_ex(addr, sync_cmd, sizeof(sync_cmd), confirmation, - &recv_len, &sw1sw2) != sectrue || - sw1sw2 != 0x9000 || recv_len != sizeof(confirmation)) { + &recv_len, &sw1sw2) != sectrue) { + goto cleanup; + } + se_handle_status(sw1sw2); + if (sw1sw2 != 0x9000 || recv_len != sizeof(confirmation)) { goto cleanup; } @@ -970,6 +1036,86 @@ int se_get_version(uint8_t addr, char *ver, uint16_t in_len) { return _se_get_ver_info(addr, 0x00, (uint8_t *)ver, in_len); } +static secbool se_parse_version_component(const char **cursor, + uint8_t *component) { + uint16_t value = 0; + bool has_digit = false; + + while (**cursor >= '0' && **cursor <= '9') { + value = value * 10U + (uint8_t)(**cursor - '0'); + if (value > UINT8_MAX) { + return secfalse; + } + has_digit = true; + (*cursor)++; + } + if (!has_digit) { + return secfalse; + } + *component = (uint8_t)value; + return sectrue; +} + +static secbool se_version_is_at_least(const char *version, + uint8_t required_major, + uint8_t required_minor, + uint8_t required_patch) { + uint8_t components[4] = {0}; + const char *cursor = version; + + if (cursor == NULL) { + return secfalse; + } + for (uint8_t index = 0; index < 4; index++) { + if (se_parse_version_component(&cursor, &components[index]) != sectrue) { + return secfalse; + } + if (*cursor == '\0') { + if (index < 2) { + return secfalse; + } + break; + } + if (*cursor != '.' || index == 3) { + return secfalse; + } + cursor++; + } + if (components[0] != required_major) { + return sectrue * (components[0] > required_major); + } + if (components[1] != required_minor) { + return sectrue * (components[1] > required_minor); + } + return sectrue * (components[2] >= required_patch); +} + +secbool se_all_versions_at_least(uint8_t required_major, + uint8_t required_minor, + uint8_t required_patch) { + static const uint8_t addresses[] = { + THD89_1ST_ADDRESS, + THD89_2ND_ADDRESS, + THD89_3RD_ADDRESS, + THD89_4TH_ADDRESS, + }; + + for (size_t i = 0; i < sizeof(addresses); i++) { + char version[16] = {0}; + int version_len = + se_get_version(addresses[i], version, sizeof(version) - 1U); + if (version_len <= 0 || version_len >= (int)sizeof(version)) { + return secfalse; + } + version[version_len] = '\0'; + if (se_version_is_at_least(version, required_major, required_minor, + required_patch) != sectrue) { + return secfalse; + } + } + return sectrue; +} + int se_get_build_id(uint8_t addr, char *build_id, uint16_t in_len) { return _se_get_ver_info(addr, 0x01, (uint8_t *)build_id, in_len); } @@ -2035,33 +2181,61 @@ secbool se_containsMnemonic(const char *mnemonic) { return sectrue * (verify == 0x55); } -secbool se_exportMnemonic(char *mnemonic, uint16_t dest_size) { - uint16_t len = dest_size; +secbool se_exportMnemonic(const uint8_t *pin, uint16_t pin_len, char *mnemonic, + uint16_t dest_size) { + uint8_t request[1 + PIN_MAX_LEN] = {0}; + uint16_t response_len = dest_size > 0 ? dest_size - 1U : 0; - if (!se_transmit_mac(0xE2, 0x00, 0x02, NULL, 0, (uint8_t *)mnemonic, &len)) { + if (mnemonic == NULL || dest_size == 0 || pin == NULL || + pin_len < MNEMONIC_EXPORT_PIN_MIN_LEN || pin_len > PIN_MAX_LEN) { return secfalse; } - mnemonic[len] = 0; + request[0] = (uint8_t)pin_len; + if (pin_len != 0) { + memcpy(&request[1], pin, pin_len); + } + if (!se_transmit_mac(0xE2, 0x00, 0x02, request, pin_len + 1U, + (uint8_t *)mnemonic, &response_len) || + response_len >= dest_size) { + memzero(request, sizeof(request)); + memzero(mnemonic, dest_size); + return secfalse; + } + mnemonic[response_len] = '\0'; + memzero(request, sizeof(request)); return sectrue; } -secbool se_set_needs_backup(bool needs_backup) { - if (!se_transmit_mac(0xE2, 0x00, 0x03, (uint8_t *)&needs_backup, 1, NULL, - NULL)) { +secbool se_set_mnemonic_export_enabled(bool enabled, const uint8_t *pin, + uint16_t pin_len) { + uint8_t request[2 + PIN_MAX_LEN] = {enabled ? 0x55 : 0x00, + (uint8_t)pin_len}; + + if (pin == NULL || pin_len < MNEMONIC_EXPORT_PIN_MIN_LEN || + pin_len > PIN_MAX_LEN) { return secfalse; } - + if (pin_len != 0) { + memcpy(&request[2], pin, pin_len); + } + if (!se_transmit_mac(0xE2, 0x00, 0x03, request, pin_len + 2U, NULL, NULL)) { + memzero(request, sizeof(request)); + return secfalse; + } + memzero(request, sizeof(request)); return sectrue; } -secbool se_get_needs_backup(bool *needs_backup) { - uint16_t len = 1; - uint8_t needs_backup_buf = 0xff; - if (!se_transmit_mac(0xE2, 0x00, 0x04, NULL, 0, &needs_backup_buf, &len)) { +secbool se_get_mnemonic_export_enabled(bool *enabled) { + uint8_t response = 0; + uint16_t response_len = sizeof(response); + + if (enabled == NULL || + !se_transmit_mac(0xE2, 0x00, 0x04, NULL, 0, &response, &response_len) || + response_len != 1 || (response != 0x00 && response != 0x55)) { return secfalse; } - *needs_backup = needs_backup_buf == 0 ? false : true; - + *enabled = response == 0x55; return sectrue; } @@ -2300,8 +2474,12 @@ static secbool se_query_progress_percent_ex(uint8_t addr, uint8_t *percent) { } if (thd89_transmit_raw_ex(addr, cmd, sizeof(cmd), NULL, &recv_len, &sw1sw2) != - sectrue || - recv_len != 0) { + sectrue) { + *pending_operation = SE_LONG_OPERATION_NONE; + return secfalse; + } + se_handle_status(sw1sw2); + if (recv_len != 0) { *pending_operation = SE_LONG_OPERATION_NONE; return secfalse; } @@ -3059,8 +3237,7 @@ secbool se_fido_credential_encrypt(const uint8_t rp_id_hash[32], if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_CREDENTIAL_ENCRYPT, APDU_DATA, plaintext_len + 32U, credential_id, &resp_len) || - resp_len < SE_FIDO_CREDENTIAL_ID_MIN_LEN || - resp_len > SE_FIDO_CREDENTIAL_ID_MAX_LEN) { + resp_len != plaintext_len + 32U) { return secfalse; } *credential_id_len = resp_len; @@ -3081,7 +3258,7 @@ secbool se_fido_credential_peek(const uint8_t *credential_id, if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_CREDENTIAL_PEEK, (uint8_t *)credential_id, credential_id_len, plaintext, &resp_len) || - resp_len == 0 || resp_len > SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN) { + resp_len != credential_id_len - 32U) { return secfalse; } *plaintext_len = resp_len; @@ -3106,7 +3283,7 @@ secbool se_fido_credential_decrypt(const uint8_t rp_id_hash[32], if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_CREDENTIAL_DECRYPT, APDU_DATA, credential_id_len + 32U, plaintext, &resp_len) || - resp_len == 0 || resp_len > SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN) { + resp_len != credential_id_len - 32U) { return secfalse; } *plaintext_len = resp_len; diff --git a/core/embed/trezorhal/se_thd89.h b/core/embed/trezorhal/se_thd89.h index 09c178e64c..fbb2a87c2b 100644 --- a/core/embed/trezorhal/se_thd89.h +++ b/core/embed/trezorhal/se_thd89.h @@ -61,6 +61,7 @@ _Static_assert(sizeof(CTAP_credential_id_storage) == typedef secbool (*UI_WAIT_CALLBACK)(uint32_t wait, uint32_t progress, const char *message); void se_set_ui_callback(UI_WAIT_CALLBACK callback); +void se_handle_status(uint16_t sw1sw2); secbool se_transmit_mac(uint8_t ins, uint8_t p1, uint8_t p2, uint8_t *data, uint16_t data_len, uint8_t *recv, uint16_t *recv_len); @@ -78,6 +79,9 @@ secbool se_reset_storage(void); secbool se_set_sn(const char *serial, uint8_t len); secbool se_get_sn(char **serial); int se_get_version(uint8_t addr, char *ver, uint16_t in_len); +secbool se_all_versions_at_least(uint8_t required_major, + uint8_t required_minor, + uint8_t required_patch); int se_get_build_id(uint8_t addr, char *build_id, uint16_t in_len); int se_get_hash(uint8_t addr, uint8_t *hash, uint16_t in_len); int se_get_boot_version(uint8_t addr, char *ver, uint16_t in_len); @@ -162,9 +166,11 @@ secbool se_set_session_key_ex(uint8_t addr, const uint8_t *session_key); secbool se_set_session_key(const uint8_t *session_key); secbool se_containsMnemonic(const char *mnemonic); -secbool se_exportMnemonic(char *mnemonic, uint16_t dest_size); -secbool se_set_needs_backup(bool needs_backup); -secbool se_get_needs_backup(bool *needs_backup); +secbool se_exportMnemonic(const uint8_t *pin, uint16_t pin_len, char *mnemonic, + uint16_t dest_size); +secbool se_set_mnemonic_export_enabled(bool enabled, const uint8_t *pin, + uint16_t pin_len); +secbool se_get_mnemonic_export_enabled(bool *enabled); secbool se_hasWipeCode(void); secbool se_changeWipeCode(const char *pin, const char *wipe_code); diff --git a/core/mocks/generated/trezorconfig.pyi b/core/mocks/generated/trezorconfig.pyi index a51d18a5e1..5d82271d7a 100644 --- a/core/mocks/generated/trezorconfig.pyi +++ b/core/mocks/generated/trezorconfig.pyi @@ -102,16 +102,16 @@ def change_wipe_code( # extmod/modtrezorconfig/modtrezorconfig.c -def get_needs_backup() -> bool: +def get_mnemonic_export_enabled() -> bool: """ - Returns needs_backup. + Returns whether mnemonic export is enabled in the SE. """ # extmod/modtrezorconfig/modtrezorconfig.c -def set_needs_backup(needs_backup: bool = False) -> bool: +def set_mnemonic_export_enabled(enabled: bool, pin: str) -> bool: """ - Set needs_backup. + Enable or disable mnemonic export in the SE. """ @@ -191,7 +191,7 @@ None, iteration_exponent: int | None) -> bool: # extmod/modtrezorconfig/modtrezorconfig.c -def se_export_mnemonic() -> bytes: +def se_export_mnemonic(pin: str) -> bytes: """ Export mnemonic from SE. """ diff --git a/core/src/apps/base.py b/core/src/apps/base.py index 4c7bc453d3..8fc2501075 100644 --- a/core/src/apps/base.py +++ b/core/src/apps/base.py @@ -168,6 +168,7 @@ def get_features() -> Features: f.sd_card_present = sdcard.is_present() f.initialized = storage.device.is_initialized() + f.needs_backup = False current_space = se_thd89.get_pin_passphrase_space() if current_space < 30: @@ -185,7 +186,6 @@ def get_features() -> Features: f.passphrase_protection = False else: f.passphrase_protection = storage.device.is_passphrase_enabled() - f.needs_backup = storage.device.needs_backup() f.unfinished_backup = storage.device.unfinished_backup() f.no_backup = storage.device.no_backup() f.flags = storage.device.get_flags() @@ -569,8 +569,6 @@ def get_state() -> str | None: dev_state = _(i18n_keys.MSG__SEEDLESS) elif storage.device.unfinished_backup(): dev_state = _(i18n_keys.MSG__BACKUP_FAILED) - elif storage.device.needs_backup(): - dev_state = _(i18n_keys.MSG__NEEDS_BACKUP) elif not config.has_pin(): dev_state = _(i18n_keys.MSG__PIN_NOT_SET) elif storage.device.get_experimental_features(): diff --git a/core/src/apps/debug/load_device.py b/core/src/apps/debug/load_device.py index b72407bf62..21fcce54c8 100644 --- a/core/src/apps/debug/load_device.py +++ b/core/src/apps/debug/load_device.py @@ -38,7 +38,6 @@ async def load_device(ctx: wire.Context, msg: LoadDevice) -> Success: storage.device.store_mnemonic_secret( secret, backup_type, - needs_backup=msg.needs_backup is True, no_backup=msg.no_backup is True, identifier=identifier, iteration_exponent=iteration_exponent, diff --git a/core/src/apps/homescreen/homescreen.py b/core/src/apps/homescreen/homescreen.py index b98ab6cc5a..8766196a93 100644 --- a/core/src/apps/homescreen/homescreen.py +++ b/core/src/apps/homescreen/homescreen.py @@ -52,8 +52,6 @@ def do_render(self) -> None: ui.header_error("SEEDLESS") elif storage.device.is_initialized() and storage.device.unfinished_backup(): ui.header_error("BACKUP FAILED!") - elif storage.device.is_initialized() and storage.device.needs_backup(): - ui.header_warning("NEEDS BACKUP!") elif storage.device.is_initialized() and not config.has_pin(): ui.header_warning("PIN NOT SET!") elif storage.device.get_experimental_features(): diff --git a/core/src/apps/management/backup_device.py b/core/src/apps/management/backup_device.py index 8c96ba8db6..ddb38b7b5b 100644 --- a/core/src/apps/management/backup_device.py +++ b/core/src/apps/management/backup_device.py @@ -1,14 +1,9 @@ from typing import TYPE_CHECKING -import storage import storage.device from trezor import wire from trezor.messages import Success -from apps.common import mnemonic - -from .reset_device import backup_seed, layout - if TYPE_CHECKING: from trezor.messages import BackupDevice @@ -16,20 +11,4 @@ async def backup_device(ctx: wire.Context, msg: BackupDevice) -> Success: if not storage.device.is_initialized(): raise wire.NotInitialized("Device is not initialized") - if not storage.device.needs_backup(): - raise wire.ProcessError("Seed already backed up") - - mnemonic_secret, mnemonic_type = mnemonic.get() - if mnemonic_secret is None: - raise RuntimeError - - storage.device.set_unfinished_backup(True) - storage.device.set_backed_up(False) - - await backup_seed(ctx, mnemonic_type, mnemonic_secret) - - storage.device.set_unfinished_backup(False) - - await layout.show_backup_success(ctx) - - return Success(message="Seed successfully backed up") + raise wire.ProcessError("Seed already backed up") diff --git a/core/src/apps/management/recovery_device/homescreen.py b/core/src/apps/management/recovery_device/homescreen.py index d2ec5dfa99..a70be8bfd8 100644 --- a/core/src/apps/management/recovery_device/homescreen.py +++ b/core/src/apps/management/recovery_device/homescreen.py @@ -258,7 +258,6 @@ async def _finish_recovery( storage_device.store_mnemonic_secret( secret, backup_type, - needs_backup=False, no_backup=False, identifier=identifier, iteration_exponent=exponent, diff --git a/core/src/apps/management/reset_device/__init__.py b/core/src/apps/management/reset_device/__init__.py index 9604da8820..545762f4bc 100644 --- a/core/src/apps/management/reset_device/__init__.py +++ b/core/src/apps/management/reset_device/__init__.py @@ -91,7 +91,6 @@ async def reset_device( storage_device.store_mnemonic_secret( secret, # for SLIP-39, this is the EMS backup_type, - needs_backup=not perform_backup, no_backup=bool(msg.no_backup), identifier=storage_device.get_slip39_identifier(), iteration_exponent=storage_device.get_slip39_iteration_exponent(), diff --git a/core/src/storage/device.py b/core/src/storage/device.py index 2805a25766..94b1e646b3 100644 --- a/core/src/storage/device.py +++ b/core/src/storage/device.py @@ -87,7 +87,6 @@ _SERIAL_NUMBER_VALUE: str | None = None _DEVICE_ID_VALUE: str | None = None _TREZOR_COMPATIBLE_VALUE: bool | None = None -_NEEDS_BACKUP_VALUE: bool | None = None _FIDO_SEED_GEN = False _FIDO2_COUNTER_VALUE: int | None = None _FIDO_ENABLED_VALUE: bool | None = None @@ -1001,8 +1000,7 @@ def set_language(lang: str) -> None: def get_mnemonic_secret() -> bytes | None: if utils.EMULATOR: return common.get(_NAMESPACE, _MNEMONIC_SECRET) - else: - return config.se_export_mnemonic() + return None def get_backup_type() -> BackupType: @@ -1115,7 +1113,6 @@ def set_appdrawer_background(full_path: str) -> None: def store_mnemonic_secret( secret: bytes, backup_type: BackupType | int, - needs_backup: bool = False, no_backup: bool = False, identifier: int | None = None, iteration_exponent: int | None = None, @@ -1123,7 +1120,6 @@ def store_mnemonic_secret( from trezor.enums import BackupType global _NO_BACKUP_VALUE - global _NEEDS_BACKUP_VALUE global _INITIALIZED_VALUE set_version(common.STORAGE_VERSION_CURRENT) if utils.EMULATOR: @@ -1136,31 +1132,10 @@ def store_mnemonic_secret( config.se_import_slip39(secret, backup_type, identifier, iteration_exponent) common.set_uint8(_NAMESPACE, _BACKUP_TYPE, backup_type) common.set_true_or_delete(_NAMESPACE, _NO_BACKUP, no_backup) - if not no_backup: - set_backed_up(needs_backup) - _NEEDS_BACKUP_VALUE = needs_backup _NO_BACKUP_VALUE = no_backup _INITIALIZED_VALUE = True -def needs_backup() -> bool: - if utils.EMULATOR: - return common.get_bool(_NAMESPACE, _NEEDS_BACKUP) - global _NEEDS_BACKUP_VALUE - if _NEEDS_BACKUP_VALUE is None: - _NEEDS_BACKUP_VALUE = config.get_needs_backup() - return _NEEDS_BACKUP_VALUE or False - - -def set_backed_up(stat: bool) -> None: - if utils.EMULATOR: - return common.delete(_NAMESPACE, _NEEDS_BACKUP) - global _NEEDS_BACKUP_VALUE - config.set_needs_backup(stat) - _NEEDS_BACKUP_VALUE = stat - return None - - def unfinished_backup() -> bool: global _UNFINISHED_BACKUP_VALUE if _UNFINISHED_BACKUP_VALUE is None: @@ -1672,7 +1647,6 @@ def clear_global_cache() -> None: global _FLAGS_VALUE global _UNFINISHED_BACKUP_VALUE global _NO_BACKUP_VALUE - global _NEEDS_BACKUP_VALUE global _BACKUP_TYPE_VALUE global _SAFETY_CHECK_LEVEL_VALUE global _AIRGAP_MODE_VALUE @@ -1713,7 +1687,6 @@ def clear_global_cache() -> None: _FLAGS_VALUE = None _UNFINISHED_BACKUP_VALUE = None _NO_BACKUP_VALUE = None - _NEEDS_BACKUP_VALUE = None _BACKUP_TYPE_VALUE = None _SAFETY_CHECK_LEVEL_VALUE = None _AIRGAP_MODE_VALUE = None From d676cbdbd3ed9a9428bdedabf0e0f7d2c35527ba Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Thu, 27 Aug 2026 14:33:56 +0800 Subject: [PATCH 10/11] fix: validate firmware vendor header bounds. --- core/embed/bootloader/bootui.c | 6 ++- core/embed/emmc_wrapper/emmc_commands.c | 27 ++++++++--- .../extmod/modtrezorutils/modtrezorutils.c | 2 +- core/embed/trezorhal/image.c | 47 ++++++++++++++----- core/embed/trezorhal/image.h | 9 ++-- 5 files changed, 65 insertions(+), 26 deletions(-) diff --git a/core/embed/bootloader/bootui.c b/core/embed/bootloader/bootui.c index ac6c2617b3..cfb0067850 100644 --- a/core/embed/bootloader/bootui.c +++ b/core/embed/bootloader/bootui.c @@ -272,8 +272,10 @@ void ui_screen_install_confirm_newvendor_or_downgrade_wipe(char* new_version) { vendor_header current_vhdr; image_header current_hdr; // char str[128] = {0}; - if (sectrue == load_vendor_header((const uint8_t*)FIRMWARE_START, FW_KEY_M, - FW_KEY_N, FW_KEYS, ¤t_vhdr)) { + if (sectrue == + load_vendor_header((const uint8_t*)FIRMWARE_START, + VENDOR_HEADER_MAX_SIZE, FW_KEY_M, FW_KEY_N, FW_KEYS, + ¤t_vhdr)) { if (sectrue == load_image_header((const uint8_t*)FIRMWARE_START + current_vhdr.hdrlen, FIRMWARE_IMAGE_MAGIC, FIRMWARE_IMAGE_MAXSIZE, diff --git a/core/embed/emmc_wrapper/emmc_commands.c b/core/embed/emmc_wrapper/emmc_commands.c index 1d6c75d5a8..b881086c8b 100644 --- a/core/embed/emmc_wrapper/emmc_commands.c +++ b/core/embed/emmc_wrapper/emmc_commands.c @@ -658,7 +658,7 @@ static int check_file_contents(uint8_t iface_num, const uint8_t* buffer, uint32_ // check firmware header // check file header ExecuteCheck_MSGS_ADV( - load_vendor_header(p_data, FW_KEY_M, FW_KEY_N, FW_KEYS, &file_vhdr), sectrue, + load_vendor_header(p_data, buffer_len, FW_KEY_M, FW_KEY_N, FW_KEYS, &file_vhdr), sectrue, { send_failure( iface_num, FailureType_Failure_ProcessError, "Update file vendor header invalid!" @@ -666,6 +666,11 @@ static int check_file_contents(uint8_t iface_num, const uint8_t* buffer, uint32_ return -1; } ); + if ( file_vhdr.hdrlen > buffer_len || buffer_len - file_vhdr.hdrlen < IMAGE_HEADER_SIZE ) + { + send_failure(iface_num, FailureType_Failure_ProcessError, "Update file header truncated!"); + return -1; + } ExecuteCheck_MSGS_ADV( load_image_header( p_data + file_vhdr.hdrlen, FIRMWARE_IMAGE_MAGIC, FIRMWARE_IMAGE_MAXSIZE, file_vhdr.vsig_m, @@ -678,6 +683,14 @@ static int check_file_contents(uint8_t iface_num, const uint8_t* buffer, uint32_ } ); + uint32_t firmware_item_len = file_vhdr.hdrlen + file_hdr.hdrlen; + if ( firmware_item_len > buffer_len || file_hdr.codelen > buffer_len - firmware_item_len ) + { + send_failure(iface_num, FailureType_Failure_ProcessError, "Firmware file truncated!"); + return -1; + } + firmware_item_len += file_hdr.codelen; + if ( file_hdr.codelen - (FIRMWARE_IMAGE_INNER_SIZE - (file_vhdr.hdrlen + file_hdr.hdrlen)) > FMC_SDRAM_FIRMWARE_P2_LEN ) { @@ -700,7 +713,7 @@ static int check_file_contents(uint8_t iface_num, const uint8_t* buffer, uint32_ // check file size ExecuteCheck_MSGS_ADV( - (file_vhdr.hdrlen + file_hdr.hdrlen + file_hdr.codelen <= FIRMWARE_IMAGE_MAXSIZE), true, + (firmware_item_len <= FIRMWARE_IMAGE_MAXSIZE), true, { send_failure(iface_num, FailureType_Failure_ProcessError, "Firmware file is too big!"); return -1; @@ -712,7 +725,8 @@ static int check_file_contents(uint8_t iface_num, const uint8_t* buffer, uint32_ update_info.mcu_update_info.purpose_changed = secfalse; // vhdr if ( load_vendor_header( - (const uint8_t*)FIRMWARE_START, FW_KEY_M, FW_KEY_N, FW_KEYS, ¤t_vhdr + (const uint8_t*)FIRMWARE_START, VENDOR_HEADER_MAX_SIZE, FW_KEY_M, FW_KEY_N, FW_KEYS, + ¤t_vhdr ) == sectrue ) { if ( load_image_header( @@ -782,14 +796,13 @@ static int check_file_contents(uint8_t iface_num, const uint8_t* buffer, uint32_ update_info.items[update_info.item_count].type = UPDATE_MCU; update_info.items[update_info.item_count].offset = p_data - buffer; - update_info.items[update_info.item_count].length = - file_vhdr.hdrlen + file_hdr.hdrlen + file_hdr.codelen; + update_info.items[update_info.item_count].length = firmware_item_len; update_info.item_count++; update_info.mcu_location = update_info.item_count; update_info.mcu_update_info.purpose = file_hdr.purpose; - p_data += file_vhdr.hdrlen + file_hdr.hdrlen + file_hdr.codelen; - buffer_len -= file_vhdr.hdrlen + file_hdr.hdrlen + file_hdr.codelen; + p_data += firmware_item_len; + buffer_len -= firmware_item_len; continue; } // SE diff --git a/core/embed/extmod/modtrezorutils/modtrezorutils.c b/core/embed/extmod/modtrezorutils/modtrezorutils.c index 2737d00083..f7a6dfff53 100644 --- a/core/embed/extmod/modtrezorutils/modtrezorutils.c +++ b/core/embed/extmod/modtrezorutils/modtrezorutils.c @@ -197,7 +197,7 @@ STATIC mp_obj_t mod_trezorutils_firmware_vendor(void) { vendor_header vhdr = {0}; uint32_t size = flash_sector_size(FLASH_SECTOR_FIRMWARE_START); const void *data = flash_get_address(FLASH_SECTOR_FIRMWARE_START, 0, size); - if (data == NULL || sectrue != read_vendor_header(data, &vhdr)) { + if (data == NULL || sectrue != read_vendor_header(data, size, &vhdr)) { mp_raise_msg(&mp_type_RuntimeError, "Failed to read vendor header."); } return mp_obj_new_str_copy(&mp_type_str, (const uint8_t *)vhdr.vstr, diff --git a/core/embed/trezorhal/image.c b/core/embed/trezorhal/image.c index c7af7d1452..af68710331 100644 --- a/core/embed/trezorhal/image.c +++ b/core/embed/trezorhal/image.c @@ -170,13 +170,19 @@ secbool load_thd89_image_header(const uint8_t* const data, const uint32_t magic, return sectrue; } -secbool read_vendor_header(const uint8_t* const data, +secbool read_vendor_header(const uint8_t* const data, size_t data_size, vendor_header* const vhdr) { + // The fixed fields and the first possible vendor-string length byte must fit. + if (data == NULL || vhdr == NULL || data_size < 33) return secfalse; + memcpy(&vhdr->magic, data, 4); if (vhdr->magic != 0x56544B4F) return secfalse; // OKTV memcpy(&vhdr->hdrlen, data + 4, 4); - if (vhdr->hdrlen > 64 * 1024) return secfalse; + if (vhdr->hdrlen < IMAGE_SIG_SIZE || + vhdr->hdrlen > VENDOR_HEADER_MAX_SIZE || vhdr->hdrlen > data_size) { + return secfalse; + } memcpy(&vhdr->expiry, data + 8, 4); if (vhdr->expiry != 0) return secfalse; @@ -191,6 +197,12 @@ secbool read_vendor_header(const uint8_t* const data, return secfalse; } + const size_t signature_offset = vhdr->hdrlen - IMAGE_SIG_SIZE; + const size_t vstr_len_offset = 32 + (size_t)vhdr->vsig_n * 32; + if (vstr_len_offset >= signature_offset) { + return secfalse; + } + for (int i = 0; i < vhdr->vsig_n; i++) { vhdr->vpub[i] = data + 32 + i * 32; } @@ -198,26 +210,34 @@ secbool read_vendor_header(const uint8_t* const data, vhdr->vpub[i] = 0; } - memcpy(&vhdr->vstr_len, data + 32 + vhdr->vsig_n * 32, 1); + memcpy(&vhdr->vstr_len, data + vstr_len_offset, 1); - vhdr->vstr = (const char*)(data + 32 + vhdr->vsig_n * 32 + 1); + const size_t vstr_offset = vstr_len_offset + 1; + if ((size_t)vhdr->vstr_len > signature_offset - vstr_offset) { + return secfalse; + } + + vhdr->vstr = (const char*)(data + vstr_offset); - vhdr->vimg = data + 32 + vhdr->vsig_n * 32 + 1 + vhdr->vstr_len; + vhdr->vimg = data + vstr_offset + vhdr->vstr_len; // align to 4 bytes vhdr->vimg += (-(uintptr_t)vhdr->vimg) & 3; + if (vhdr->vimg > data + signature_offset) { + return secfalse; + } - memcpy(&vhdr->sigmask, data + vhdr->hdrlen - IMAGE_SIG_SIZE, 1); + memcpy(&vhdr->sigmask, data + signature_offset, 1); - memcpy(vhdr->sig, data + vhdr->hdrlen - IMAGE_SIG_SIZE + 1, - IMAGE_SIG_SIZE - 1); + memcpy(vhdr->sig, data + signature_offset + 1, IMAGE_SIG_SIZE - 1); return sectrue; } -secbool load_vendor_header(const uint8_t* const data, uint8_t key_m, - uint8_t key_n, const uint8_t* const* keys, +secbool load_vendor_header(const uint8_t* const data, size_t data_size, + uint8_t key_m, uint8_t key_n, + const uint8_t* const* keys, vendor_header* const vhdr) { - if (sectrue != read_vendor_header(data, vhdr)) { + if (sectrue != read_vendor_header(data, data_size, vhdr)) { return secfalse; } @@ -712,8 +732,9 @@ secbool verify_firmware(vendor_header* const vhdr, image_header* const hdr, const size_t fw_external_size = FMC_SDRAM_FIRMWARE_P2_LEN; // verify vhdr - ExecuteCheck_ADV(load_vendor_header((const uint8_t*)FIRMWARE_START, FW_KEY_M, - FW_KEY_N, FW_KEYS, &_vhdr), + ExecuteCheck_ADV(load_vendor_header((const uint8_t*)FIRMWARE_START, + VENDOR_HEADER_MAX_SIZE, FW_KEY_M, FW_KEY_N, + FW_KEYS, &_vhdr), sectrue, { if (error_msg != NULL) strncpy(error_msg, "Firmware vendor header invalid!", diff --git a/core/embed/trezorhal/image.h b/core/embed/trezorhal/image.h index 5a96e10226..5eb5b05309 100644 --- a/core/embed/trezorhal/image.h +++ b/core/embed/trezorhal/image.h @@ -20,6 +20,7 @@ #ifndef __TREZORHAL_IMAGE_H__ #define __TREZORHAL_IMAGE_H__ +#include #include #include "secbool.h" @@ -33,6 +34,7 @@ #define IMAGE_HEADER_SIZE 0x400 // size of the bootloader or firmware header #define IMAGE_SIG_SIZE 65 +#define VENDOR_HEADER_MAX_SIZE (64 * 1024) #define IMAGE_CHUNK_SIZE (128 * 1024) #define IMAGE_INIT_CHUNK_SIZE (16 * 1024) @@ -179,11 +181,12 @@ secbool __wur load_thd89_image_header(const uint8_t* const data, const uint32_t maxsize, image_header_th89* const hdr); -secbool __wur load_vendor_header(const uint8_t* const data, uint8_t key_m, - uint8_t key_n, const uint8_t* const* keys, +secbool __wur load_vendor_header(const uint8_t* const data, size_t data_size, + uint8_t key_m, uint8_t key_n, + const uint8_t* const* keys, vendor_header* const vhdr); -secbool __wur read_vendor_header(const uint8_t* const data, +secbool __wur read_vendor_header(const uint8_t* const data, size_t data_size, vendor_header* const vhdr); void vendor_header_hash(const vendor_header* const vhdr, uint8_t* hash); From c02be1118167fc65110d0feb766d809bf1992427 Mon Sep 17 00:00:00 2001 From: lihuanhuan Date: Thu, 10 Sep 2026 10:43:36 +0800 Subject: [PATCH 11/11] feat: migrate Core FIDO to SE 1.3.2 managed credentials. --- core/embed/bootloader/bootui.c | 2 +- core/embed/bootloader/main.c | 2 +- .../extmod/modtrezorconfig/modtrezorconfig.c | 35 +- .../modtrezorcrypto-se-thd89.h | 243 ++++++++---- core/embed/firmware/main.c | 2 +- core/embed/firmware/version.h | 2 +- core/embed/trezorhal/se_thd89.c | 370 ++++++++++++------ core/embed/trezorhal/se_thd89.h | 46 +-- .../mocks/generated/trezorcrypto/se_thd89.pyi | 41 +- .../apps/webauthn/add_resident_credential.py | 5 +- core/src/apps/webauthn/credential.py | 32 +- core/src/apps/webauthn/fido2.py | 4 +- core/src/apps/webauthn/fido_seed.py | 28 +- .../src/apps/webauthn/resident_credentials.py | 45 ++- core/src/storage/device.py | 6 + core/src/storage/resident_credentials.py | 17 +- 16 files changed, 569 insertions(+), 311 deletions(-) diff --git a/core/embed/bootloader/bootui.c b/core/embed/bootloader/bootui.c index cfb0067850..e31346d78d 100644 --- a/core/embed/bootloader/bootui.c +++ b/core/embed/bootloader/bootui.c @@ -1055,7 +1055,7 @@ void ui_bootloader_se_version_required(const image_header* const hdr) { "SE firmware update required", -1, FONT_NORMAL, COLOR_BL_FG, COLOR_BL_BG); display_text_center(DISPLAY_RESX / 2, SUBTITLE_OFFSET_Y + 36, - "Version 1.3.0 or later is required", -1, FONT_NORMAL, + "Version 1.3.2 or later is required", -1, FONT_NORMAL, COLOR_BL_SUBTITLE, COLOR_BL_BG); display_text_center(DISPLAY_RESX / 2, SUBTITLE_OFFSET_Y + 72, "Install an SE firmware update", -1, FONT_NORMAL, diff --git a/core/embed/bootloader/main.c b/core/embed/bootloader/main.c index e81e81e938..47e7504107 100644 --- a/core/embed/bootloader/main.c +++ b/core/embed/bootloader/main.c @@ -62,7 +62,7 @@ #define REQUIRED_SE_VERSION_MAJOR 1 #define REQUIRED_SE_VERSION_MINOR 3 -#define REQUIRED_SE_VERSION_PATCH 0 +#define REQUIRED_SE_VERSION_PATCH 2 #if defined(STM32H747xx) #include "stm32h7xx_hal.h" diff --git a/core/embed/extmod/modtrezorconfig/modtrezorconfig.c b/core/embed/extmod/modtrezorconfig/modtrezorconfig.c index 7b96fa885c..33c4f7f158 100644 --- a/core/embed/extmod/modtrezorconfig/modtrezorconfig.c +++ b/core/embed/extmod/modtrezorconfig/modtrezorconfig.c @@ -419,17 +419,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorconfig_get_val_len_obj, 2, /// """ STATIC mp_obj_t mod_trezorconfig_get(size_t n_args, const mp_obj_t *args) { uint8_t app = trezor_obj_get_uint8(args[0]); - // webauthn resident credentials, FIDO2 if (app == 4) { - uint32_t index = trezor_obj_get_uint(args[1]); - uint16_t len = sizeof(CTAP_credential_id_storage) - - FIDO2_RESIDENT_CREDENTIALS_HEADER_LEN; - CTAP_credential_id_storage cred_id = {0}; - - if (!se_get_fido2_resident_credentials(index, cred_id.rp_id_hash, &len)) { - return mp_const_none; - } - return mp_obj_new_bytes(cred_id.rp_id_hash, len); + mp_raise_msg(&mp_type_RuntimeError, + "FIDO credentials are managed by the secure element"); } uint32_t key = trezor_obj_get_uint(args[1]); @@ -475,20 +467,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorconfig_get_obj, 2, 3, /// """ STATIC mp_obj_t mod_trezorconfig_set(size_t n_args, const mp_obj_t *args) { uint8_t app = trezor_obj_get_uint8(args[0]); - // webauthn resident credentials, FIDO2 if (app == 4) { - uint32_t index = trezor_obj_get_uint(args[1]); - - mp_buffer_info_t cred_id; - mp_get_buffer_raise(args[2], &cred_id, MP_BUFFER_READ); - if (cred_id.len > sizeof(CTAP_credential_id_storage) - - FIDO2_RESIDENT_CREDENTIALS_HEADER_LEN) { - mp_raise_msg(&mp_type_RuntimeError, "Credential ID too long"); - } - if (!se_set_fido2_resident_credentials(index, cred_id.buf, cred_id.len)) { - mp_raise_msg(&mp_type_RuntimeError, "Could not save value"); - } - return mp_const_none; + mp_raise_msg(&mp_type_RuntimeError, + "FIDO credentials are managed by the secure element"); } uint32_t key = trezor_obj_get_uint(args[1]); @@ -525,13 +506,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorconfig_set_obj, 3, 4, /// """ STATIC mp_obj_t mod_trezorconfig_delete(size_t n_args, const mp_obj_t *args) { uint8_t app = trezor_obj_get_uint8(args[0]); - // webauthn resident credentials, FIDO2 if (app == 4) { - uint32_t index = trezor_obj_get_uint(args[1]); - if (!se_delete_fido2_resident_credentials(index)) { - mp_raise_msg(&mp_type_RuntimeError, "Could not delete value"); - } - return mp_const_true; + mp_raise_msg(&mp_type_RuntimeError, + "FIDO credentials are managed by the secure element"); } uint32_t key = trezor_obj_get_uint(args[1]); diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h index 2c42457050..8a05793cfd 100644 --- a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h @@ -1083,84 +1083,188 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1( mod_trezorcrypto_se_thd89_fido_att_sign_digest_obj, mod_trezorcrypto_se_thd89_fido_att_sign_digest); -/// def fido_credential_encrypt(rp_id_hash: bytes, plaintext: bytes) -> bytes: -/// """Encrypt a SLIP-0022 credential ID inside the secure element.""" -STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_credential_encrypt( - mp_obj_t rp_id_hash_obj, mp_obj_t plaintext_obj) { - mp_buffer_info_t rp_id_hash = {0}; +/// def fido_credential_create( +/// plaintext: bytes, resident: bool +/// ) -> tuple[bytes, int, int]: +STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_credential_create( + mp_obj_t plaintext_obj, mp_obj_t resident_obj) { mp_buffer_info_t plaintext = {0}; + uint16_t response_len = 516; uint16_t credential_id_len = 0; - vstr_t credential_id = {0}; - mp_get_buffer_raise(rp_id_hash_obj, &rp_id_hash, MP_BUFFER_READ); + vstr_t response = {0}; + mp_obj_t result[3] = {0}; + mp_get_buffer_raise(plaintext_obj, &plaintext, MP_BUFFER_READ); - if (rp_id_hash.len != 32 || plaintext.len == 0 || plaintext.len > 480) { + if (plaintext.len == 0 || plaintext.len > 480 || + (resident_obj != mp_const_false && resident_obj != mp_const_true)) { mp_raise_ValueError("invalid FIDO credential data"); } - vstr_init_len(&credential_id, 512); - if (se_fido_credential_encrypt(rp_id_hash.buf, plaintext.buf, plaintext.len, - (uint8_t *)credential_id.buf, - &credential_id_len) != sectrue) { - mp_raise_ValueError("FIDO credential encryption failed"); - } - credential_id.len = credential_id_len; - return mp_obj_new_str_from_vstr(&mp_type_bytes, &credential_id); + vstr_init_len(&response, response_len); + if (se_fido_credential_create(plaintext.buf, plaintext.len, + resident_obj == mp_const_true ? 1 : 0, + (uint8_t *)response.buf, + &response_len) != sectrue) { + memzero(response.buf, response.len); + vstr_clear(&response); + mp_raise_ValueError("FIDO credential creation failed"); + } + credential_id_len = ((uint16_t)(uint8_t)response.buf[0] << 8) | + (uint8_t)response.buf[1]; + result[0] = mp_obj_new_bytes((const uint8_t *)response.buf + 2, + credential_id_len); + result[1] = mp_obj_new_int((uint8_t)response.buf[credential_id_len + 2]); + result[2] = mp_obj_new_int((uint8_t)response.buf[credential_id_len + 3]); + memzero(response.buf, response.len); + vstr_clear(&response); + return mp_obj_new_tuple(3, result); } STATIC MP_DEFINE_CONST_FUN_OBJ_2( - mod_trezorcrypto_se_thd89_fido_credential_encrypt_obj, - mod_trezorcrypto_se_thd89_fido_credential_encrypt); + mod_trezorcrypto_se_thd89_fido_credential_create_obj, + mod_trezorcrypto_se_thd89_fido_credential_create); -/// def fido_credential_peek(credential_id: bytes) -> bytes: -/// """Tentatively decrypt a credential for legacy RP-ID discovery.""" -STATIC mp_obj_t -mod_trezorcrypto_se_thd89_fido_credential_peek(mp_obj_t credential_id_obj) { +/// def fido_credential_validate( +/// credential_id: bytes, rp_id_hash: bytes | None +/// ) -> bytes: +STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_credential_validate( + mp_obj_t credential_id_obj, mp_obj_t rp_id_hash_obj) { mp_buffer_info_t credential_id = {0}; + mp_buffer_info_t rp_id_hash = {0}; + const uint8_t *rp_id_hash_ptr = NULL; uint16_t plaintext_len = 0; vstr_t plaintext = {0}; + mp_get_buffer_raise(credential_id_obj, &credential_id, MP_BUFFER_READ); if (credential_id.len < 33 || credential_id.len > 512) { mp_raise_ValueError("invalid FIDO credential ID"); } - vstr_init_len(&plaintext, 480); - if (se_fido_credential_peek(credential_id.buf, credential_id.len, - (uint8_t *)plaintext.buf, - &plaintext_len) != sectrue) { - mp_raise_ValueError("FIDO credential peek failed"); + if (rp_id_hash_obj != mp_const_none) { + mp_get_buffer_raise(rp_id_hash_obj, &rp_id_hash, MP_BUFFER_READ); + if (rp_id_hash.len != 32) { + mp_raise_ValueError("invalid FIDO RP ID hash"); + } + rp_id_hash_ptr = rp_id_hash.buf; + } + plaintext_len = credential_id.len - 32; + vstr_init_len(&plaintext, plaintext_len); + if (se_fido_credential_validate(rp_id_hash_ptr, credential_id.buf, + credential_id.len, + (uint8_t *)plaintext.buf, + &plaintext_len) != sectrue) { + memzero(plaintext.buf, plaintext.len); + vstr_clear(&plaintext); + mp_raise_ValueError("FIDO credential validation failed"); } plaintext.len = plaintext_len; return mp_obj_new_str_from_vstr(&mp_type_bytes, &plaintext); } +STATIC MP_DEFINE_CONST_FUN_OBJ_2( + mod_trezorcrypto_se_thd89_fido_credential_validate_obj, + mod_trezorcrypto_se_thd89_fido_credential_validate); + +/// def fido_resident_credentials_list() -> tuple[int, ...]: +STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_resident_credentials_list(void) { + uint8_t indexes[FIDO2_RESIDENT_CREDENTIALS_COUNT] = {0}; + uint16_t count = FIDO2_RESIDENT_CREDENTIALS_COUNT; + mp_obj_tuple_t *result = NULL; + + if (se_fido_resident_credentials_list(indexes, &count) != sectrue) { + mp_raise_ValueError("FIDO resident credential list failed"); + } + result = MP_OBJ_TO_PTR(mp_obj_new_tuple(count, NULL)); + for (uint16_t i = 0; i < count; i++) { + result->items[i] = mp_obj_new_int(indexes[i]); + } + return MP_OBJ_FROM_PTR(result); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0( + mod_trezorcrypto_se_thd89_fido_resident_credentials_list_obj, + mod_trezorcrypto_se_thd89_fido_resident_credentials_list); + +/// def fido_resident_credential_read(index: int) -> tuple[bytes, bytes]: +STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_resident_credential_read( + mp_obj_t index_obj) { + uint8_t index = trezor_obj_get_uint8(index_obj); + uint16_t packed_len = 920; + uint16_t credential_id_len = 0; + uint16_t plaintext_len = 0; + vstr_t packed = {0}; + mp_obj_t result[2] = {0}; + + if (index >= FIDO2_RESIDENT_CREDENTIALS_COUNT) { + mp_raise_ValueError("invalid FIDO resident credential index"); + } + vstr_init_len(&packed, packed_len); + if (se_fido_resident_credential_read(index, (uint8_t *)packed.buf, + &packed_len) != sectrue) { + memzero(packed.buf, packed.len); + vstr_clear(&packed); + mp_raise_ValueError("FIDO resident credential read failed"); + } + credential_id_len = ((uint16_t)(uint8_t)packed.buf[0] << 8) | + (uint8_t)packed.buf[1]; + plaintext_len = ((uint16_t)(uint8_t)packed.buf[credential_id_len + 2] << 8) | + (uint8_t)packed.buf[credential_id_len + 3]; + result[0] = mp_obj_new_bytes((const uint8_t *)packed.buf + 2, + credential_id_len); + result[1] = mp_obj_new_bytes( + (const uint8_t *)packed.buf + credential_id_len + 4, plaintext_len); + memzero(packed.buf, packed.len); + vstr_clear(&packed); + return mp_obj_new_tuple(2, result); +} STATIC MP_DEFINE_CONST_FUN_OBJ_1( - mod_trezorcrypto_se_thd89_fido_credential_peek_obj, - mod_trezorcrypto_se_thd89_fido_credential_peek); + mod_trezorcrypto_se_thd89_fido_resident_credential_read_obj, + mod_trezorcrypto_se_thd89_fido_resident_credential_read); -/// def fido_credential_decrypt( -/// rp_id_hash: bytes, credential_id: bytes -/// ) -> bytes: -/// """Authenticate and decrypt a SLIP-0022 credential ID.""" -STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_credential_decrypt( - mp_obj_t rp_id_hash_obj, mp_obj_t credential_id_obj) { - mp_buffer_info_t rp_id_hash = {0}; +/// def fido_resident_credential_import(credential_id: bytes) -> tuple[int, int]: +STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_resident_credential_import( + mp_obj_t credential_id_obj) { mp_buffer_info_t credential_id = {0}; - uint16_t plaintext_len = 0; - vstr_t plaintext = {0}; - mp_get_buffer_raise(rp_id_hash_obj, &rp_id_hash, MP_BUFFER_READ); + uint8_t slot = 0; + uint8_t action = 0; + mp_obj_t result[2] = {0}; + mp_get_buffer_raise(credential_id_obj, &credential_id, MP_BUFFER_READ); - if (rp_id_hash.len != 32 || credential_id.len < 33 || - credential_id.len > 512) { - mp_raise_ValueError("invalid FIDO credential data"); + if (credential_id.len < 33 || credential_id.len > 474) { + mp_raise_ValueError("invalid FIDO credential ID"); } - vstr_init_len(&plaintext, 480); - if (se_fido_credential_decrypt(rp_id_hash.buf, credential_id.buf, - credential_id.len, (uint8_t *)plaintext.buf, - &plaintext_len) != sectrue) { - mp_raise_ValueError("FIDO credential decryption failed"); + if (se_fido_resident_credential_import(credential_id.buf, credential_id.len, + &slot, &action) != sectrue) { + mp_raise_ValueError("FIDO resident credential import failed"); } - plaintext.len = plaintext_len; - return mp_obj_new_str_from_vstr(&mp_type_bytes, &plaintext); + result[0] = mp_obj_new_int(slot); + result[1] = mp_obj_new_int(action); + return mp_obj_new_tuple(2, result); } -STATIC MP_DEFINE_CONST_FUN_OBJ_2( - mod_trezorcrypto_se_thd89_fido_credential_decrypt_obj, - mod_trezorcrypto_se_thd89_fido_credential_decrypt); +STATIC MP_DEFINE_CONST_FUN_OBJ_1( + mod_trezorcrypto_se_thd89_fido_resident_credential_import_obj, + mod_trezorcrypto_se_thd89_fido_resident_credential_import); + +/// def fido_resident_credential_delete(index: int) -> None: +STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_resident_credential_delete( + mp_obj_t index_obj) { + uint8_t index = trezor_obj_get_uint8(index_obj); + + if (index >= FIDO2_RESIDENT_CREDENTIALS_COUNT || + se_fido_resident_credential_delete(index) != sectrue) { + mp_raise_ValueError("FIDO resident credential delete failed"); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1( + mod_trezorcrypto_se_thd89_fido_resident_credential_delete_obj, + mod_trezorcrypto_se_thd89_fido_resident_credential_delete); + +/// def fido_resident_credentials_clear() -> None: +STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_resident_credentials_clear(void) { + if (se_fido_resident_credentials_clear() != sectrue) { + mp_raise_ValueError("FIDO resident credential clear failed"); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0( + mod_trezorcrypto_se_thd89_fido_resident_credentials_clear_obj, + mod_trezorcrypto_se_thd89_fido_resident_credentials_clear); /// def fido_hmac_secret(credential_id: bytes, salt: bytes) -> bytes: /// """Return the purpose-bound hmac-secret output for one or two salts.""" @@ -1185,19 +1289,6 @@ STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_hmac_secret( STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_se_thd89_fido_hmac_secret_obj, mod_trezorcrypto_se_thd89_fido_hmac_secret); -/// def fido_delete_all_credentials() -> None: -/// """ -/// Delete all FIDO2 credentials. -/// """ -STATIC mp_obj_t mod_trezorcrypto_se_thd89_fido_delete_all_credentials(void) { - se_delete_all_fido2_credentials(); - return mp_const_none; -} - -STATIC MP_DEFINE_CONST_FUN_OBJ_0( - mod_trezorcrypto_se_thd89_fido_delete_all_credentials_obj, - mod_trezorcrypto_se_thd89_fido_delete_all_credentials); - /// def get_pin_passphrase_space() -> int: /// """ /// get the number of available pin-passphrase slots. @@ -1435,20 +1526,26 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_se_thd89_globals_table[] = { MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_u2f_authenticate_obj)}, {MP_ROM_QSTR(MP_QSTR_fido_u2f_validate), MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_u2f_validate_obj)}, - {MP_ROM_QSTR(MP_QSTR_fido_credential_encrypt), - MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_credential_encrypt_obj)}, - {MP_ROM_QSTR(MP_QSTR_fido_credential_peek), - MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_credential_peek_obj)}, - {MP_ROM_QSTR(MP_QSTR_fido_credential_decrypt), - MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_credential_decrypt_obj)}, + {MP_ROM_QSTR(MP_QSTR_fido_credential_create), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_credential_create_obj)}, + {MP_ROM_QSTR(MP_QSTR_fido_credential_validate), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_credential_validate_obj)}, + {MP_ROM_QSTR(MP_QSTR_fido_resident_credentials_list), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_resident_credentials_list_obj)}, + {MP_ROM_QSTR(MP_QSTR_fido_resident_credential_read), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_resident_credential_read_obj)}, + {MP_ROM_QSTR(MP_QSTR_fido_resident_credential_import), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_resident_credential_import_obj)}, + {MP_ROM_QSTR(MP_QSTR_fido_resident_credential_delete), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_resident_credential_delete_obj)}, + {MP_ROM_QSTR(MP_QSTR_fido_resident_credentials_clear), + MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_resident_credentials_clear_obj)}, {MP_ROM_QSTR(MP_QSTR_fido_hmac_secret), MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_hmac_secret_obj)}, {MP_ROM_QSTR(MP_QSTR_fido_sign_digest), MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_sign_digest_obj)}, {MP_ROM_QSTR(MP_QSTR_fido_att_sign_digest), MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_att_sign_digest_obj)}, - {MP_ROM_QSTR(MP_QSTR_fido_delete_all_credentials), - MP_ROM_PTR(&mod_trezorcrypto_se_thd89_fido_delete_all_credentials_obj)}, {MP_ROM_QSTR(MP_QSTR_FIDO2_CRED_COUNT_MAX), MP_ROM_INT(FIDO2_RESIDENT_CREDENTIALS_COUNT)}, {MP_ROM_QSTR(MP_QSTR_get_pin_passphrase_space), diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c index cc325f03b5..8a17bf339b 100644 --- a/core/embed/firmware/main.c +++ b/core/embed/firmware/main.c @@ -87,7 +87,7 @@ static void __attribute__((noreturn)) show_se_version_required(void) { sizeof(toi_icon_warning) - 12); display_text(8, 140, "SE firmware update required.", -1, FONT_NORMAL, COLOR_WHITE, COLOR_BLACK); - display_text(8, 720, "Version 1.3.0 or later is required.", -1, + display_text(8, 720, "Version 1.3.2 or later is required.", -1, FONT_NORMAL, RGB16(0x69, 0x69, 0x69), COLOR_BLACK); display_text(8, 784, "Tap to enter Update Mode.", -1, FONT_NORMAL, COLOR_WHITE, COLOR_BLACK); diff --git a/core/embed/firmware/version.h b/core/embed/firmware/version.h index b830f8c6b4..1234e120bc 100644 --- a/core/embed/firmware/version.h +++ b/core/embed/firmware/version.h @@ -16,7 +16,7 @@ // Minimum SE version required for firmware upgrade #define SE_MINIMUM_VERSION_MAJOR 1 #define SE_MINIMUM_VERSION_MINOR 3 -#define SE_MINIMUM_VERSION_PATCH 0 +#define SE_MINIMUM_VERSION_PATCH 2 #define SE_MINIMUM_VERSION_UINT32 \ (SE_MINIMUM_VERSION_MAJOR | (SE_MINIMUM_VERSION_MINOR << 8) | \ (SE_MINIMUM_VERSION_PATCH << 16) | (0 << 24)) diff --git a/core/embed/trezorhal/se_thd89.c b/core/embed/trezorhal/se_thd89.c index 5e610220ae..630b8579d7 100644 --- a/core/embed/trezorhal/se_thd89.c +++ b/core/embed/trezorhal/se_thd89.c @@ -57,15 +57,22 @@ typedef enum { SE_FIDO_DERIVE_NODE, SE_FIDO_NODE_SIGN, SE_FIDO_ATT_SIGN, - SE_FIDO_SLIP21_CREDENTIAL_ENCRYPT, - SE_FIDO_SLIP21_CREDENTIAL_PEEK, - SE_FIDO_SLIP21_CREDENTIAL_DECRYPT, - SE_FIDO_SLIP21_HMAC_SECRET, + SE_FIDO_SLIP21_HMAC_SECRET = 0x0E, + SE_FIDO_LIST_RESIDENT_CREDENTIALS, + SE_FIDO_READ_RESIDENT_CREDENTIAL, + SE_FIDO_CREATE_CREDENTIAL, + SE_FIDO_VALIDATE_CREDENTIAL, + SE_FIDO_DELETE_RESIDENT_CREDENTIAL, + SE_FIDO_CLEAR_RESIDENT_CREDENTIALS, + SE_FIDO_IMPORT_RESIDENT_CREDENTIAL, } SE_FIDO_P2; #define SE_FIDO_CREDENTIAL_ID_MIN_LEN 33U #define SE_FIDO_CREDENTIAL_ID_MAX_LEN 512U #define SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN 480U +#define SE_FIDO_RESIDENT_CREDENTIAL_ID_MAX_LEN 474U +#define SE_FIDO_RESIDENT_CREDENTIAL_PLAINTEXT_MAX_LEN 442U +#define SE_FIDO_RESIDENT_CREDENTIAL_READ_MAX_LEN 920U #define SE_PIN_RETRY_MAX 5 #define SE_SW_PIN_RETRY_LIMIT_REACHED 0x6983 @@ -3220,173 +3227,284 @@ secbool se_fido_att_sign_digest(const uint8_t *hash, uint8_t *sig) { return sectrue; } -secbool se_fido_credential_encrypt(const uint8_t rp_id_hash[32], - const uint8_t *plaintext, - uint16_t plaintext_len, - uint8_t *credential_id, - uint16_t *credential_id_len) { - uint16_t resp_len = SE_FIDO_CREDENTIAL_ID_MAX_LEN; +secbool se_fido_hmac_secret(const uint8_t *credential_id, + uint16_t credential_id_len, const uint8_t *salt, + uint16_t salt_len, uint8_t *out) { + uint16_t resp_len = salt_len; - if (rp_id_hash == NULL || plaintext == NULL || plaintext_len == 0 || - plaintext_len > SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN || - credential_id == NULL || credential_id_len == NULL) { + if (credential_id == NULL || + credential_id_len < SE_FIDO_CREDENTIAL_ID_MIN_LEN || + credential_id_len > SE_FIDO_CREDENTIAL_ID_MAX_LEN || salt == NULL || + (salt_len != 32 && salt_len != 64) || out == NULL) { return secfalse; } - memcpy(APDU_DATA, rp_id_hash, 32); - memcpy(APDU_DATA + 32, plaintext, plaintext_len); - if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_CREDENTIAL_ENCRYPT, - APDU_DATA, plaintext_len + 32U, credential_id, - &resp_len) || - resp_len != plaintext_len + 32U) { + APDU_DATA[0] = (uint8_t)(credential_id_len >> 8); + APDU_DATA[1] = (uint8_t)credential_id_len; + memcpy(APDU_DATA + 2, credential_id, credential_id_len); + APDU_DATA[2 + credential_id_len] = (uint8_t)salt_len; + memcpy(APDU_DATA + 3 + credential_id_len, salt, salt_len); + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_HMAC_SECRET, APDU_DATA, + credential_id_len + salt_len + 3U, out, &resp_len) || + resp_len != salt_len) { return secfalse; } - *credential_id_len = resp_len; return sectrue; } -secbool se_fido_credential_peek(const uint8_t *credential_id, - uint16_t credential_id_len, uint8_t *plaintext, - uint16_t *plaintext_len) { - uint16_t resp_len = SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN; +secbool se_fido_credential_create(const uint8_t *plaintext, + uint16_t plaintext_len, uint8_t resident, + uint8_t *response, uint16_t *response_len) { + uint16_t response_capacity = 0; + uint16_t response_received = 0; + uint16_t credential_id_len = 0; + uint16_t expected_len = 0; - if (credential_id == NULL || - credential_id_len < SE_FIDO_CREDENTIAL_ID_MIN_LEN || - credential_id_len > SE_FIDO_CREDENTIAL_ID_MAX_LEN || plaintext == NULL || - plaintext_len == NULL) { - return secfalse; + if (response_len != NULL) { + response_capacity = *response_len; + *response_len = 0; } - if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_CREDENTIAL_PEEK, - (uint8_t *)credential_id, credential_id_len, plaintext, - &resp_len) || - resp_len != credential_id_len - 32U) { - return secfalse; + if (plaintext == NULL || plaintext_len == 0 || + plaintext_len > SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN || resident > 1 || + response == NULL || response_len == NULL || + (resident != 0 && + plaintext_len > SE_FIDO_RESIDENT_CREDENTIAL_PLAINTEXT_MAX_LEN)) { + goto cleanup; + } + + credential_id_len = plaintext_len + 32U; + expected_len = credential_id_len + 4U; + if (response_capacity < expected_len) { + goto cleanup; } - *plaintext_len = resp_len; + + APDU_DATA[0] = resident; + memcpy(APDU_DATA + 1, plaintext, plaintext_len); + response_received = response_capacity; + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_CREATE_CREDENTIAL, + APDU_DATA, plaintext_len + 1U, response, + &response_received) || + response_received != expected_len || + (((uint16_t)response[0] << 8) | response[1]) != credential_id_len) { + goto cleanup; + } + + if ((resident == 0 && + (response[expected_len - 2] != 0xff || response[expected_len - 1] != 0)) || + (resident != 0 && + (response[expected_len - 2] >= FIDO2_RESIDENT_CREDENTIALS_COUNT || + (response[expected_len - 1] != 1 && response[expected_len - 1] != 2)))) { + goto cleanup; + } + + *response_len = response_received; return sectrue; + +cleanup: + if (response != NULL) { + memzero(response, response_capacity); + } + return secfalse; } -secbool se_fido_credential_decrypt(const uint8_t rp_id_hash[32], - const uint8_t *credential_id, - uint16_t credential_id_len, - uint8_t *plaintext, - uint16_t *plaintext_len) { - uint16_t resp_len = SE_FIDO_CREDENTIAL_PLAINTEXT_MAX_LEN; +secbool se_fido_credential_validate(const uint8_t rp_id_hash[32], + const uint8_t *credential_id, + uint16_t credential_id_len, + uint8_t *plaintext, + uint16_t *plaintext_len) { + uint16_t plaintext_capacity = 0; + uint16_t plaintext_received = 0; + uint16_t expected_len = 0; + uint16_t request_len = 0; - if (rp_id_hash == NULL || credential_id == NULL || + if (plaintext_len != NULL) { + plaintext_capacity = *plaintext_len; + *plaintext_len = 0; + } + if (credential_id == NULL || credential_id_len < SE_FIDO_CREDENTIAL_ID_MIN_LEN || credential_id_len > SE_FIDO_CREDENTIAL_ID_MAX_LEN || plaintext == NULL || plaintext_len == NULL) { - return secfalse; + goto cleanup; } - memcpy(APDU_DATA, rp_id_hash, 32); - memcpy(APDU_DATA + 32, credential_id, credential_id_len); - if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_CREDENTIAL_DECRYPT, - APDU_DATA, credential_id_len + 32U, plaintext, - &resp_len) || - resp_len != credential_id_len - 32U) { - return secfalse; + + expected_len = credential_id_len - 32U; + if (plaintext_capacity < expected_len) { + goto cleanup; } - *plaintext_len = resp_len; + + APDU_DATA[0] = rp_id_hash != NULL ? 1 : 0; + request_len = 1; + if (rp_id_hash != NULL) { + memcpy(APDU_DATA + request_len, rp_id_hash, 32); + request_len += 32; + } + memcpy(APDU_DATA + request_len, credential_id, credential_id_len); + request_len += credential_id_len; + plaintext_received = plaintext_capacity; + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_VALIDATE_CREDENTIAL, + APDU_DATA, request_len, plaintext, &plaintext_received) || + plaintext_received != expected_len) { + goto cleanup; + } + + *plaintext_len = plaintext_received; return sectrue; + +cleanup: + if (plaintext != NULL) { + memzero(plaintext, plaintext_capacity); + } + return secfalse; } -secbool se_fido_hmac_secret(const uint8_t *credential_id, - uint16_t credential_id_len, const uint8_t *salt, - uint16_t salt_len, uint8_t *out) { - uint16_t resp_len = salt_len; +secbool se_fido_resident_credentials_list(uint8_t *indexes, uint16_t *count) { + uint8_t response[FIDO2_RESIDENT_CREDENTIALS_COUNT + 1] = {0}; + uint16_t count_capacity = 0; + uint16_t response_len = sizeof(response); + uint8_t response_count = 0; + uint8_t previous = 0; - if (credential_id == NULL || - credential_id_len < SE_FIDO_CREDENTIAL_ID_MIN_LEN || - credential_id_len > SE_FIDO_CREDENTIAL_ID_MAX_LEN || salt == NULL || - (salt_len != 32 && salt_len != 64) || out == NULL) { - return secfalse; + if (count != NULL) { + count_capacity = *count; + *count = 0; } - APDU_DATA[0] = (uint8_t)(credential_id_len >> 8); - APDU_DATA[1] = (uint8_t)credential_id_len; - memcpy(APDU_DATA + 2, credential_id, credential_id_len); - APDU_DATA[2 + credential_id_len] = (uint8_t)salt_len; - memcpy(APDU_DATA + 3 + credential_id_len, salt, salt_len); - if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_SLIP21_HMAC_SECRET, APDU_DATA, - credential_id_len + salt_len + 3U, out, &resp_len) || - resp_len != salt_len) { - return secfalse; + if (indexes == NULL || count == NULL || + !se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_LIST_RESIDENT_CREDENTIALS, + NULL, 0, response, &response_len) || response_len == 0) { + goto cleanup; } - return sectrue; -} -secbool se_get_fido2_data(uint16_t offset, uint8_t *dest, uint16_t len) { - uint8_t cmd[4] = {0}; - uint16_t recv_len = len; - cmd[0] = (offset >> 8) & 0xFF; - cmd[1] = offset & 0xFF; - cmd[2] = (len >> 8) & 0xFF; - cmd[3] = len & 0xFF; - if (!se_transmit_mac(SE_INS_READ_DATA, 0x00, 0x03, cmd, sizeof(cmd), dest, - &recv_len)) { - return secfalse; + response_count = response[0]; + if (response_count > FIDO2_RESIDENT_CREDENTIALS_COUNT || + response_len != (uint16_t)response_count + 1U || + count_capacity < response_count) { + goto cleanup; + } + for (uint8_t i = 0; i < response_count; i++) { + if (response[i + 1] >= FIDO2_RESIDENT_CREDENTIALS_COUNT || + (i != 0 && response[i + 1] <= previous)) { + goto cleanup; + } + previous = response[i + 1]; } + + memcpy(indexes, response + 1, response_count); + *count = response_count; + memzero(response, sizeof(response)); return sectrue; + +cleanup: + memzero(response, sizeof(response)); + if (indexes != NULL) { + memzero(indexes, count_capacity); + } + return secfalse; } -secbool se_set_fido2_data(uint16_t offset, const uint8_t *src, uint16_t len) { - uint8_t cmd[4] = {0}; - cmd[0] = (offset >> 8) & 0xFF; - cmd[1] = offset & 0xFF; - cmd[2] = (len >> 8) & 0xFF; - cmd[3] = len & 0xFF; - memcpy(APDU_DATA, cmd, 4); - memcpy(APDU_DATA + 4, src, len); - if (!se_transmit_mac(SE_INS_WRITE_DATA, 0x00, 0x03, APDU_DATA, 4 + len, NULL, - NULL)) { - return secfalse; +secbool se_fido_resident_credential_read(uint8_t index, uint8_t *packed, + uint16_t *packed_len) { + uint16_t packed_capacity = 0; + uint16_t packed_received = 0; + uint16_t credential_id_len = 0; + uint16_t plaintext_len = 0; + uint16_t expected_len = 0; + + if (packed_len != NULL) { + packed_capacity = *packed_len; + *packed_len = 0; } + if (index >= FIDO2_RESIDENT_CREDENTIALS_COUNT || packed == NULL || + packed_len == NULL) { + goto cleanup; + } + + packed_received = packed_capacity; + if (!se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_READ_RESIDENT_CREDENTIAL, + &index, 1, packed, &packed_received) || + packed_received < 5U) { + goto cleanup; + } + credential_id_len = ((uint16_t)packed[0] << 8) | packed[1]; + if (credential_id_len < SE_FIDO_CREDENTIAL_ID_MIN_LEN || + credential_id_len > SE_FIDO_RESIDENT_CREDENTIAL_ID_MAX_LEN || + packed_received < credential_id_len + 4U) { + goto cleanup; + } + plaintext_len = ((uint16_t)packed[credential_id_len + 2] << 8) | + packed[credential_id_len + 3]; + expected_len = credential_id_len + plaintext_len + 4U; + if (plaintext_len == 0 || + plaintext_len > SE_FIDO_RESIDENT_CREDENTIAL_PLAINTEXT_MAX_LEN || + plaintext_len != credential_id_len - 32U || packed_received != expected_len || + packed_received > SE_FIDO_RESIDENT_CREDENTIAL_READ_MAX_LEN) { + goto cleanup; + } + + *packed_len = packed_received; return sectrue; + +cleanup: + if (packed != NULL) { + memzero(packed, packed_capacity); + } + return secfalse; } -secbool se_get_fido2_resident_credentials(uint32_t index, uint8_t *dest, - uint16_t *dst_len) { - if (index >= FIDO2_RESIDENT_CREDENTIALS_COUNT) return secfalse; - uint8_t buffer[FIDO2_RESIDENT_CREDENTIALS_SIZE]; - CTAP_credential_id_storage *cred_id = (CTAP_credential_id_storage *)buffer; - if (!se_get_fido2_data(index * FIDO2_RESIDENT_CREDENTIALS_SIZE, buffer, 6)) { - return secfalse; +secbool se_fido_resident_credential_import(const uint8_t *credential_id, + uint16_t credential_id_len, + uint8_t *slot, uint8_t *action) { + uint8_t response[2] = {0}; + uint16_t response_len = sizeof(response); + + if (slot != NULL) { + *slot = 0; } - if (memcmp(cred_id->credential_id_flag, FIDO2_RESIDENT_CREDENTIALS_FLAGS, - 4) != 0) { - return secfalse; + if (action != NULL) { + *action = 0; } - if (*dst_len < cred_id->credential_length) { + if (credential_id == NULL || + credential_id_len < SE_FIDO_CREDENTIAL_ID_MIN_LEN || + credential_id_len > SE_FIDO_RESIDENT_CREDENTIAL_ID_MAX_LEN || slot == NULL || + action == NULL || + !se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_IMPORT_RESIDENT_CREDENTIAL, + (uint8_t *)credential_id, credential_id_len, response, + &response_len) || + response_len != sizeof(response) || + response[0] >= FIDO2_RESIDENT_CREDENTIALS_COUNT || + (response[1] != 1 && response[1] != 2)) { + goto cleanup; + } + + *slot = response[0]; + *action = response[1]; + memzero(response, sizeof(response)); + return sectrue; + +cleanup: + memzero(response, sizeof(response)); + return secfalse; +} + +secbool se_fido_resident_credential_delete(uint8_t index) { + uint16_t response_len = 0; + + if (index >= FIDO2_RESIDENT_CREDENTIALS_COUNT) { return secfalse; } - if (!se_get_fido2_data(index * FIDO2_RESIDENT_CREDENTIALS_SIZE + 6, - buffer + 6, cred_id->credential_length)) { + if (se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_DELETE_RESIDENT_CREDENTIAL, + &index, 1, NULL, &response_len) != sectrue || + response_len != 0) { return secfalse; } - *dst_len = cred_id->credential_length; - memcpy(dest, cred_id->rp_id_hash, *dst_len); return sectrue; } -secbool se_set_fido2_resident_credentials(uint32_t index, const uint8_t *src, - uint16_t len) { - if (index >= FIDO2_RESIDENT_CREDENTIALS_COUNT) return secfalse; - if (len > (FIDO2_RESIDENT_CREDENTIALS_SIZE - 6)) return secfalse; - CTAP_credential_id_storage cred_id = {0}; - memcpy(cred_id.credential_id_flag, FIDO2_RESIDENT_CREDENTIALS_FLAGS, 4); - cred_id.credential_length = len; - memcpy(cred_id.rp_id_hash, src, len); - return se_set_fido2_data(index * FIDO2_RESIDENT_CREDENTIALS_SIZE, - (uint8_t *)&cred_id, 6 + len); -} - -secbool se_delete_fido2_resident_credentials(uint32_t index) { - uint8_t buffer[FIDO2_RESIDENT_CREDENTIALS_HEADER_LEN] = {0xff}; - return se_set_fido2_data(index * FIDO2_RESIDENT_CREDENTIALS_SIZE, buffer, - FIDO2_RESIDENT_CREDENTIALS_HEADER_LEN); -} +secbool se_fido_resident_credentials_clear(void) { + uint16_t response_len = 0; -secbool se_delete_all_fido2_credentials(void) { - if (!se_transmit_mac(SE_INS_WRITE_DATA, 0x00, 0x04, NULL, 0, NULL, NULL)) { + if (se_transmit_mac(SE_INS_FIDO, 0x00, SE_FIDO_CLEAR_RESIDENT_CREDENTIALS, + NULL, 0, NULL, &response_len) != sectrue || + response_len != 0) { return secfalse; } return sectrue; diff --git a/core/embed/trezorhal/se_thd89.h b/core/embed/trezorhal/se_thd89.h index fbb2a87c2b..90ee1781c5 100644 --- a/core/embed/trezorhal/se_thd89.h +++ b/core/embed/trezorhal/se_thd89.h @@ -44,19 +44,7 @@ typedef enum { PIN_TYPE_MAX } pin_type_t; -#define FIDO2_RESIDENT_CREDENTIALS_SIZE (512) #define FIDO2_RESIDENT_CREDENTIALS_COUNT (60) -#define FIDO2_RESIDENT_CREDENTIALS_FLAGS "\x66\x69\x64\x6F" // "fido" -#define FIDO2_RESIDENT_CREDENTIALS_HEADER_LEN (6) -typedef struct { - uint8_t credential_id_flag[4]; - uint16_t credential_length; - uint8_t rp_id_hash[32]; - uint8_t credential_id[474]; -} __attribute__((packed)) CTAP_credential_id_storage; -_Static_assert(sizeof(CTAP_credential_id_storage) == - FIDO2_RESIDENT_CREDENTIALS_SIZE, - "CTAP_credential_id_storage size must be flash page size"); typedef secbool (*UI_WAIT_CALLBACK)(uint32_t wait, uint32_t progress, const char *message); @@ -269,27 +257,25 @@ secbool se_derive_fido_keys(HDNode *out, const char *curve, uint32_t *fingerprint); secbool se_fido_hdnode_sign_digest(const uint8_t *hash, uint8_t *sig); secbool se_fido_att_sign_digest(const uint8_t *hash, uint8_t *sig); -secbool se_fido_credential_encrypt(const uint8_t rp_id_hash[32], - const uint8_t *plaintext, - uint16_t plaintext_len, - uint8_t *credential_id, - uint16_t *credential_id_len); -secbool se_fido_credential_peek(const uint8_t *credential_id, - uint16_t credential_id_len, uint8_t *plaintext, - uint16_t *plaintext_len); -secbool se_fido_credential_decrypt(const uint8_t rp_id_hash[32], - const uint8_t *credential_id, - uint16_t credential_id_len, - uint8_t *plaintext, uint16_t *plaintext_len); +secbool se_fido_credential_create(const uint8_t *plaintext, + uint16_t plaintext_len, uint8_t resident, + uint8_t *response, uint16_t *response_len); +secbool se_fido_credential_validate(const uint8_t rp_id_hash[32], + const uint8_t *credential_id, + uint16_t credential_id_len, + uint8_t *plaintext, + uint16_t *plaintext_len); secbool se_fido_hmac_secret(const uint8_t *credential_id, uint16_t credential_id_len, const uint8_t *salt, uint16_t salt_len, uint8_t *out); -secbool se_get_fido2_resident_credentials(uint32_t index, uint8_t *dest, - uint16_t *dst_len); -secbool se_set_fido2_resident_credentials(uint32_t index, const uint8_t *src, - uint16_t len); -secbool se_delete_fido2_resident_credentials(uint32_t index); -secbool se_delete_all_fido2_credentials(void); +secbool se_fido_resident_credentials_list(uint8_t *indexes, uint16_t *count); +secbool se_fido_resident_credential_read(uint8_t index, uint8_t *packed, + uint16_t *packed_len); +secbool se_fido_resident_credential_import(const uint8_t *credential_id, + uint16_t credential_id_len, + uint8_t *slot, uint8_t *action); +secbool se_fido_resident_credential_delete(uint8_t index); +secbool se_fido_resident_credentials_clear(void); secbool se_query_progress_percent(uint8_t *percent); diff --git a/core/mocks/generated/trezorcrypto/se_thd89.pyi b/core/mocks/generated/trezorcrypto/se_thd89.pyi index 78f9b972bf..41c6d078b3 100644 --- a/core/mocks/generated/trezorcrypto/se_thd89.pyi +++ b/core/mocks/generated/trezorcrypto/se_thd89.pyi @@ -329,32 +329,45 @@ def fido_att_sign_digest( # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h -def fido_credential_encrypt(rp_id_hash: bytes, plaintext: bytes) -> bytes: - """Encrypt a SLIP-0022 credential ID inside the secure element.""" +def fido_credential_create(plaintext: bytes, resident: bool) -> tuple[bytes, int, int]: + """Create a FIDO credential ID in the secure element.""" # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h -def fido_credential_peek(credential_id: bytes) -> bytes: - """Tentatively decrypt a credential for legacy RP-ID discovery.""" +def fido_credential_validate( + credential_id: bytes, rp_id_hash: bytes | None +) -> bytes: + """Authenticate a credential ID and return its CBOR plaintext.""" # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h -def fido_credential_decrypt( - rp_id_hash: bytes, credential_id: bytes -) -> bytes: - """Authenticate and decrypt a SLIP-0022 credential ID.""" +def fido_resident_credentials_list() -> tuple[int, ...]: + """Return occupied resident credential slot indexes.""" # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h -def fido_hmac_secret(credential_id: bytes, salt: bytes) -> bytes: - """Return the purpose-bound hmac-secret output for one or two salts.""" +def fido_resident_credential_read(index: int) -> tuple[bytes, bytes]: + """Read one authenticated resident credential.""" # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h -def fido_delete_all_credentials() -> None: - """ - Delete all FIDO2 credentials. - """ +def fido_resident_credential_import(credential_id: bytes) -> tuple[int, int]: + """Store an authenticated external credential ID.""" + + +# extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +def fido_resident_credential_delete(index: int) -> None: + """Delete one resident credential slot.""" + + +# extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +def fido_resident_credentials_clear() -> None: + """Clear every resident credential slot.""" + + +# extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h +def fido_hmac_secret(credential_id: bytes, salt: bytes) -> bytes: + """Return the purpose-bound hmac-secret output for one or two salts.""" # extmod/modtrezorcrypto/modtrezorcrypto-se-thd89.h diff --git a/core/src/apps/webauthn/add_resident_credential.py b/core/src/apps/webauthn/add_resident_credential.py index 9141218418..c961d38ade 100644 --- a/core/src/apps/webauthn/add_resident_credential.py +++ b/core/src/apps/webauthn/add_resident_credential.py @@ -1,5 +1,5 @@ import storage.device -from trezor import wire +from trezor import utils, wire from trezor.lvglui.i18n import gettext as _, keys as i18n_keys from trezor.messages import Success, WebAuthnAddResidentCredential from trezor.ui.components.common.webauthn import ConfirmInfo @@ -7,6 +7,7 @@ from trezor.ui.layouts.lvgl.webauthn import confirm_webauthn from .credential import Fido2Credential +from .fido_seed import ensure_fido_seed_ready from .resident_credentials import store_resident_credential @@ -35,6 +36,8 @@ async def add_resident_credential( raise wire.ProcessError("Missing credential ID parameter.") try: + if utils.USE_THD89: + ensure_fido_seed_ready() cred = Fido2Credential.from_cred_id(bytes(msg.credential_id), None) except Exception: await show_error_and_raise( diff --git a/core/src/apps/webauthn/credential.py b/core/src/apps/webauthn/credential.py index 0ae17f3ae0..42ebb48c06 100644 --- a/core/src/apps/webauthn/credential.py +++ b/core/src/apps/webauthn/credential.py @@ -164,7 +164,9 @@ def generate_id(self) -> None: plaintext = cbor.encode(data) if utils.USE_THD89: - self.id = se_thd89.fido_credential_encrypt(self.rp_id_hash, plaintext) + self.id, slot, action = se_thd89.fido_credential_create(plaintext, False) + if slot != 0xFF or action != 0: + raise AssertionError else: key = seed.derive_slip21_node_without_passphrase( [b"SLIP-0022", _CRED_ID_VERSION, b"Encryption key"] @@ -186,16 +188,7 @@ def from_cred_id( if len(cred_id) < CRED_ID_MIN_LENGTH or cred_id[0:4] != _CRED_ID_VERSION: raise ValueError # invalid length or version if utils.USE_THD89: - if rp_id_hash is None: - candidate_data = se_thd89.fido_credential_peek(cred_id) - try: - rp_id = cbor.decode(candidate_data)[_CRED_ID_RP_ID] - except Exception as e: - raise ValueError from e # CBOR decoding failed - rp_id_hash = hashlib.sha256(rp_id).digest() - del candidate_data - del rp_id - data = se_thd89.fido_credential_decrypt(rp_id_hash, cred_id) + data = se_thd89.fido_credential_validate(cred_id, rp_id_hash) else: key = seed.derive_slip21_node_without_passphrase( [b"SLIP-0022", cred_id[0:4], b"Encryption key"] @@ -220,8 +213,14 @@ def from_cred_id( if not utils.consteq(ctx.finish(), tag): raise ValueError # inauthentic ciphertext + return cls.from_authenticated_plaintext(cred_id, data, rp_id_hash) + + @classmethod + def from_authenticated_plaintext( + cls, cred_id: bytes, plaintext: bytes, rp_id_hash: bytes | None + ) -> "Fido2Credential": try: - data = cbor.decode(data) + data = cbor.decode(plaintext) except Exception as e: raise ValueError from e # CBOR decoding failed @@ -230,7 +229,6 @@ def from_cred_id( cred = cls() cred.rp_id = data.get(_CRED_ID_RP_ID, None) - cred.rp_id_hash = rp_id_hash cred.rp_name = data.get(_CRED_ID_RP_NAME, None) cred.user_id = data.get(_CRED_ID_USER_ID, None) cred.user_name = data.get(_CRED_ID_USER_NAME, None) @@ -246,10 +244,16 @@ def from_cred_id( (_CRED_ID_ALGORITHM in data) != (_CRED_ID_CURVE in data) or not cred.check_required_fields() or not cred.check_data_types() - or hashlib.sha256(cred.rp_id).digest() != rp_id_hash ): raise ValueError # data consistency check failed + calculated_rp_id_hash = hashlib.sha256(cred.rp_id).digest() + if rp_id_hash is None: + rp_id_hash = calculated_rp_id_hash + elif calculated_rp_id_hash != rp_id_hash: + raise ValueError # data consistency check failed + cred.rp_id_hash = rp_id_hash + return cred def truncate_names(self) -> None: diff --git a/core/src/apps/webauthn/fido2.py b/core/src/apps/webauthn/fido2.py index 515548f587..88271c4c8a 100644 --- a/core/src/apps/webauthn/fido2.py +++ b/core/src/apps/webauthn/fido2.py @@ -213,7 +213,9 @@ # FIDO2 configuration. _ALLOW_FIDO2 = True -_ALLOW_RESIDENT_CREDENTIALS = storage.device.get_se01_version() >= "1.1.5" +_ALLOW_RESIDENT_CREDENTIALS = storage.device.get_se01_version() >= ( + "1.3.2" if utils.USE_THD89 else "1.1.5" +) _ALLOW_WINK = False # The default value of the use_sign_count flag for newly created credentials. diff --git a/core/src/apps/webauthn/fido_seed.py b/core/src/apps/webauthn/fido_seed.py index 19f5fcb7cc..f435c472e7 100644 --- a/core/src/apps/webauthn/fido_seed.py +++ b/core/src/apps/webauthn/fido_seed.py @@ -1,18 +1,20 @@ +def ensure_fido_seed_ready() -> None: + from trezor.crypto import se_thd89 + from utime import sleep_ms + + while True: + try: + ret = se_thd89.fido_seed() + if ret: + return + sleep_ms(100) + except Exception: + raise Exception("Failed to generate seed.") + + def ensure_fido_seed(func): def wrapper(*args, **kwargs): - from trezor.crypto import se_thd89 - from utime import sleep_ms - - while True: - try: - ret = se_thd89.fido_seed() - if ret: - break - else: - sleep_ms(100) - continue - except Exception: - raise Exception("Failed to generate seed.") + ensure_fido_seed_ready() return func(*args, **kwargs) return wrapper diff --git a/core/src/apps/webauthn/resident_credentials.py b/core/src/apps/webauthn/resident_credentials.py index 116ff46f80..922c689659 100644 --- a/core/src/apps/webauthn/resident_credentials.py +++ b/core/src/apps/webauthn/resident_credentials.py @@ -4,12 +4,24 @@ import storage import storage.resident_credentials from storage.resident_credentials import MAX_RESIDENT_CREDENTIALS +from trezor import utils +from trezor.crypto import se_thd89 from .credential import Fido2Credential from .fido_seed import ensure_fido_seed RP_ID_HASH_LENGTH = const(32) -_ALLOW_RESIDENT_CREDENTIALS = storage.device.get_se01_version() >= "1.1.5" +_ALLOW_RESIDENT_CREDENTIALS = storage.device.get_se01_version() >= ( + "1.3.2" if utils.USE_THD89 else "1.1.5" +) + + +def _credential_from_se( + index: int, cred_id: bytes, plaintext: bytes +) -> Fido2Credential: + cred = Fido2Credential.from_authenticated_plaintext(cred_id, plaintext, None) + cred.index = index + return cred def _credential_from_data(index: int, data: bytes) -> Fido2Credential: @@ -24,6 +36,12 @@ def _credential_from_data(index: int, data: bytes) -> Fido2Credential: def find_all() -> Iterator[Fido2Credential]: if not _ALLOW_RESIDENT_CREDENTIALS: return + if utils.USE_THD89: + for index in se_thd89.fido_resident_credentials_list(): + cred_id, plaintext = se_thd89.fido_resident_credential_read(index) + yield _credential_from_se(index, cred_id, plaintext) + return + registered_count = storage.resident_credentials.get_fido2_counter() if registered_count == 0: return @@ -41,6 +59,14 @@ def find_all() -> Iterator[Fido2Credential]: def find_by_rp_id_hash(rp_id_hash: bytes) -> Iterator[Fido2Credential]: if not _ALLOW_RESIDENT_CREDENTIALS: return + if utils.USE_THD89: + for index in se_thd89.fido_resident_credentials_list(): + cred_id, plaintext = se_thd89.fido_resident_credential_read(index) + cred = _credential_from_se(index, cred_id, plaintext) + if cred.rp_id_hash == rp_id_hash: + yield cred + return + for index in range(MAX_RESIDENT_CREDENTIALS): data = storage.resident_credentials.get(index) @@ -62,10 +88,15 @@ def get_resident_credential(index: int) -> Fido2Credential | None: if not 0 <= index < MAX_RESIDENT_CREDENTIALS: return None + if utils.USE_THD89: + if index not in se_thd89.fido_resident_credentials_list(): + return None + cred_id, plaintext = se_thd89.fido_resident_credential_read(index) + return _credential_from_se(index, cred_id, plaintext) + data = storage.resident_credentials.get(index) if data is None: return None - return _credential_from_data(index, data) @@ -73,6 +104,16 @@ def get_resident_credential(index: int) -> Fido2Credential | None: def store_resident_credential(cred: Fido2Credential) -> bool: if not _ALLOW_RESIDENT_CREDENTIALS: return False + if utils.USE_THD89: + try: + slot, action = se_thd89.fido_resident_credential_import(cred.id) + except ValueError: + return False + if not 0 <= slot < MAX_RESIDENT_CREDENTIALS or action not in (1, 2): + return False + cred.index = slot + return True + if storage.resident_credentials.get_fido2_counter() >= MAX_RESIDENT_CREDENTIALS: return False diff --git a/core/src/storage/device.py b/core/src/storage/device.py index 94b1e646b3..29145a3e7b 100644 --- a/core/src/storage/device.py +++ b/core/src/storage/device.py @@ -864,6 +864,10 @@ def get_wp_cnts() -> int: def get_fido2_counter() -> int: global _FIDO2_COUNTER_VALUE + if utils.USE_THD89: + from trezor.crypto import se_thd89 + + return len(se_thd89.fido_resident_credentials_list()) if _FIDO2_COUNTER_VALUE is None: counter = common.get(_NAMESPACE, _FIDO2_COUNTER, public=True) if counter is None: @@ -883,6 +887,8 @@ def get_fido2_counter() -> int: def set_fido2_counter(value: int) -> None: global _FIDO2_COUNTER_VALUE + if utils.USE_THD89: + raise RuntimeError("FIDO credentials are managed by the secure element") from .resident_credentials import MAX_RESIDENT_CREDENTIALS assert ( diff --git a/core/src/storage/resident_credentials.py b/core/src/storage/resident_credentials.py index c01b98443d..7898c78aab 100644 --- a/core/src/storage/resident_credentials.py +++ b/core/src/storage/resident_credentials.py @@ -13,6 +13,8 @@ def get(index: int) -> bytes | None: if not 0 <= index < MAX_RESIDENT_CREDENTIALS: raise ValueError # invalid credential index + if utils.USE_THD89: + raise RuntimeError("FIDO credentials are managed by the secure element") return common.get(common.APP_WEBAUTHN, index + _RESIDENT_CREDENTIAL_START_KEY) @@ -20,6 +22,8 @@ def get(index: int) -> bytes | None: def set(index: int, data: bytes, is_overwritten: bool = False) -> None: if not 0 <= index < MAX_RESIDENT_CREDENTIALS: raise ValueError # invalid credential index + if utils.USE_THD89: + raise RuntimeError("FIDO credentials are managed by the secure element") common.set(common.APP_WEBAUTHN, index + _RESIDENT_CREDENTIAL_START_KEY, data) if not is_overwritten: @@ -29,23 +33,28 @@ def set(index: int, data: bytes, is_overwritten: bool = False) -> None: def delete(index: int) -> None: if not 0 <= index < MAX_RESIDENT_CREDENTIALS: raise ValueError # invalid credential index + if utils.USE_THD89: + se_thd89.fido_resident_credential_delete(index) + return common.delete(common.APP_WEBAUTHN, index + _RESIDENT_CREDENTIAL_START_KEY) _decrement_fido2_counter() def delete_all() -> None: - if device.get_fido2_counter() == 0: - return if utils.USE_THD89: - se_thd89.fido_delete_all_credentials() + se_thd89.fido_resident_credentials_clear() else: + if device.get_fido2_counter() == 0: + return for i in range(MAX_RESIDENT_CREDENTIALS): common.delete(common.APP_WEBAUTHN, i + _RESIDENT_CREDENTIAL_START_KEY) - _reset_fido2_counter() + _reset_fido2_counter() def get_fido2_counter() -> int: + if utils.USE_THD89: + return len(se_thd89.fido_resident_credentials_list()) return device.get_fido2_counter()