refactor(core/embed): remember optiga auto states
What changed, and why it matters
This change refactors how the Trezor hardware wallet tracks and clears temporary 'auto-state' sessions with the Optiga secure chip. Previously, the code manually cleared specific auto-states one by one. Now it keeps a list of all active auto-states and clears them all at once. The main security-relevant improvement is that the list is updated before sending the command to the chip, so if a communication error happens while receiving the response, the state is still recorded and can be cleaned up later. This reduces the risk of leaking or exhausting limited secure-chip session slots.
Review the new `auto_states` array for thread-safety and concurrency assumptions, verify that `AUTO_STATES_MAX_COUNT` of 5 matches the Optiga hardware limit, and ensure that `optiga_clear_all_auto_states()` is called on all error paths that may leave auto-states registered. Consider adding a changelog entry because this change touches security-relevant secure-element state management.
Security signals we found
Refactor of secure-element session lifecycle management
Defensive change to avoid exhausting limited secure-chip auto-state slots
Addition of new error return path when auto-state tracking array is full
Pre-registration of auto-state before command execution to handle response-receipt failures
No changelog entry despite touching security-critical Optiga code
Evidence from the diff
The patch introduces a static array auto_states[AUTO_STATES_MAX_COUNT] and helper auto_states_add() in optiga_commands.c. optiga_set_auto_state() now registers the key_oid before executing the command, so that a later optiga_clear_all_auto_states() can iterate the list and clear every registered auto-state. Existing call sites in optiga.c are updated to call optiga_clear_all_auto_states() instead of clearing individual OIDs. A new failure path returns OPTIGA_ERR_CODE_MEMORY if the auto-state array is full. The change is defensive: it prevents a scenario where the auto-state is created on the Optiga but, due to a transport/response error, the host does not know about it and therefore cannot clear it, eventually exhausting the chip’s limited auto-state slots.
Changed components
core/embed/sec/optiga/optiga_commands.ccore/embed/sec/optiga/optiga.ccore/embed/sec/optiga/inc/sec/optiga_commands.hInspect captured patch +40 / −5
diff --git a/core/embed/sec/optiga/inc/sec/optiga_commands.h b/core/embed/sec/optiga/inc/sec/optiga_commands.h
index 5fbaecb54..7ffc9fece 100644
--- a/core/embed/sec/optiga/inc/sec/optiga_commands.h
+++ b/core/embed/sec/optiga/inc/sec/optiga_commands.h
@@ -239,6 +239,7 @@ optiga_result optiga_derive_key(optiga_key_derivation deriv, uint16_t oid,
size_t key_size);
optiga_result optiga_set_trust_anchor(void);
optiga_result optiga_set_priv_key(uint16_t oid, const uint8_t priv_key[32]);
+optiga_result optiga_clear_all_auto_states(void);
#if !PRODUCTION
void optiga_command_set_log_hex(optiga_log_hex_t f);
diff --git a/core/embed/sec/optiga/optiga.c b/core/embed/sec/optiga/optiga.c
index fad86e1c6..2105bfbf0 100644
--- a/core/embed/sec/optiga/optiga.c
+++ b/core/embed/sec/optiga/optiga.c
@@ -728,8 +728,7 @@ end:
memzero(pin_hmac, sizeof(pin_hmac));
memzero(pin_secret, sizeof(pin_secret));
memzero(digest, sizeof(digest));
- optiga_clear_auto_state(OID_PIN_SECRET);
- optiga_clear_auto_state(OID_STRETCHED_PIN);
+ optiga_clear_all_auto_states();
optiga_set_ui_progress(NULL);
return ret;
}
@@ -816,7 +815,7 @@ optiga_pin_result optiga_pin_verify_v4(
end:
memzero(stretched_pin, sizeof(stretched_pin));
- optiga_clear_auto_state(OID_STRETCHED_PIN);
+ optiga_clear_all_auto_states();
optiga_set_ui_progress(NULL);
return ret;
}
@@ -946,8 +945,7 @@ optiga_pin_result optiga_pin_verify(
end:
memzero(pin_secret, sizeof(pin_secret));
memzero(digest, sizeof(digest));
- optiga_clear_auto_state(OID_STRETCHED_PIN);
- optiga_clear_auto_state(OID_PIN_SECRET);
+ optiga_clear_all_auto_states();
optiga_set_ui_progress(NULL);
return ret;
}
diff --git a/core/embed/sec/optiga/optiga_commands.c b/core/embed/sec/optiga/optiga_commands.c
index e3e5181bf..a314c1787 100644
--- a/core/embed/sec/optiga/optiga_commands.c
+++ b/core/embed/sec/optiga/optiga_commands.c
@@ -36,10 +36,18 @@
#include "nist256p1.h"
#include "sha2.h"
+#define AUTO_STATES_MAX_COUNT 5
+
// Static buffer for commands and responses.
static uint8_t tx_buffer[OPTIGA_MAX_APDU_SIZE] = {0};
static size_t tx_size = 0;
+// List of all OIDs with auto-state.
+// Optiga can hold a limited number of auto-states. Once this limit is reached,
+// it returns OPTIGA_ERR_CODE_MEMORY.
+static uint16_t auto_states[AUTO_STATES_MAX_COUNT] = {0};
+static size_t auto_states_count = 0;
+
const optiga_metadata_item OPTIGA_META_LCS_OPERATIONAL =
OPTIGA_META_VALUE(OPTIGA_LCS_OP);
const optiga_metadata_item OPTIGA_META_ACCESS_ALWAYS =
@@ -64,6 +72,16 @@ void optiga_command_set_log_hex(optiga_log_hex_t f) { log_hex = f; }
}
#endif
+static bool auto_states_add(optiga_oid oid) {
+ if (auto_states_count >= AUTO_STATES_MAX_COUNT) {
+ return false;
+ }
+
+ auto_states[auto_states_count] = oid;
+ auto_states_count++;
+ return true;
+}
+
static optiga_result process_output(uint8_t **out_data, size_t *out_size) {
// Check that there is no trailing output data in the response.
if (tx_size < 4 || (tx_buffer[2] << 8) + tx_buffer[3] != tx_size - 4) {
@@ -515,6 +533,13 @@ optiga_result optiga_set_auto_state(uint16_t nonce_oid, uint16_t key_oid,
hmac_sha256(key, key_size, nonce, sizeof(nonce), ptr);
OPTIGA_LOG(__func__, tx_buffer, tx_size)
+ // The auto-state is added before the command is actually executed. Otherwise,
+ // it could happen that the command succeeded, but an error occurred during
+ // the receipt of the response. In such a case, the auto-state couldn't be
+ // cleared using optiga_clear_all_auto_states().
+ if (!auto_states_add(key_oid)) {
+ return OPTIGA_ERR_CODE_MEMORY;
+ }
ret = optiga_execute_command(tx_buffer, tx_size, tx_buffer, sizeof(tx_buffer),
&tx_size);
if (ret != OPTIGA_SUCCESS) {
@@ -963,4 +988,15 @@ optiga_result optiga_set_priv_key(uint16_t oid, const uint8_t priv_key[32]) {
return process_output_fixedlen(NULL, 0);
}
+optiga_result optiga_clear_all_auto_states(void) {
+ for (int i = auto_states_count - 1; i >= 0; i--) {
+ optiga_result ret = optiga_clear_auto_state(auto_states[i]);
+ if (ret != OPTIGA_SUCCESS) {
+ return ret;
+ }
+ auto_states_count--;
+ }
+ return OPTIGA_SUCCESS;
+}
+
#endif // SECURE_MODE
Why this scored 35/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.