diff --git a/core/.changelog.d/6136.added b/core/.changelog.d/6136.added new file mode 100644 index 00000000000..5b512cdaf01 --- /dev/null +++ b/core/.changelog.d/6136.added @@ -0,0 +1 @@ +[T3W1] Introduce wear leveling of mac-and-destroy slots in Tropic. diff --git a/core/embed/sec/tropic/inc/sec/tropic.h b/core/embed/sec/tropic/inc/sec/tropic.h index 26da3640240..1316761319f 100644 --- a/core/embed/sec/tropic/inc/sec/tropic.h +++ b/core/embed/sec/tropic/inc/sec/tropic.h @@ -36,16 +36,6 @@ #define TROPIC_DEVICE_CERT_SLOT_COUNT 3 #define TROPIC_DEVICE_KEY_SLOT 0 // ECC_SLOT_0 -#ifdef USE_STORAGE -// KEK masks used in PIN verification -#define TROPIC_KEK_MASKS_PRIVILEGED_SLOT 128 -#define TROPIC_KEK_MASKS_UNPRIVILEGED_SLOT 256 - -// Mac-and-destroy slots used in PIN verification -#define TROPIC_FIRST_MAC_AND_DESTROY_SLOT_PRIVILEGED 0 -#define TROPIC_FIRST_MAC_AND_DESTROY_SLOT_UNPRIVILEGED 64 -#endif - // Pairing key used by prodtest to inject the privileged and unprivileged // pairing keys. #define TROPIC_FACTORY_PAIRING_KEY_SLOT 0 // PAIRING_KEY_SLOT_INDEX_0 diff --git a/core/embed/sec/tropic/tropic.c b/core/embed/sec/tropic/tropic.c index f2fdaa9bd2e..487655901f5 100644 --- a/core/embed/sec/tropic/tropic.c +++ b/core/embed/sec/tropic/tropic.c @@ -43,6 +43,29 @@ // Maximum time to wait for Tropic to boot. Chosen arbitrarily. #define TROPIC_BOOT_TIMEOUT_MS 1000 +// KEK masks used in PIN verification +#define TROPIC_KEK_MASKS_PRIVILEGED_SLOT 128 +#define TROPIC_KEK_MASKS_UNPRIVILEGED_SLOT 256 + +// Mac-and-destroy slots used in PIN verification +#define TROPIC_FIRST_MAC_AND_DESTROY_SLOT_PRIVILEGED 0 +#define TROPIC_FIRST_MAC_AND_DESTROY_SLOT_UNPRIVILEGED 64 +#define TROPIC_MAC_AND_DESTROY_SLOTS_COUNT 64 + +// The value by which the index of the first mac-and-destroy slot is shifted +// before every PIN change. The value is coprime to +// TROPIC_MAC_AND_DESTROY_SLOTS_COUNT to ensure that +// `get_mac_and_destroy_slot(i, change_pin_counter)` achieves the maximum +// possible period in `change_pin_counter`. +#define TROPIC_MAC_AND_DESTROY_SHIFT 11 + +// The slot that stores the number of times the PIN has been changed. +// Since the counter can only be decremented, its acutal value is +// TROPIC_CHANGE_COUNTER_SLOT_MAX_VALUE minus the number of PIN changes. This +// slot is used both for both privileged and the unprivileged sessions. +#define TROPIC_CHANGE_COUNTER_SLOT MCOUNTER_INDEX_4 +#define TROPIC_CHANGE_COUNTER_SLOT_MAX_VALUE 0xfffffffe + typedef struct { bool initialized; bool session_started; @@ -370,11 +393,16 @@ void tropic_random_buffer_time(uint32_t *time_ms) { // Defined in tropic01.c void tropic_set_ui_progress(tropic_ui_progress_t f); -static mac_and_destroy_slot_t get_first_mac_and_destroy_slot( - tropic_driver_t *drv) { - return drv->pairing_key_index == TROPIC_UNPRIVILEGED_PAIRING_KEY_SLOT - ? TROPIC_FIRST_MAC_AND_DESTROY_SLOT_UNPRIVILEGED - : TROPIC_FIRST_MAC_AND_DESTROY_SLOT_PRIVILEGED; +static mac_and_destroy_slot_t get_mac_and_destroy_slot( + uint16_t pin_index, uint32_t change_pin_counter) { + mac_and_destroy_slot_t first_slot_index = + g_tropic_driver.pairing_key_index == TROPIC_UNPRIVILEGED_PAIRING_KEY_SLOT + ? TROPIC_FIRST_MAC_AND_DESTROY_SLOT_UNPRIVILEGED + : TROPIC_FIRST_MAC_AND_DESTROY_SLOT_PRIVILEGED; + + return first_slot_index + + (change_pin_counter * TROPIC_MAC_AND_DESTROY_SHIFT + pin_index) % + TROPIC_MAC_AND_DESTROY_SLOTS_COUNT; } static uint16_t get_kek_masks_slot(tropic_driver_t *drv) { @@ -395,8 +423,82 @@ static void lt_r_mem_data_write_time(uint32_t *time_ms) { *time_ms += 77; } +static void lt_mcounter_get_time(uint32_t *time_ms) { *time_ms += 51; } + +static void lt_mcounter_update_time(uint32_t *time_ms) { *time_ms += 51; } + static void lt_r_mem_data_erase_time(uint32_t *time_ms) { *time_ms += 55; } +static uint32_t change_pin_counter_cached = 0; +static bool is_change_pin_counter_cached = false; + +static bool get_change_pin_counter(uint32_t *change_pin_counter) { + tropic_driver_t *drv = &g_tropic_driver; + + if (is_change_pin_counter_cached) { + *change_pin_counter = change_pin_counter_cached; + return true; + } + + lt_ret_t ret = lt_mcounter_get(&drv->handle, TROPIC_CHANGE_COUNTER_SLOT, + change_pin_counter); + if (ret == LT_OK) { + *change_pin_counter = + TROPIC_CHANGE_COUNTER_SLOT_MAX_VALUE - *change_pin_counter; + } else if (ret == LT_L3_COUNTER_INVALID) { + // The counter has not been initialized yet + *change_pin_counter = 0; + } else { + return false; + } + + change_pin_counter_cached = *change_pin_counter; + is_change_pin_counter_cached = true; + + return true; +} + +static void get_change_pin_counter_time(uint32_t *time_ms, + bool is_change_pin_counter_cached) { + if (!is_change_pin_counter_cached) { + lt_mcounter_get_time(time_ms); + } +} + +static bool update_change_pin_counter() { + tropic_driver_t *drv = &g_tropic_driver; + lt_ret_t ret = LT_FAIL; + + ret = lt_mcounter_update(&drv->handle, TROPIC_CHANGE_COUNTER_SLOT); + if (ret == LT_L3_COUNTER_INVALID) { + // The counter has not been initialized yet + ret = lt_mcounter_init(&drv->handle, TROPIC_CHANGE_COUNTER_SLOT, + TROPIC_CHANGE_COUNTER_SLOT_MAX_VALUE - 1); + if (ret != LT_OK) { + return false; + } + change_pin_counter_cached = 1; + is_change_pin_counter_cached = true; + return true; + } + + if (ret != LT_OK) { + return false; + } + + if (is_change_pin_counter_cached) { + change_pin_counter_cached++; + } + + return true; +} + +static void update_change_pin_counter_time(uint32_t *time_ms) { + lt_mcounter_update_time(time_ms); + // Ignore the time of `lt_mcounter_init()` since we cannot easily determine + // whether it will be executed. +} + bool tropic_pin_stretch(tropic_ui_progress_t ui_progress, uint16_t pin_index, uint8_t stretched_pin[TROPIC_MAC_AND_DESTROY_SIZE]) { if (pin_index >= PIN_MAX_TRIES) { @@ -412,14 +514,18 @@ bool tropic_pin_stretch(tropic_ui_progress_t ui_progress, uint16_t pin_index, goto cleanup; } - mac_and_destroy_slot_t first_slot_index = get_first_mac_and_destroy_slot(drv); - uint8_t digest[TROPIC_MAC_AND_DESTROY_SIZE] = {0}; hmac_sha256(stretched_pin, TROPIC_MAC_AND_DESTROY_SIZE, NULL, 0, digest); - if (lt_mac_and_destroy(&drv->handle, first_slot_index + pin_index, digest, - digest) != LT_OK) { + uint32_t change_pin_counter = 0; + if (!get_change_pin_counter(&change_pin_counter)) { + goto cleanup; + } + + if (lt_mac_and_destroy( + &drv->handle, get_mac_and_destroy_slot(pin_index, change_pin_counter), + digest, digest) != LT_OK) { goto cleanup; } @@ -435,6 +541,7 @@ bool tropic_pin_stretch(tropic_ui_progress_t ui_progress, uint16_t pin_index, } void tropic_pin_stretch_time(uint32_t *time_ms) { + get_change_pin_counter_time(time_ms, is_change_pin_counter_cached); lt_mac_and_destroy_time(time_ms); } @@ -456,11 +563,15 @@ bool tropic_pin_reset_slots( uint8_t output[TROPIC_MAC_AND_DESTROY_SIZE] = {0}; - mac_and_destroy_slot_t first_slot_index = get_first_mac_and_destroy_slot(drv); + uint32_t change_pin_counter = 0; + if (!get_change_pin_counter(&change_pin_counter)) { + goto cleanup; + } for (int i = 0; i <= pin_index; i++) { - if (lt_mac_and_destroy(&drv->handle, first_slot_index + i, reset_key, - output) != LT_OK) { + if (lt_mac_and_destroy(&drv->handle, + get_mac_and_destroy_slot(i, change_pin_counter), + reset_key, output) != LT_OK) { goto cleanup; } } @@ -475,6 +586,10 @@ bool tropic_pin_reset_slots( } void tropic_pin_reset_slots_time(uint32_t *time_ms, uint16_t pin_index) { + // When get_change_pin_counter() is called in tropic_pin_reset_slots(), the + // change pin counter will have already been cached by + // get_change_pin_counter_time() in tropic_pin_stretch() + get_change_pin_counter_time(time_ms, true); for (int i = 0; i <= pin_index; i++) { lt_mac_and_destroy_time(time_ms); } @@ -497,30 +612,38 @@ bool tropic_pin_set( goto cleanup; } + if (!update_change_pin_counter()) { + goto cleanup; + } + uint32_t change_pin_counter = 0; + if (!get_change_pin_counter(&change_pin_counter)) { + goto cleanup; + } + uint8_t output[TROPIC_MAC_AND_DESTROY_SIZE] = {0}; uint8_t digest[TROPIC_MAC_AND_DESTROY_SIZE] = {0}; - mac_and_destroy_slot_t first_slot_index = get_first_mac_and_destroy_slot(drv); - for (int i = 0; i < PIN_MAX_TRIES; i++) { - if (lt_mac_and_destroy(&drv->handle, first_slot_index + i, reset_key, - output) != LT_OK) { + mac_and_destroy_slot_t slot_index = + get_mac_and_destroy_slot(i, change_pin_counter); + + if (lt_mac_and_destroy(&drv->handle, slot_index, reset_key, output) != + LT_OK) { goto cleanup; } hmac_sha256(stretched_pins[i], TROPIC_MAC_AND_DESTROY_SIZE, NULL, 0, digest); - if (lt_mac_and_destroy(&drv->handle, first_slot_index + i, digest, - digest) != LT_OK) { + if (lt_mac_and_destroy(&drv->handle, slot_index, digest, digest) != LT_OK) { goto cleanup; } hmac_sha256(stretched_pins[i], TROPIC_MAC_AND_DESTROY_SIZE, digest, sizeof(digest), stretched_pins[i]); - if (lt_mac_and_destroy(&drv->handle, first_slot_index + i, reset_key, - output) != LT_OK) { + if (lt_mac_and_destroy(&drv->handle, slot_index, reset_key, output) != + LT_OK) { goto cleanup; } } @@ -537,6 +660,11 @@ bool tropic_pin_set( void tropic_pin_set_time(uint32_t *time_ms) { rng_fill_buffer_strong_time(time_ms); + update_change_pin_counter_time(time_ms); + // When get_change_pin_counter() is called in tropic_pin_set(), the + // change pin counter will have already been cached by + // update_change_pin_counter() in tropic_pin_set() + get_change_pin_counter_time(time_ms, true); for (int i = 0; i < PIN_MAX_TRIES; i++) { lt_mac_and_destroy_time(time_ms); lt_mac_and_destroy_time(time_ms);