fix(core): lazy tropic secure session start
What changed, and why it matters
This commit restructures how the Trezor device starts a secure session with its Tropic security chip. Previously, the device tried to initialize and immediately start a secure session during boot. Now it initializes the chip early but waits until the chip reports ready, and only starts the secure session when a function actually needs it. This is a defensive hardening change that reduces the chance of session-start commands failing or being issued while the chip is still booting. It also removes a duplicate initialization call in production-test code.
Treat as a hardening fix. Review whether any callers still bypass `tropic_session_start()` and directly use `lt_session_start()`, and verify that `tropic_wait_for_ready()` timeout handling is robust. No immediate emergency action is indicated by the diff alone.
Security signals we found
Lazy secure session establishment to avoid premature session-start during boot
Addition of explicit Tropic chip readiness polling before issuing session commands
Removal of duplicate `tropic_init()` in production-test firmware update path
Centralization of session-start logic through `tropic_start_custom_session()`
State tracking for `session_started` and `chip_ready` added to driver struct
Evidence from the diff
The patch splits tropic_init() into chip initialization (tropic_init()) and secure session establishment (tropic_session_start()), and adds tropic_wait_for_ready() to poll lt_get_info_riscv_fw_ver() until the chip is no longer reporting LT_L1_CHIP_BUSY. A new helper, tropic_start_custom_session(), wraps lt_session_start() with readiness waiting and records session state. Multiple Tropic API functions (tropic_ping, tropic_ecc_key_generate, tropic_ecc_sign, tropic_data_read, tropic_random_buffer, and several PIN functions) now call tropic_session_start() lazily instead of relying solely on drv->initialized. Production-test code is updated to use the new wrapper and to remove an extra tropic_init() call.
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.