refator(core): refactor tropic certificate cache
What changed, and why it matters
This commit is a code cleanup: it moves the Tropic chip certificate cache out of the production-test command file and into the shared Tropic driver, then exposes two helper functions so other code can fetch the cached public key and certificate chain. There is no visible security bug being fixed or introduced in the diff itself.
No security action required. Treat as a normal maintainability refactor. If reviewing for defense in depth, verify that the new `tropic_get_pubkey()` and `tropic_get_cert_chain_ptr()` helpers are only callable under the intended non-production/prodtest build configurations, which the `#if !PRODUCTION || defined(TREZOR_PRODTEST)` guard appears to ensure.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors certificate handling for the Tropic secure element. Previously, prodtest_tropic.c kept its own static certificate buffer, cached the certificate chain, and extracted the public key. This commit removes that local implementation and adds tropic_get_pubkey() and tropic_get_cert_chain_ptr() in sec/tropic/tropic.c, guarded by #if !PRODUCTION || defined(TREZOR_PRODTEST). The production-test code now calls those helpers. The same caching logic is reused, with one functional difference: the public key is now cached once and copied on request, rather than being re-extracted from a temporary buffer each time. No bounds checks were added or removed, and no new memory allocations or external inputs appear.
Changed components
core/embed/projects/prodtest/cmd/prodtest_tropic.ccore/embed/sec/tropic/inc/sec/tropic.hcore/embed/sec/tropic/tropic.cInspect captured patch +82 / −97
diff --git a/core/embed/projects/prodtest/cmd/prodtest_tropic.c b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
index 2fd0b16c..bea75d51 100644
--- a/core/embed/projects/prodtest/cmd/prodtest_tropic.c
+++ b/core/embed/projects/prodtest/cmd/prodtest_tropic.c
@@ -54,11 +54,6 @@ typedef enum {
static tropic_handshake_state_t tropic_handshake_state =
TROPIC_HANDSHAKE_STATE_0;
-static uint8_t tropic_cert_chain[LT_NUM_CERTIFICATES *
- LT_L2_GET_INFO_REQ_CERT_SIZE_SINGLE] = {0};
-static size_t tropic_cert_chain_length = 0;
-static curve25519_key tropic_public_cached = {0};
-
// TODO: Update this link to correspond with the latest chip revision when it
// becomes available.
// https://github.com/tropicsquare/tropic01/blob/da459d18db7aea107419035b9cdf316d89a73445/doc/api/tropic01_user_api_v1.1.2.pdf
@@ -540,60 +535,17 @@ static void prodtest_tropic_get_chip_id(cli_t* cli) {
cli_ok_hexdata(cli, &chip_id, sizeof(chip_id));
}
-static bool cache_tropic_cert_chain(void) {
- if (tropic_cert_chain_length > 0) {
- return true;
- }
-
- struct lt_cert_store_t cert_store = {0};
- for (size_t i = 0; i < LT_NUM_CERTIFICATES; i++) {
- cert_store.certs[i] =
- &tropic_cert_chain[i * LT_L2_GET_INFO_REQ_CERT_SIZE_SINGLE];
- cert_store.buf_len[i] = LT_L2_GET_INFO_REQ_CERT_SIZE_SINGLE;
- }
-
- lt_ret_t ret = LT_FAIL;
-
- ret = lt_get_info_cert_store(tropic_get_handle(), &cert_store);
- if (ret != LT_OK) {
- return false;
- }
-
- ret = lt_get_st_pub(&cert_store, tropic_public_cached,
- sizeof(tropic_public_cached));
- if (ret != LT_OK) {
- return false;
- }
-
- // Compactify tropic_cert_chain for future use. This invalidates the
- // cert_store.
- size_t length = 0;
- for (size_t i = 0; i < LT_NUM_CERTIFICATES; i++) {
- memmove(&tropic_cert_chain[length], cert_store.certs[i],
- cert_store.cert_len[i]);
- length += cert_store.cert_len[i];
- }
-
- tropic_cert_chain_length = length;
-
- return true;
-}
-
-static const curve25519_key* prodtest_tropic_get_tropic_public(void) {
- if (!cache_tropic_cert_chain()) {
- return NULL;
- }
- return &tropic_public_cached;
-}
-
static void prodtest_tropic_certtropic_read(cli_t* cli) {
if (cli_arg_count(cli) > 0) {
cli_error_arg_count(cli);
return;
}
- if (!cache_tropic_cert_chain()) {
- cli_error(cli, CLI_ERROR, "`cache_tropic_cert_chain()` failed");
+ const uint8_t* tropic_cert_chain = NULL;
+ size_t tropic_cert_chain_length = 0;
+ if (!tropic_get_cert_chain_ptr(&tropic_cert_chain,
+ &tropic_cert_chain_length)) {
+ cli_error(cli, CLI_ERROR, "`tropic_get_cert_chain_ptr()` failed");
return;
}
@@ -787,10 +739,9 @@ static void prodtest_tropic_pair(cli_t* cli) {
lt_handle_t* tropic_handle = tropic_get_handle();
// Get the Tropic01 public pairing key from the chip's certificate.
- const curve25519_key* tropic_public_cert =
- prodtest_tropic_get_tropic_public();
- if (tropic_public_cert == NULL) {
- cli_error(cli, CLI_ERROR, "`prodtest_tropic_get_tropic_public()` failed");
+ curve25519_key tropic_public = {0};
+ if (!tropic_get_pubkey(tropic_public)) {
+ cli_error(cli, CLI_ERROR, "`tropic_get_tropic_pubkey()` failed");
goto cleanup;
}
@@ -800,7 +751,7 @@ static void prodtest_tropic_pair(cli_t* cli) {
if (secret_key_tropic_public(tropic_public_flash) != sectrue) {
#ifdef SECRET_TROPIC_TROPIC_PUBKEY_SLOT
// This is skipped in the prodtest emulator.
- if (secret_key_set(SECRET_TROPIC_TROPIC_PUBKEY_SLOT, *tropic_public_cert,
+ if (secret_key_set(SECRET_TROPIC_TROPIC_PUBKEY_SLOT, tropic_public,
sizeof(curve25519_key)) != sectrue) {
cli_error(cli, CLI_ERROR,
"`secret_key_set()` failed for tropic public key.");
@@ -812,8 +763,7 @@ static void prodtest_tropic_pair(cli_t* cli) {
goto cleanup;
}
}
- if (memcmp(*tropic_public_cert, tropic_public_flash,
- sizeof(curve25519_key)) != 0) {
+ if (memcmp(tropic_public, tropic_public_flash, sizeof(curve25519_key)) != 0) {
cli_error(cli, CLI_ERROR,
"Tropic public key does not match the expected value.");
goto cleanup;
@@ -908,15 +858,14 @@ static void prodtest_tropic_get_access_credential(cli_t* cli) {
goto cleanup;
}
- const curve25519_key* tropic_public = prodtest_tropic_get_tropic_public();
- if (tropic_public == NULL) {
- cli_error(cli, CLI_ERROR, "`prodtest_tropic_get_tropic_public()` failed");
- goto cleanup;
+ curve25519_key tropic_public = {0};
+ if (!tropic_get_pubkey(tropic_public)) {
+ cli_error(cli, CLI_ERROR, "`tropic_get_tropic_pubkey()` failed");
}
uint8_t output[sizeof(unprivileged_private) + NOISE_TAG_SIZE] = {0};
if (!secure_channel_encrypt((uint8_t*)unprivileged_private,
- sizeof(unprivileged_private), *tropic_public,
+ sizeof(unprivileged_private), tropic_public,
sizeof(curve25519_key), output)) {
// `secure_channel_handshake_2()` might not have been called
cli_error(cli, CLI_ERROR, "`secure_channel_encrypt()` failed.");
diff --git a/core/embed/sec/tropic/inc/sec/tropic.h b/core/embed/sec/tropic/inc/sec/tropic.h
index 6dcb2ee5..21c8211d 100644
--- a/core/embed/sec/tropic/inc/sec/tropic.h
+++ b/core/embed/sec/tropic/inc/sec/tropic.h
@@ -78,6 +78,10 @@ lt_ret_t tropic_custom_session_start(pkey_index_t pairing_key_index);
lt_ret_t tropic_session_invalidate(void);
bool tropic_wait_for_ready(void);
+
+bool tropic_get_pubkey(curve25519_key pubkey);
+
+bool tropic_get_cert_chain_ptr(uint8_t const** cert_chain, size_t* length);
#endif
#endif
diff --git a/core/embed/sec/tropic/tropic.c b/core/embed/sec/tropic/tropic.c
index dc37ed8c..bc484bf6 100644
--- a/core/embed/sec/tropic/tropic.c
+++ b/core/embed/sec/tropic/tropic.c
@@ -57,10 +57,69 @@ typedef struct {
static tropic_driver_t g_tropic_driver = {0};
-#if !PRODUCTION
-static bool tropic_get_tropic_pubkey(lt_handle_t *handle,
- curve25519_key pubkey);
-#endif
+#if !PRODUCTION || defined(TREZOR_PRODTEST)
+static uint8_t tropic_cert_chain[LT_NUM_CERTIFICATES *
+ LT_L2_GET_INFO_REQ_CERT_SIZE_SINGLE] = {0};
+static size_t tropic_cert_chain_length = 0;
+static curve25519_key tropic_public_cached = {0};
+
+static bool cache_tropic_cert_chain(void) {
+ if (tropic_cert_chain_length > 0) {
+ return true;
+ }
+
+ struct lt_cert_store_t cert_store = {0};
+ for (size_t i = 0; i < LT_NUM_CERTIFICATES; i++) {
+ cert_store.certs[i] =
+ &tropic_cert_chain[i * LT_L2_GET_INFO_REQ_CERT_SIZE_SINGLE];
+ cert_store.buf_len[i] = LT_L2_GET_INFO_REQ_CERT_SIZE_SINGLE;
+ }
+
+ lt_ret_t ret = LT_FAIL;
+
+ ret = lt_get_info_cert_store(&g_tropic_driver.handle, &cert_store);
+ if (ret != LT_OK) {
+ return false;
+ }
+
+ ret = lt_get_st_pub(&cert_store, tropic_public_cached,
+ sizeof(tropic_public_cached));
+ if (ret != LT_OK) {
+ return false;
+ }
+
+ // Compactify tropic_cert_chain for future use. This invalidates the
+ // cert_store.
+ size_t length = 0;
+ for (size_t i = 0; i < LT_NUM_CERTIFICATES; i++) {
+ memmove(&tropic_cert_chain[length], cert_store.certs[i],
+ cert_store.cert_len[i]);
+ length += cert_store.cert_len[i];
+ }
+
+ tropic_cert_chain_length = length;
+
+ return true;
+}
+
+bool tropic_get_pubkey(curve25519_key pubkey) {
+ if (!cache_tropic_cert_chain()) {
+ return false;
+ }
+ memcpy(pubkey, tropic_public_cached, sizeof(curve25519_key));
+ return true;
+}
+
+bool tropic_get_cert_chain_ptr(uint8_t const **cert_chain,
+ size_t *cert_chain_length) {
+ if (!cache_tropic_cert_chain()) {
+ return false;
+ }
+ *cert_chain = tropic_cert_chain;
+ *cert_chain_length = tropic_cert_chain_length;
+ return true;
+}
+#endif // !PRODUCTION || defined(TREZOR_PRODTEST)
bool tropic_wait_for_ready(void) {
tropic_driver_t *drv = &g_tropic_driver;
@@ -134,7 +193,7 @@ lt_ret_t tropic_custom_session_start(pkey_index_t pairing_key_index) {
if (secret_key_tropic_public(tropic_public) != sectrue) {
#if !PRODUCTION || defined(TREZOR_PRODTEST)
if (pairing_key_index != TROPIC_FACTORY_PAIRING_KEY_SLOT ||
- !tropic_get_tropic_pubkey(&drv->handle, tropic_public))
+ !tropic_get_pubkey(tropic_public))
#endif
{
goto cleanup;
@@ -295,33 +354,6 @@ bool tropic_data_read(uint16_t udata_slot, uint8_t *data, uint16_t *size) {
return res == LT_OK;
}
-#if !PRODUCTION
-static bool tropic_get_tropic_pubkey(lt_handle_t *handle,
- curve25519_key pubkey) {
- uint8_t buffer[LT_NUM_CERTIFICATES * LT_L2_GET_INFO_REQ_CERT_SIZE_SINGLE];
-
- struct lt_cert_store_t cert_store = {0};
- for (size_t i = 0; i < LT_NUM_CERTIFICATES; i++) {
- cert_store.certs[i] = &buffer[i * LT_L2_GET_INFO_REQ_CERT_SIZE_SINGLE];
- cert_store.buf_len[i] = LT_L2_GET_INFO_REQ_CERT_SIZE_SINGLE;
- }
-
- lt_ret_t ret = LT_FAIL;
-
- ret = lt_get_info_cert_store(handle, &cert_store);
- if (ret != LT_OK) {
- return false;
- }
-
- ret = lt_get_st_pub(&cert_store, pubkey, sizeof(curve25519_key));
- if (ret != LT_OK) {
- return false;
- }
-
- return true;
-}
-#endif // !PRODUCTION
-
void tropic_get_factory_privkey(curve25519_key privkey) {
#ifdef TREZOR_EMULATOR
curve25519_key factory_private = {
Why this scored 15/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.