What changed, and why it matters
This commit adds a new feature to read pre-stored digital certificates from the Tropic secure chip inside Trezor hardware wallets. It does not change how keys are generated or signed, and it does not appear to fix a known bug or vulnerability. The new code is mostly about safely reading fixed-size data slots from the chip and exposing them to Python applications.
Treat as a routine feature commit rather than a security patch. If reviewing for release, verify that R_MEM_DATA_SLOT_MAX, R_MEM_DATA_SIZE_MAX, TROPIC_DEVICE_CERT_FIRST_SLOT, TROPIC_DEVICE_CERT_SLOT_COUNT, and related FIDO constants are defined securely, and consider replacing the stack buffer in tropic_data_multi_read() with a heap allocation to avoid large stack frames.
Security signals we found
New C helpers parse a 2-byte length prefix and enforce length <= max_data_length and length + prefix_length <= total_slots_length
Slot index and count are validated against R_MEM_DATA_SLOT_MAX before reading
Multi-read helper requires every slot to return exactly R_MEM_DATA_SIZE_MAX bytes
Large stack allocation of R_MEM_DATA_SIZE_MAX * slot_count noted by the author as suboptimal
SECURE_MODE preprocessor guard moved so new helpers are available in non-secure builds
Evidence from the diff
The patch introduces tropic_data_multi_size() and tropic_data_multi_read() in core/embed/sec/tropic/tropic.c, plus a MicroPython binding get_user_data() in modtrezorcrypto-tropic.h. It also moves the #ifdef SECURE_MODE guard so the new multi-read helpers are compiled even outside secure mode. The helpers read a 2-byte big-endian length prefix from the first user-data slot, validate bounds, then copy the remaining bytes into a caller-supplied buffer. The Python layer maps index 0 to a device certificate and index 1 to a FIDO certificate via constants not shown in the diff.
Changed components
core/embed/sec/tropic/tropic.ccore/embed/sec/tropic/inc/sec/tropic.hcore/embed/upymod/modtrezorcrypto/modtrezorcrypto-tropic.hcore/mocks/generated/trezorcrypto/tropic.pyiInspect captured patch +168 / −23
diff --git a/core/embed/sec/tropic/inc/sec/tropic.h b/core/embed/sec/tropic/inc/sec/tropic.h
index 043916f2..8587f4ad 100644
--- a/core/embed/sec/tropic/inc/sec/tropic.h
+++ b/core/embed/sec/tropic/inc/sec/tropic.h
@@ -63,3 +63,9 @@ bool tropic_ecc_sign(uint16_t key_slot_index, const uint8_t* dig,
uint16_t dig_len, uint8_t* sig);
bool tropic_data_read(uint16_t udata_slot, uint8_t* data, uint16_t* size);
+
+bool tropic_data_multi_size(uint16_t first_slot, size_t* data_length);
+
+bool tropic_data_multi_read(uint16_t first_slot, uint16_t slot_count,
+ uint8_t* data, size_t max_data_length,
+ size_t* data_length);
diff --git a/core/embed/sec/tropic/tropic.c b/core/embed/sec/tropic/tropic.c
index a98f6237..94eddd72 100644
--- a/core/embed/sec/tropic/tropic.c
+++ b/core/embed/sec/tropic/tropic.c
@@ -17,8 +17,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifdef SECURE_MODE
-
#include <trezor_rtl.h>
#include <trezor_types.h>
@@ -37,6 +35,8 @@
#include "ed25519-donna/ed25519.h"
#include "memzero.h"
+#ifdef SECURE_MODE
+
typedef struct {
bool initialized;
bool sec_chan_established;
@@ -185,3 +185,66 @@ bool tropic_data_read(uint16_t udata_slot, uint8_t *data, uint16_t *size) {
}
#endif // SECURE_MODE
+
+bool tropic_data_multi_size(uint16_t first_slot, size_t *data_length) {
+ if (first_slot > R_MEM_DATA_SLOT_MAX) {
+ return false;
+ }
+
+ uint8_t prefixed_data[R_MEM_DATA_SIZE_MAX];
+ uint16_t slot_length = 0;
+ if (!tropic_data_read(first_slot, prefixed_data, &slot_length)) {
+ return false;
+ }
+
+ const size_t prefix_length = 2;
+ if (slot_length < prefix_length) {
+ return false;
+ }
+
+ *data_length = prefixed_data[0] << 8 | prefixed_data[1];
+ return true;
+}
+
+bool tropic_data_multi_read(uint16_t first_slot, uint16_t slot_count,
+ uint8_t *data, size_t max_data_length,
+ size_t *data_length) {
+ const uint16_t last_data_slot = first_slot + slot_count - 1;
+ if (slot_count == 0 || last_data_slot > R_MEM_DATA_SLOT_MAX) {
+ return false;
+ }
+
+ // The following code can be further optimized:
+ // * It uses unnecessary amount of memory.
+ // * It reads from a data slot even if there is no data to be read.
+
+ const size_t total_slots_length = R_MEM_DATA_SIZE_MAX * slot_count;
+ uint8_t prefixed_data[total_slots_length];
+ size_t position = 0;
+ uint16_t slot = first_slot;
+
+ while (slot <= last_data_slot) {
+ uint16_t slot_length = 0;
+ if (!tropic_data_read(slot, prefixed_data + position, &slot_length)) {
+ return false;
+ }
+
+ if (slot_length != R_MEM_DATA_SIZE_MAX) {
+ return false;
+ }
+
+ position += R_MEM_DATA_SIZE_MAX;
+ slot += 1;
+ }
+
+ const size_t prefix_length = 2;
+ size_t length = prefixed_data[0] << 8 | prefixed_data[1];
+ if (length > max_data_length || length + prefix_length > total_slots_length) {
+ return false;
+ }
+
+ *data_length = length;
+ memcpy(data, prefixed_data + prefix_length, length);
+
+ return true;
+}
diff --git a/core/embed/upymod/modtrezorcrypto/modtrezorcrypto-tropic.h b/core/embed/upymod/modtrezorcrypto/modtrezorcrypto-tropic.h
index 00fcac93..1d158368 100644
--- a/core/embed/upymod/modtrezorcrypto/modtrezorcrypto-tropic.h
+++ b/core/embed/upymod/modtrezorcrypto/modtrezorcrypto-tropic.h
@@ -35,6 +35,10 @@ MP_DEFINE_EXCEPTION(TropicError, Exception)
#define CERT_SIZE 512
+#define TROPIC_DEVICE_CERT_INDEX 0
+#define TROPIC_FIDO_CERT_INDEX 1
+
+/// mock:global
/// def ping(message: str) -> str:
/// """
/// Test the session by pinging the chip.
@@ -116,17 +120,74 @@ STATIC mp_obj_t mod_trezorcrypto_tropic_sign(mp_obj_t key_index,
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_tropic_sign_obj,
mod_trezorcrypto_tropic_sign);
+static bool get_slot_range(int index, uint16_t *first_slot,
+ uint16_t *slot_count) {
+ switch (index) {
+ case TROPIC_DEVICE_CERT_INDEX:
+ *first_slot = TROPIC_DEVICE_CERT_FIRST_SLOT;
+ *slot_count = TROPIC_DEVICE_CERT_SLOT_COUNT;
+ break;
+ case TROPIC_FIDO_CERT_INDEX:
+ *first_slot = TROPIC_FIDO_CERT_FIRST_SLOT;
+ *slot_count = TROPIC_FIDO_CERT_SLOT_COUNT;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+/// def get_user_data(index: int) -> bytes:
+/// """
+/// Return the user data stored at the given index.
+/// """
+STATIC mp_obj_t mod_trezorcrypto_tropic_get_user_data(mp_obj_t index) {
+ mp_int_t idx = mp_obj_get_int(index);
+ uint16_t first_slot = 0;
+ uint16_t slot_count = 0;
+ if (!get_slot_range(idx, &first_slot, &slot_count)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("Invalid index."));
+ }
+
+ size_t data_size = 0;
+ if (!tropic_data_multi_size(first_slot, &data_size)) {
+ mp_raise_msg(&mp_type_TropicError,
+ MP_ERROR_TEXT("Failed to read user data size."));
+ }
+
+ vstr_t data = {0};
+ vstr_init_len(&data, data_size);
+ if (!tropic_data_multi_read(first_slot, slot_count, (uint8_t *)data.buf,
+ data.alloc, &data_size)) {
+ vstr_clear(&data);
+ mp_raise_msg(&mp_type_TropicError,
+ MP_ERROR_TEXT("Failed to read user data."));
+ }
+
+ data.len = data_size;
+ return mp_obj_new_str_from_vstr(&mp_type_bytes, &data);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_tropic_get_user_data_obj,
+ mod_trezorcrypto_tropic_get_user_data);
+
+/// DEVICE_CERT_INDEX: int
/// DEVICE_KEY_SLOT: int
+/// FIDO_CERT_INDEX: int
/// FIDO_KEY_SLOT: int
STATIC const mp_rom_map_elem_t mod_trezorcrypto_tropic_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_tropic)},
+ {MP_ROM_QSTR(MP_QSTR_DEVICE_CERT_INDEX),
+ MP_ROM_INT(TROPIC_DEVICE_CERT_INDEX)},
{MP_ROM_QSTR(MP_QSTR_DEVICE_KEY_SLOT), MP_ROM_INT(TROPIC_DEVICE_KEY_SLOT)},
+ {MP_ROM_QSTR(MP_QSTR_FIDO_CERT_INDEX), MP_ROM_INT(TROPIC_FIDO_CERT_INDEX)},
{MP_ROM_QSTR(MP_QSTR_FIDO_KEY_SLOT), MP_ROM_INT(TROPIC_FIDO_KEY_SLOT)},
{MP_ROM_QSTR(MP_QSTR_ping), MP_ROM_PTR(&mod_trezorcrypto_tropic_ping_obj)},
{MP_ROM_QSTR(MP_QSTR_key_generate),
MP_ROM_PTR(&mod_trezorcrypto_tropic_key_generate_obj)},
{MP_ROM_QSTR(MP_QSTR_sign), MP_ROM_PTR(&mod_trezorcrypto_tropic_sign_obj)},
+ {MP_ROM_QSTR(MP_QSTR_get_user_data),
+ MP_ROM_PTR(&mod_trezorcrypto_tropic_get_user_data_obj)},
{MP_ROM_QSTR(MP_QSTR_TropicError), MP_ROM_PTR(&mp_type_TropicError)}};
STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_tropic_globals,
mod_trezorcrypto_tropic_globals_table);
diff --git a/core/mocks/generated/trezorcrypto/tropic.pyi b/core/mocks/generated/trezorcrypto/tropic.pyi
index 3dd84cb4..000df41b 100644
--- a/core/mocks/generated/trezorcrypto/tropic.pyi
+++ b/core/mocks/generated/trezorcrypto/tropic.pyi
@@ -5,24 +5,39 @@ from typing import *
class TropicError(Exception):
"""Error returned by the Tropic Square chip."""
- def ping(message: str) -> str:
- """
- Test the session by pinging the chip.
- """
-
- def key_generate(
- key_index: int,
- ) -> None:
- """
- Generate ECC key in the device's ECC key slot.
- """
-
- def sign(
- key_index: int,
- digest: bytes,
- ) -> bytes:
- """
- Uses the private key at key_index to produce a signature of the digest.
- """
- DEVICE_KEY_SLOT: int
- FIDO_KEY_SLOT: int
+
+# upymod/modtrezorcrypto/modtrezorcrypto-tropic.h
+def ping(message: str) -> str:
+ """
+ Test the session by pinging the chip.
+ """
+
+
+# upymod/modtrezorcrypto/modtrezorcrypto-tropic.h
+def key_generate(
+ key_index: int,
+) -> None:
+ """
+ Generate ECC key in the device's ECC key slot.
+ """
+
+
+# upymod/modtrezorcrypto/modtrezorcrypto-tropic.h
+def sign(
+ key_index: int,
+ digest: bytes,
+) -> bytes:
+ """
+ Uses the private key at key_index to produce a signature of the digest.
+ """
+
+
+# upymod/modtrezorcrypto/modtrezorcrypto-tropic.h
+def get_user_data(index: int) -> bytes:
+ """
+ Return the user data stored at the given index.
+ """
+DEVICE_CERT_INDEX: int
+DEVICE_KEY_SLOT: int
+FIDO_CERT_INDEX: int
+FIDO_KEY_SLOT: int
Why this scored 17/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.