fix(core): lazy tropic secure session start
What changed, and why it matters
This commit restructures how the Trezor firmware initializes and starts secure sessions with the Tropic secure chip. Previously, session startup happened eagerly during device initialization. Now it is deferred until the first actual use of the Tropic chip, and a separate wait-for-ready step is added. The change is framed as a fix for a lazy session-start issue. It is not obviously exploitable by itself, but it touches security-critical pairing/session logic and removes a duplicate tropic_init() call in production-test code, which could matter if the duplicate call had side effects.
Treat as a hardening/refactoring change in a security-critical subsystem. Review the new tropic_session_start() and tropic_start_custom_session() paths for race conditions or state inconsistencies, especially around repeated calls, deinit/reinit, and failure handling. Verify that lazy session start does not weaken pairing-key validation or allow unintended fallback pairing slots. No immediate user action is indicated absent a vendor security advisory.
Security signals we found
Refactor of secure-element session establishment
Removal of duplicate tropic_init() in production-test firmware update path
Addition of chip-ready polling before session commands
Change from eager to lazy session start for Tropic secure chip
New state tracking for session_started and chip_ready
Evidence from the diff
The patch splits tropic_init() into initialization (lt_init) and session startup (tropic_session_start()), and introduces tropic_wait_for_ready() to poll lt_get_info_riscv_fw_ver() until the chip is no longer LT_L1_CHIP_BUSY. A new helper tropic_start_custom_session() wraps lt_session_start() and records pairing_key_index and session_started state. Multiple Tropic operations now call tropic_session_start() instead of checking only drv->initialized. In prodtest, direct lt_session_start() calls are replaced with tropic_start_custom_session(), and one redundant tropic_init() in prodtest_tropic_update_fw is removed. prodtest main.c now calls tropic_init() followed by tropic_wait_for_ready().
Changed components
core/embed/sec/tropic/tropic.ccore/embed/sec/tropic/inc/sec/tropic.hcore/embed/projects/prodtest/cmd/prodtest_tropic.ccore/embed/projects/prodtest/main.cInspect captured patch +142 / −70
diff --git a/core/embed/projects/prodtest/cmd/prodtest_tropic.c b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
index 4764e250a..8a747adcb 100644
--- a/core/embed/projects/prodtest/cmd/prodtest_tropic.c
+++ b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
@@ -650,9 +650,9 @@ tropic_locked_status get_tropic_locked_status(cli_t* cli) {
curve25519_key privileged_public = {0};
curve25519_scalarmult_basepoint(privileged_public, privileged_private);
- ret = lt_session_start(tropic_handle, tropic_public,
- TROPIC_PRIVILEGED_PAIRING_KEY_SLOT, privileged_private,
- privileged_public);
+ ret = tropic_start_custom_session(tropic_public,
+ TROPIC_PRIVILEGED_PAIRING_KEY_SLOT,
+ privileged_private, privileged_public);
if (ret != LT_OK) {
// The Tropic pairing process was initiated but probably failed midway.
locked_status = TROPIC_LOCKED_FALSE;
@@ -747,15 +747,15 @@ static bool tropic_is_paired(cli_t* cli) {
}
curve25519_key unprivileged_public = {0};
curve25519_scalarmult_basepoint(unprivileged_public, unprivileged_private);
- ret = lt_session_start(tropic_handle, tropic_public,
- TROPIC_UNPRIVILEGED_PAIRING_KEY_SLOT,
- unprivileged_private, unprivileged_public);
+ ret = tropic_start_custom_session(tropic_public,
+ TROPIC_UNPRIVILEGED_PAIRING_KEY_SLOT,
+ unprivileged_private, unprivileged_public);
if (ret != LT_OK) {
if (cli != NULL) {
- cli_error(
- cli, CLI_ERROR,
- "`lt_session_start()` for unprivileged key failed with error %d",
- ret);
+ cli_error(cli, CLI_ERROR,
+ "`tropic_lt_session_start()` for unprivileged key failed with "
+ "error %d",
+ ret);
}
goto cleanup;
}
@@ -772,14 +772,15 @@ static bool tropic_is_paired(cli_t* cli) {
curve25519_key privileged_public = {0};
curve25519_scalarmult_basepoint(privileged_public, privileged_private);
- ret = lt_session_start(tropic_handle, tropic_public,
- TROPIC_PRIVILEGED_PAIRING_KEY_SLOT, privileged_private,
- privileged_public);
+ ret = tropic_start_custom_session(tropic_public,
+ TROPIC_PRIVILEGED_PAIRING_KEY_SLOT,
+ privileged_private, privileged_public);
if (ret != LT_OK) {
if (cli != NULL) {
- cli_error(cli, CLI_ERROR,
- "`lt_session_start()` for privileged key failed with error %d",
- ret);
+ cli_error(
+ cli, CLI_ERROR,
+ "`tropic_lt_session_start()` for privileged key failed with error %d",
+ ret);
}
goto cleanup;
}
@@ -833,9 +834,9 @@ bool prodtest_tropic_factory_session_start(lt_handle_t* tropic_handle) {
}
// Try to establish a session using the factory pairing key.
- return LT_OK == lt_session_start(tropic_handle, *tropic_public,
- TROPIC_FACTORY_PAIRING_KEY_SLOT,
- factory_private, factory_public);
+ return LT_OK == tropic_start_custom_session(*tropic_public,
+ TROPIC_FACTORY_PAIRING_KEY_SLOT,
+ factory_private, factory_public);
}
static void prodtest_tropic_pair(cli_t* cli) {
@@ -1272,13 +1273,14 @@ static void prodtest_tropic_lock(cli_t* cli) {
curve25519_key privileged_public = {0};
curve25519_scalarmult_basepoint(privileged_public, privileged_private);
- ret = lt_session_start(tropic_handle, tropic_public,
- TROPIC_PRIVILEGED_PAIRING_KEY_SLOT, privileged_private,
- privileged_public);
+ ret = tropic_start_custom_session(tropic_public,
+ TROPIC_PRIVILEGED_PAIRING_KEY_SLOT,
+ privileged_private, privileged_public);
if (ret != LT_OK) {
- cli_error(cli, CLI_ERROR,
- "`lt_session_start()` for privileged key failed with error %d",
- ret);
+ cli_error(
+ cli, CLI_ERROR,
+ "`tropic_lt_session_start()` for privileged key failed with error %d",
+ ret);
goto cleanup;
}
@@ -1492,13 +1494,14 @@ static void cert_write(cli_t* cli, uint16_t first_slot, uint16_t slots_count) {
curve25519_key privileged_public = {0};
curve25519_scalarmult_basepoint(privileged_public, privileged_private);
- ret = lt_session_start(tropic_handle, tropic_public,
- TROPIC_PRIVILEGED_PAIRING_KEY_SLOT, privileged_private,
- privileged_public);
+ ret = tropic_start_custom_session(tropic_public,
+ TROPIC_PRIVILEGED_PAIRING_KEY_SLOT,
+ privileged_private, privileged_public);
if (ret != LT_OK) {
- cli_error(cli, CLI_ERROR,
- "`lt_session_start()` for privileged key failed with error %d",
- ret);
+ cli_error(
+ cli, CLI_ERROR,
+ "`tropic_lt_session_start()` for privileged key failed with error %d",
+ ret);
goto cleanup;
}
@@ -1560,13 +1563,14 @@ static void cert_read(cli_t* cli, uint16_t first_slot, uint16_t slots_count) {
curve25519_key privileged_public = {0};
curve25519_scalarmult_basepoint(privileged_public, privileged_private);
- ret = lt_session_start(tropic_handle, tropic_public,
- TROPIC_PRIVILEGED_PAIRING_KEY_SLOT, privileged_private,
- privileged_public);
+ ret = tropic_start_custom_session(tropic_public,
+ TROPIC_PRIVILEGED_PAIRING_KEY_SLOT,
+ privileged_private, privileged_public);
if (ret != LT_OK) {
- cli_error(cli, CLI_ERROR,
- "`lt_session_start()` for privileged key failed with error %d",
- ret);
+ cli_error(
+ cli, CLI_ERROR,
+ "`tropic_lt_session_start()` for privileged key failed with error %d",
+ ret);
goto cleanup;
}
@@ -1651,8 +1655,6 @@ static void prodtest_tropic_update_fw(cli_t* cli) {
return;
}
- tropic_init();
-
lt_handle_t* h = tropic_get_handle();
lt_chip_id_t chip_id = {0};
diff --git a/core/embed/projects/prodtest/main.c b/core/embed/projects/prodtest/main.c
index c89cd930c..9455b5491 100644
--- a/core/embed/projects/prodtest/main.c
+++ b/core/embed/projects/prodtest/main.c
@@ -192,6 +192,7 @@ static void drivers_init(void) {
#endif
#ifdef USE_TROPIC
tropic_init();
+ tropic_wait_for_ready();
#endif
#ifdef USE_HW_REVISION
hw_revision_init();
diff --git a/core/embed/sec/tropic/inc/sec/tropic.h b/core/embed/sec/tropic/inc/sec/tropic.h
index 26e5876ca..5d1f0d020 100644
--- a/core/embed/sec/tropic/inc/sec/tropic.h
+++ b/core/embed/sec/tropic/inc/sec/tropic.h
@@ -68,6 +68,13 @@ void tropic_deinit(void);
#ifdef TREZOR_PRODTEST
#include "libtropic.h"
lt_handle_t* tropic_get_handle(void);
+
+lt_ret_t tropic_start_custom_session(const uint8_t* stpub,
+ const pkey_index_t pkey_index,
+ const uint8_t* shipriv,
+ const uint8_t* shipub);
+
+bool tropic_wait_for_ready(void);
#endif
#endif
diff --git a/core/embed/sec/tropic/tropic.c b/core/embed/sec/tropic/tropic.c
index b848e8bba..6fb0585e2 100644
--- a/core/embed/sec/tropic/tropic.c
+++ b/core/embed/sec/tropic/tropic.c
@@ -45,6 +45,8 @@
typedef struct {
bool initialized;
+ bool session_started;
+ bool chip_ready;
pkey_index_t pairing_key_index;
lt_handle_t handle;
#ifdef TREZOR_EMULATOR
@@ -59,6 +61,51 @@ static bool tropic_get_tropic_pubkey(lt_handle_t *handle,
curve25519_key pubkey);
#endif
+bool tropic_wait_for_ready(void) {
+ tropic_driver_t *drv = &g_tropic_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (drv->chip_ready) {
+ return true;
+ }
+
+ // Wait for Tropic to boot before issuing any session commands.
+ uint32_t boot_start_ms = hal_ticks_ms();
+ while (hal_ticks_ms() - boot_start_ms < TROPIC_BOOT_TIMEOUT_MS) {
+ uint8_t ver[LT_L2_GET_INFO_RISCV_FW_SIZE] = {0};
+ if (lt_get_info_riscv_fw_ver(&drv->handle, ver) != LT_L1_CHIP_BUSY) {
+ drv->chip_ready = true;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+lt_ret_t tropic_start_custom_session(const uint8_t *stpub,
+ const pkey_index_t pkey_index,
+ const uint8_t *shipriv,
+ const uint8_t *shipub) {
+ tropic_driver_t *drv = &g_tropic_driver;
+
+ if (!drv->initialized) {
+ return LT_FAIL;
+ }
+
+ tropic_wait_for_ready();
+
+ lt_ret_t ret =
+ lt_session_start(&drv->handle, stpub, pkey_index, shipriv, shipub);
+
+ drv->pairing_key_index = pkey_index;
+ drv->session_started = (ret == LT_OK);
+
+ return ret;
+}
+
static bool session_start(tropic_driver_t *drv,
pkey_index_t pairing_key_index) {
bool ret = false;
@@ -95,12 +142,11 @@ static bool session_start(tropic_driver_t *drv,
}
}
- if (lt_session_start(&drv->handle, tropic_public, pairing_key_index,
- trezor_private, trezor_public) != LT_OK) {
+ if (tropic_start_custom_session(tropic_public, pairing_key_index,
+ trezor_private, trezor_public) != LT_OK) {
goto cleanup;
}
- drv->pairing_key_index = pairing_key_index;
ret = true;
cleanup:
@@ -109,33 +155,19 @@ cleanup:
return ret;
}
-bool tropic_init(void) {
+bool tropic_session_start(void) {
tropic_driver_t *drv = &g_tropic_driver;
- if (drv->initialized) {
- return true;
- }
-
-#ifdef TREZOR_EMULATOR
- drv->device.addr = inet_addr("127.0.0.1");
- drv->device.port = 28992;
- drv->handle.l2.device = &drv->device;
-#endif
-
- if (lt_init(&drv->handle) != LT_OK) {
+ if (!drv->initialized) {
return false;
}
- drv->initialized = true;
- // Wait for Tropic to boot before issuing any session commands.
- uint32_t boot_start_ms = hal_ticks_ms();
- while (hal_ticks_ms() - boot_start_ms < TROPIC_BOOT_TIMEOUT_MS) {
- uint8_t ver[LT_L2_GET_INFO_RISCV_FW_SIZE] = {0};
- if (lt_get_info_riscv_fw_ver(&drv->handle, ver) != LT_L1_CHIP_BUSY) {
- break;
- }
+ if (drv->session_started) {
+ return true;
}
+ tropic_wait_for_ready();
+
#ifndef TREZOR_EMULATOR
if (session_start(drv, TROPIC_PRIVILEGED_PAIRING_KEY_SLOT)) {
return true;
@@ -153,6 +185,27 @@ bool tropic_init(void) {
return false;
}
+bool tropic_init(void) {
+ tropic_driver_t *drv = &g_tropic_driver;
+
+ if (drv->initialized) {
+ return true;
+ }
+
+#ifdef TREZOR_EMULATOR
+ drv->device.addr = inet_addr("127.0.0.1");
+ drv->device.port = 28992;
+ drv->handle.l2.device = &drv->device;
+#endif
+
+ if (lt_init(&drv->handle) != LT_OK) {
+ return false;
+ }
+ drv->initialized = true;
+
+ return true;
+}
+
void tropic_deinit(void) {
tropic_driver_t *drv = &g_tropic_driver;
lt_deinit(&drv->handle);
@@ -172,7 +225,7 @@ lt_handle_t *tropic_get_handle(void) {
bool tropic_ping(const uint8_t *msg_out, uint8_t *msg_in, uint16_t msg_len) {
tropic_driver_t *drv = &g_tropic_driver;
- if (!drv->initialized) {
+ if (!tropic_session_start()) {
return false;
}
@@ -183,7 +236,7 @@ bool tropic_ping(const uint8_t *msg_out, uint8_t *msg_in, uint16_t msg_len) {
bool tropic_ecc_key_generate(uint16_t slot_index) {
tropic_driver_t *drv = &g_tropic_driver;
- if (!drv->initialized) {
+ if (!tropic_session_start()) {
return false;
}
@@ -199,7 +252,7 @@ bool tropic_ecc_sign(uint16_t key_slot_index, const uint8_t *dig,
uint16_t dig_len, uint8_t *sig) {
tropic_driver_t *drv = &g_tropic_driver;
- if (!drv->initialized) {
+ if (!tropic_session_start()) {
return false;
}
@@ -220,7 +273,7 @@ bool tropic_ecc_sign(uint16_t key_slot_index, const uint8_t *dig,
bool tropic_data_read(uint16_t udata_slot, uint8_t *data, uint16_t *size) {
tropic_driver_t *drv = &g_tropic_driver;
- if (!drv->initialized) {
+ if (!tropic_session_start()) {
return false;
}
@@ -286,7 +339,7 @@ void tropic_get_factory_privkey(curve25519_key privkey) {
bool tropic_random_buffer(void *buffer, size_t length) {
tropic_driver_t *drv = &g_tropic_driver;
- if (!drv->initialized) {
+ if (!tropic_session_start()) {
return false;
}
@@ -322,7 +375,7 @@ bool tropic_pin_stretch(tropic_ui_progress_t ui_progress, uint16_t pin_index,
tropic_driver_t *drv = &g_tropic_driver;
- if (!drv->initialized) {
+ if (!tropic_session_start()) {
return false;
}
@@ -357,7 +410,7 @@ bool tropic_pin_reset_slots(
tropic_driver_t *drv = &g_tropic_driver;
- if (!drv->initialized) {
+ if (!tropic_session_start()) {
return false;
}
@@ -392,7 +445,7 @@ bool tropic_pin_set(
tropic_driver_t *drv = &g_tropic_driver;
- if (!drv->initialized) {
+ if (!tropic_session_start()) {
return false;
}
@@ -454,6 +507,11 @@ bool tropic_pin_set_kek_masks(
// Time: 130 ms
tropic_driver_t *drv = &g_tropic_driver;
+
+ if (!tropic_session_start()) {
+ return false;
+ }
+
lt_ret_t ret = LT_FAIL;
uint8_t masks[PIN_MAX_TRIES * TROPIC_MAC_AND_DESTROY_SIZE] = {0};
@@ -497,6 +555,10 @@ bool tropic_pin_unmask_kek(
tropic_driver_t *drv = &g_tropic_driver;
+ if (!tropic_session_start()) {
+ return false;
+ }
+
uint8_t masks[R_MEM_DATA_SIZE_MAX] = {0};
_Static_assert(
R_MEM_DATA_SIZE_MAX >= PIN_MAX_TRIES * TROPIC_MAC_AND_DESTROY_SIZE,
Why this scored 41/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.