fix(storage,core/embed): fix and improve estimates
What changed, and why it matters
This commit is a code-quality and accuracy improvement for how the Trezor firmware estimates how long PIN operations will take. It replaces a single coarse heuristic with finer-grained per-command timing estimates for the Optiga and Tropic secure chips, adds a helper that models the chip's security-event-counter throttling, and moves the estimate logic closer to the functions it describes. There is no direct security vulnerability being fixed; the change is about making progress-bar timing more accurate, which indirectly helps user experience and may make side-channel timing analysis slightly more predictable. The commit message says '[no changelog]' and does not claim to fix a security issue.
No immediate security action required. Reviewers may want to verify that the new timing estimates do not materially under-estimate operation duration on any supported hardware, which could cause a poor user experience but is not a security flaw. If using this commit for incident response, treat it as a maintenance/refactoring change rather than a vulnerability patch.
Security signals we found
Timing/estimate logic refactored, not a vulnerability fix
New per-operation timing helpers added for Optiga and Tropic secure elements
Optiga SEC throttling now modeled in operation_add_time()
No changelog entry; commit title describes 'fix and improve estimates'
No explicit security relevance disclosed by vendor
Evidence from the diff
The patch removes the old monolithic optiga_estimate_time_ms() and tropic_estimate_time_ms() functions and introduces a family of *_time() helpers next to the actual cryptographic/storage operations. New helpers include optiga_get_data_object_time, optiga_set_data_object_time, optiga_encrypt_sym_time, optiga_set_auto_state_time, optiga_clear_auto_state_time, optiga_gen_key_pair_time, optiga_gen_sym_key_time, optiga_calc_ssec_time, optiga_reset_counter_time, and corresponding Tropic and RNG timing functions. A central operation_add_time() in optiga_commands.c now models the Optiga SEC (security event counter) increase and the throttling delay that applies when SEC > 127, including the fact that SEC decreases every 5000 ms even when security events occur. storage.c now builds its UI time estimate by summing these per-step estimates plus PBKDF2 and CPU-cycle estimates, rather than using the previous hard-coded constants. A small bug is fixed in the backoff calculation: the old code added the backoff to the progress total before sleeping; the new code still sleeps for the same duration but the estimate is computed inside unlock_time().
Changed components
core/embed/sec/optiga/optiga.ccore/embed/sec/optiga/optiga_commands.ccore/embed/sec/optiga/unix/optiga.ccore/embed/sec/rng/rng_common.ccore/embed/sec/time_estimate/stm32/time_estimate.ccore/embed/sec/time_estimate/unix/time_estimate.ccore/embed/sec/tropic/tropic.cstorage/storage.clegacy/time_estimate.cInspect captured patch +535 / −147
diff --git a/core/embed/sec/optiga/inc/sec/optiga.h b/core/embed/sec/optiga/inc/sec/optiga.h
index a4a3e188..a293cd8c 100644
--- a/core/embed/sec/optiga/inc/sec/optiga.h
+++ b/core/embed/sec/optiga/inc/sec/optiga.h
@@ -62,27 +62,42 @@ void optiga_set_sec_max(void);
bool __wur optiga_random_buffer(uint8_t *dest, size_t size);
+void optiga_random_buffer_time(uint32_t *time_ms);
+
bool __wur optiga_pin_init(optiga_ui_progress_t ui_progress);
+void optiga_pin_init_time(uint32_t *time_ms);
+
bool optiga_pin_stretch_cmac_ecdh(
optiga_ui_progress_t ui_progress,
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]);
+void optiga_pin_stretch_cmac_ecdh_time(uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms);
+
bool __wur optiga_pin_set(
optiga_ui_progress_t ui_progress,
uint8_t stretched_pins[STRETCHED_PIN_COUNT][OPTIGA_PIN_SECRET_SIZE],
uint8_t hmac_reset_key[OPTIGA_PIN_SECRET_SIZE]);
+void optiga_pin_set_time(uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms);
+
bool __wur
optiga_pin_reset_hmac_counter(optiga_ui_progress_t ui_progress,
const uint8_t reset_key[OPTIGA_PIN_SECRET_SIZE]);
-uint32_t optiga_estimate_time_ms(storage_pin_op_t op, uint8_t slot_index);
+void optiga_pin_reset_hmac_counter_time(
+ uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms);
optiga_pin_result __wur
optiga_pin_verify(optiga_ui_progress_t ui_progress, uint8_t index,
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]);
+void optiga_pin_verify_time(uint8_t pin_index, uint32_t *time_ms,
+ uint8_t *optiga_sec, uint32_t *optiga_last_time);
+
optiga_pin_result __wur
optiga_pin_verify_v4(optiga_ui_progress_t ui_progress,
const uint8_t pin_secret[OPTIGA_PIN_SECRET_SIZE],
diff --git a/core/embed/sec/optiga/inc/sec/optiga_commands.h b/core/embed/sec/optiga/inc/sec/optiga_commands.h
index 0cbd6e05..1d8c785d 100644
--- a/core/embed/sec/optiga/inc/sec/optiga_commands.h
+++ b/core/embed/sec/optiga/inc/sec/optiga_commands.h
@@ -205,17 +205,26 @@ optiga_result optiga_get_error_code(uint8_t *error_code);
optiga_result optiga_get_data_object(uint16_t oid, bool get_metadata,
uint8_t *data, size_t max_data_size,
size_t *data_size);
+void optiga_get_data_object_time(bool is_metadata, uint32_t *time_ms);
optiga_result optiga_set_data_object(uint16_t oid, bool set_metadata,
const uint8_t *data, size_t data_size);
+void optiga_set_data_object_time(bool is_metadata, uint32_t *time_ms);
optiga_result optiga_count_data_object(uint16_t oid, uint8_t count);
optiga_result optiga_get_random(uint8_t *random, size_t random_size);
+void optiga_get_random_time(uint32_t *time_ms);
optiga_result optiga_encrypt_sym(optiga_sym_mode mode, uint16_t oid,
const uint8_t *input, size_t input_size,
uint8_t *output, size_t max_output_size,
size_t *output_size);
+void optiga_encrypt_sym_time(optiga_sym_mode mode, uint32_t *time_ms,
+ uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms_ms);
optiga_result optiga_set_auto_state(uint16_t nonce_oid, uint16_t key_oid,
const uint8_t *key, size_t key_size);
+void optiga_set_auto_state_time(uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms);
optiga_result optiga_clear_auto_state(uint16_t key_oid);
+void optiga_clear_auto_state_time(uint32_t *time_ms);
optiga_result optiga_calc_sign(uint16_t oid, const uint8_t *digest,
size_t digest_size, uint8_t *signature,
size_t max_sig_size, size_t *sig_size);
@@ -227,12 +236,16 @@ optiga_result optiga_gen_key_pair(optiga_curve curve, optiga_key_usage usage,
uint16_t oid, uint8_t *public_key,
size_t max_public_key_size,
size_t *public_key_size);
+void optiga_gen_key_pair_time(uint32_t *time_ms);
optiga_result optiga_gen_sym_key(optiga_aes algorithm, optiga_key_usage usage,
uint16_t oid);
+void optiga_gen_sym_key_time(uint32_t *time_ms);
optiga_result optiga_calc_ssec(optiga_curve curve, uint16_t oid,
const uint8_t *public_key,
size_t public_key_size, uint8_t *secret,
size_t max_secret_size, size_t *secret_size);
+void optiga_calc_ssec_time(uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms);
optiga_result optiga_derive_key(optiga_key_derivation deriv, uint16_t oid,
const uint8_t *salt, size_t salt_size,
uint8_t *info, size_t info_size, uint8_t *key,
@@ -241,6 +254,7 @@ 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);
optiga_result optiga_reset_counter(uint16_t oid, uint32_t limit);
+void optiga_reset_counter_time(uint32_t *time_ms);
#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 65143c6a..08677138 100644
--- a/core/embed/sec/optiga/optiga.c
+++ b/core/embed/sec/optiga/optiga.c
@@ -33,6 +33,7 @@
#include "hmac.h"
#include "memzero.h"
#include "nist256p1.h"
+#include "time_estimate.h"
// Counter-protected PIN secret and reset key for OID_STRETCHED_PIN_CTR (OID
// 0xF1D0).
@@ -68,9 +69,6 @@
#define PIN_STRETCH_ITERATIONS 2
#endif
-// The throttling delay when the security event counter is at its maximum.
-#define OPTIGA_T_MAX_MS 5000
-
// Initial value of the counter which limits the total number of PIN stretching
// operations. The limit is 600000 stretching operations, which equates to
// 300000 / PIN_STRETCH_ITERATIONS unlock operations over the lifetime of the
@@ -252,71 +250,6 @@ void optiga_set_sec_max(void) {
sizeof(invalid_point), buffer, sizeof(buffer), &size);
}
-// slot_index is only used if op == STORAGE_PIN_OP_VERIFY
-uint32_t optiga_estimate_time_ms(storage_pin_op_t op, uint8_t pin_index) {
- uint8_t sec = 0;
- if (!optiga_read_sec(&sec)) {
- return UINT32_MAX;
- }
-
- // Heuristic: The SEC will increase by about 4 during the operation up to a
- // maximum of 255.
- sec = (sec < 255 - 4) ? sec + 4 : 255;
-
- // If the SEC is above 127, then Optiga introduces a throttling delay before
- // the execution of each protected command. The delay grows propotionally to
- // the SEC value up to a maximum delay of OPTIGA_T_MAX_MS.
- uint32_t throttling_delay =
- sec > 127 ? (sec - 127) * OPTIGA_T_MAX_MS / 128 : 0;
-
- // To estimate the overall time of the PIN operation we multiply the
- // throttling delay by the number of protected Optiga commands and add the
- // time required to execute all Optiga commands without throttling delays.
-
- // Time: (STRETCHED_PIN_COUNT + 7) * 15 ms + 295 ms
- const int init_time = (STRETCHED_PIN_COUNT + 7) * 15 + 295;
- const int init_protected_command_count = 0;
-
- const int stretch_cmac_ecdh_time = PIN_STRETCH_ITERATIONS * 275;
- const int stretch_cmac_ecdh_protected_command_count =
- PIN_STRETCH_ITERATIONS * 2;
-
- const int set_time = STRETCHED_PIN_COUNT * 180 + 400;
- const int set_protected_command_count = 2 * (STRETCHED_PIN_COUNT + 1);
-
- const int verify_time = (STRETCHED_PIN_COUNT - pin_index - 1) * 170 + 470;
- const int verify_protected_command_count =
- 1 + 2 * (STRETCHED_PIN_COUNT - pin_index + 1);
-
- const int reset_hmac_counter_time = 170;
- const int reset_hmac_counter_protected_command_count = 2;
-
- const int pin_set_time =
- throttling_delay * (init_protected_command_count +
- stretch_cmac_ecdh_protected_command_count +
- set_protected_command_count) +
- init_time + stretch_cmac_ecdh_time + set_time;
- const int pin_verify_time =
- throttling_delay * (stretch_cmac_ecdh_protected_command_count +
- verify_protected_command_count) +
- stretch_cmac_ecdh_time + verify_time +
- (pin_index == 0
- ? 0
- : (reset_hmac_counter_time +
- throttling_delay * reset_hmac_counter_protected_command_count));
-
- switch (op) {
- case STORAGE_PIN_OP_SET:
- return pin_set_time;
- case STORAGE_PIN_OP_VERIFY:
- return pin_verify_time;
- case STORAGE_PIN_OP_CHANGE:
- return pin_set_time + pin_verify_time;
- default:
- return 0;
- }
-}
-
bool optiga_random_buffer(uint8_t *dest, size_t size) {
while (size > OPTIGA_RANDOM_MAX_SIZE) {
if (optiga_get_random(dest, OPTIGA_RANDOM_MAX_SIZE) != OPTIGA_SUCCESS) {
@@ -336,6 +269,11 @@ bool optiga_random_buffer(uint8_t *dest, size_t size) {
return optiga_get_random(dest, size) == OPTIGA_SUCCESS;
}
+void optiga_random_buffer_time(uint32_t *time_ms) {
+ // Assuming the data size is 32 bytes
+ return optiga_get_random_time(time_ms);
+}
+
static bool read_metadata(uint16_t oid, optiga_metadata *metadata) {
static uint8_t serialized[OPTIGA_MAX_METADATA_SIZE] = {0};
size_t size = 0;
@@ -403,6 +341,43 @@ bool optiga_set_metadata(uint16_t oid, const optiga_metadata *metadata) {
return true;
}
+void optiga_set_metadata_time(bool is_configured, uint32_t *time_ms) {
+ optiga_get_data_object_time(true, time_ms);
+ if (!is_configured) {
+ optiga_set_data_object_time(true, time_ms);
+ optiga_get_data_object_time(true, time_ms);
+ }
+#if PRODUCTION
+ if (!is_configured) {
+ optiga_set_data_object_time(true, time_ms);
+ optiga_get_data_object_time(true, time_ms);
+ }
+#endif
+}
+
+// This is a heuristic and can only be used to estimate how long it will take to
+// execute `optiga_pin_init_metadata()
+static bool optiga_is_configured() {
+ // Read the metadata of OID_PIN_SECRET to determine whether
+ // optiga_pin_init_metadata() has been called in the past
+ optiga_metadata metadata = {0};
+ optiga_metadata metadata_stored = {0};
+
+ metadata.change = OPTIGA_META_ACCESS_ALWAYS;
+ metadata.read = ACCESS_LAST_STRETCHED_PIN;
+ metadata.execute = OPTIGA_META_ACCESS_ALWAYS;
+ metadata.data_type = TYPE_AUTOREF;
+#if PRODUCTION
+ metadata_locked.lcso = OPTIGA_META_LCS_OPERATIONAL;
+#endif
+
+ if (!read_metadata(OID_PIN_SECRET, &metadata_stored)) {
+ return false;
+ }
+
+ return optiga_compare_metadata(&metadata, &metadata_stored);
+}
+
static bool optiga_pin_init_metadata() {
optiga_metadata metadata = {0};
@@ -538,6 +513,33 @@ static bool optiga_pin_init_metadata() {
return true;
}
+static void optiga_pin_init_metadata_time(uint32_t *time_ms) {
+ bool is_configured = optiga_is_configured();
+ optiga_set_metadata_time(is_configured, time_ms); // OID_PIN_SECRET
+
+#if STRETCHED_PIN_COUNT == 1
+ optiga_set_metadata_time(is_configured, time_ms); // OID_STRETCHED_PINS[0]
+#else
+ optiga_set_metadata_time(is_configured, time_ms); // OID_STRETCHED_PINS[0]
+ for (int i = 1; i < STRETCHED_PIN_COUNT - 1; i++) {
+ // OID_STRETCHED_PINS[i]
+ optiga_set_metadata_time(is_configured, time_ms);
+ }
+ // OID_STRETCHED_PINS[STRETCHED_PIN_COUNT - 1]
+ optiga_set_metadata_time(is_configured, time_ms);
+#endif
+ optiga_set_metadata_time(is_configured, time_ms); // OID_PIN_HMAC
+ optiga_set_metadata_time(is_configured, time_ms); // OID_STRETCHED_PIN_CTR
+ optiga_set_metadata_time(is_configured, time_ms); // OID_PIN_HMAC_CTR
+ optiga_set_data_object_time(true, time_ms); // OID_PIN_TOTAL_CTR
+ if (is_configured) {
+ optiga_reset_counter_time(time_ms); // OID_PIN_TOTAL_CTR
+ }
+ optiga_set_metadata_time(is_configured, time_ms); // OID_PIN_TOTAL_CTR
+ optiga_set_metadata_time(is_configured, time_ms); // OID_PIN_CMAC
+ optiga_set_metadata_time(is_configured, time_ms); // OID_PIN_ECDH
+}
+
static bool optiga_pin_init_stretch() {
// Generate a new key in OID_PIN_CMAC.
if (optiga_gen_sym_key(OPTIGA_AES_256, OPTIGA_KEY_USAGE_ENC, OID_PIN_CMAC) !=
@@ -555,6 +557,11 @@ static bool optiga_pin_init_stretch() {
return res == OPTIGA_SUCCESS;
}
+static void optiga_pin_init_stretch_time(uint32_t *time_ms) {
+ optiga_gen_sym_key_time(time_ms);
+ optiga_gen_key_pair_time(time_ms);
+}
+
static bool optiga_pin_stretch_common(
HMAC_SHA256_CTX *ctx, const uint8_t input[OPTIGA_PIN_SECRET_SIZE],
bool version4) {
@@ -644,9 +651,6 @@ static bool optiga_pin_stretch_secret_v4(
bool optiga_pin_stretch_cmac_ecdh(
optiga_ui_progress_t ui_progress,
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]) {
- // Protected commands: PIN_STRETCH_ITERATIONS * 2
- // Time: PIN_STRETCH_ITERATIONS * 275 ms
-
optiga_set_ui_progress(ui_progress);
// This step hardens the PIN verification process in case an attacker is able
// to extract the secret value of a data object in Optiga that has a
@@ -691,15 +695,29 @@ end:
return ret;
}
+void optiga_pin_stretch_cmac_ecdh_time(
+ uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {
+ for (int i = 0; i < PIN_STRETCH_ITERATIONS; ++i) {
+ optiga_encrypt_sym_time(OPTIGA_SYM_MODE_CMAC, time_ms, optiga_sec,
+ optiga_last_time_decreased_ms);
+ *time_ms += time_estimate_hash_to_curve_ms();
+ optiga_calc_ssec_time(time_ms, optiga_sec, optiga_last_time_decreased_ms);
+ }
+}
+
bool optiga_pin_init(optiga_ui_progress_t ui_progress) {
- // Time: (STRETCHED_PIN_COUNT + 7) * 15 ms + 295 ms
- // Protected commands: 0
optiga_set_ui_progress(ui_progress);
bool ret = optiga_pin_init_metadata() && optiga_pin_init_stretch();
optiga_set_ui_progress(NULL);
return ret;
}
+void optiga_pin_init_time(uint32_t *time_ms) {
+ optiga_pin_init_metadata_time(time_ms);
+ optiga_pin_init_stretch_time(time_ms);
+}
+
static void optiga_pin_stretch_hmac_offline(
const uint8_t hmac_stretching_secret[OPTIGA_PIN_SECRET_SIZE],
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]) {
@@ -731,9 +749,6 @@ bool optiga_pin_set(
optiga_ui_progress_t ui_progress,
uint8_t stretched_pins[STRETCHED_PIN_COUNT][OPTIGA_PIN_SECRET_SIZE],
uint8_t hmac_reset_key[OPTIGA_PIN_SECRET_SIZE]) {
- // Protected commands: 2 * (STRETCHED_PIN_COUNT + 1)
- // Time: STRETCHED_PIN_COUNT * 180 ms + 400 ms
-
optiga_set_ui_progress(ui_progress);
bool ret = true;
@@ -783,8 +798,6 @@ bool optiga_pin_set(
uint8_t digest[OPTIGA_PIN_SECRET_SIZE] = {0};
for (int i = STRETCHED_PIN_COUNT - 1; i >= 0; i--) {
- // Time: 180 ms
-
// Process the stretched PIN using a one-way function before sending it to
// the Optiga. This ensures that in the unlikely case of an attacker
// recording communication between the MCU and Optiga, they will not gain
@@ -844,6 +857,29 @@ end:
return ret;
}
+void optiga_pin_set_time(uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {
+ rng_fill_buffer_strong_time(time_ms); // hmac_stretching_secret
+ rng_fill_buffer_strong_time(time_ms); // pin_secret
+ optiga_set_data_object_time(false, time_ms); // OID_PIN_SECRET
+ // OID_PIN_SECRET
+ optiga_set_auto_state_time(time_ms, optiga_sec,
+ optiga_last_time_decreased_ms);
+ optiga_reset_counter_time(time_ms); // OID_STRETCHED_PIN_CTR
+ for (int i = STRETCHED_PIN_COUNT - 1; i >= 0; i--) {
+ optiga_set_data_object_time(false, time_ms); // OID_STRETCHED_PINS[i]
+ // OID_STRETCHED_PINS[i - 1] or OID_PIN_SECRET
+ optiga_clear_auto_state_time(time_ms);
+ // OID_STRETCHED_PINS[i]
+ optiga_set_auto_state_time(time_ms, optiga_sec,
+ optiga_last_time_decreased_ms);
+ }
+ optiga_set_data_object_time(false, time_ms); // OID_PIN_HMAC
+ optiga_reset_counter_time(time_ms); // OID_PIN_HMAC_CTR
+ // OID_STRETCHED_PINS[STRETCHED_PIN_COUNT - 1]
+ optiga_clear_auto_state_time(time_ms);
+}
+
optiga_pin_result optiga_pin_verify_v4(
optiga_ui_progress_t ui_progress,
const uint8_t pin_secret[OPTIGA_PIN_SECRET_SIZE],
@@ -930,9 +966,6 @@ end:
static optiga_pin_result optiga_pin_stretch_hmac(
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]) {
- // Protected commands: 1
- // Time: 120 ms
-
optiga_pin_result ret = OPTIGA_PIN_SUCCESS;
// Process the stretched PIN using a one-way function before sending it to the
@@ -968,12 +1001,16 @@ end:
return ret;
}
+static void optiga_pin_stretch_hmac_time(
+ uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {
+ optiga_encrypt_sym_time(OPTIGA_SYM_MODE_HMAC_SHA256, time_ms, optiga_sec,
+ optiga_last_time_decreased_ms);
+}
+
optiga_pin_result optiga_pin_verify(
optiga_ui_progress_t ui_progress, uint8_t pin_index,
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]) {
- // Protected commands: 1 + 2 * (STRETCHED_PIN_COUNT - pin_index + 1)
- // Time: (STRETCHED_PIN_COUNT - pin_index - 1) * 170 ms + 470 ms
-
optiga_set_ui_progress(ui_progress);
optiga_pin_result ret = OPTIGA_PIN_SUCCESS;
@@ -1035,8 +1072,6 @@ optiga_pin_result optiga_pin_verify(
}
for (int i = pin_index + 1; i < STRETCHED_PIN_COUNT; i++) {
- // Time: 170 ms
-
size_t size = 0;
if (optiga_get_data_object(OID_STRETCHED_PINS[i], false, digest,
OPTIGA_PIN_SECRET_SIZE,
@@ -1090,12 +1125,32 @@ end:
return ret;
}
+void optiga_pin_verify_time(uint8_t pin_index, uint32_t *time_ms,
+ uint8_t *optiga_sec, uint32_t *optiga_last_time) {
+ optiga_pin_stretch_hmac_time(time_ms, optiga_sec, optiga_last_time);
+ // OID_STRETCHED_PINS[pin_index]
+ optiga_set_auto_state_time(time_ms, optiga_sec, optiga_last_time);
+ if (pin_index == 0) {
+ optiga_reset_counter_time(time_ms); // OID_PIN_HMAC_CTR
+ }
+ for (int i = pin_index + 1; i < STRETCHED_PIN_COUNT; i++) {
+ optiga_get_data_object_time(false, time_ms); // OID_STRETCHED_PINS[i]
+ optiga_clear_auto_state_time(time_ms); // OID_STRETCHED_PINS[i - 1]
+ // OID_STRETCHED_PINS[i]
+ optiga_set_auto_state_time(time_ms, optiga_sec, optiga_last_time);
+ }
+ optiga_get_data_object_time(false, time_ms); // OID_PIN_SECRET
+ // OID_STRETCHED_PIN_CTR[STRETCHED_PIN_COUNT - 1]
+ optiga_clear_auto_state_time(time_ms);
+ // OID_PIN_SECRET
+ optiga_set_auto_state_time(time_ms, optiga_sec, optiga_last_time);
+ optiga_reset_counter_time(time_ms); // OID_STRETCHED_PIN_CTR
+ optiga_clear_auto_state_time(time_ms); // OID_PIN_SECRET
+}
+
bool optiga_pin_reset_hmac_counter(
optiga_ui_progress_t ui_progress,
const uint8_t hmac_reset_key[OPTIGA_PIN_SECRET_SIZE]) {
- // Protected commands: 2
- // Time: 170 ms
-
optiga_set_ui_progress(ui_progress);
bool res = false;
@@ -1120,6 +1175,16 @@ cleanup:
return res;
}
+void optiga_pin_reset_hmac_counter_time(
+ uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {
+ optiga_set_auto_state_time(
+ time_ms, optiga_sec,
+ optiga_last_time_decreased_ms); // OID_STRETCHED_PINS[0]
+ optiga_reset_counter_time(time_ms); // OID_PIN_HMAC_CTR
+ optiga_clear_auto_state_time(time_ms); // OID_STRETCHED_PINS[0]
+}
+
static uint32_t uint32_from_be(uint8_t buf[4]) {
uint32_t i = buf[0];
i = (i << 8) + buf[1];
diff --git a/core/embed/sec/optiga/optiga_commands.c b/core/embed/sec/optiga/optiga_commands.c
index 40052892..a8d32bd2 100644
--- a/core/embed/sec/optiga/optiga_commands.c
+++ b/core/embed/sec/optiga/optiga_commands.c
@@ -36,6 +36,9 @@
#include "nist256p1.h"
#include "sha2.h"
+// The throttling delay when the security event counter is at its maximum.
+#define OPTIGA_T_MAX_MS 5000
+
#define AUTO_STATES_MAX_COUNT 5
// Static buffer for commands and responses.
@@ -82,6 +85,38 @@ static bool auto_states_add(optiga_oid oid) {
return true;
}
+static void operation_add_time(uint32_t *total_time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms,
+ uint32_t operation_time_ms) {
+ if (optiga_sec && optiga_last_time_decreased_ms) {
+ // Every OPTIGA_T_MAX_MS, the SEC counter decreases by 1. Contrary to the
+ // documentation, this occurs even if a security event happens during this
+ // interval.
+ int decrease =
+ (*total_time_ms - *optiga_last_time_decreased_ms) / OPTIGA_T_MAX_MS;
+ *optiga_last_time_decreased_ms += decrease * OPTIGA_T_MAX_MS;
+ if (decrease > *optiga_sec) {
+ *optiga_sec = 0;
+ } else {
+ *optiga_sec -= decrease;
+ }
+
+ // The maximum value of SEC is 255.
+ if (*optiga_sec < 255) {
+ *optiga_sec += 1;
+ }
+
+ // If the SEC is above 127, then Optiga introduces a throttling delay before
+ // the execution of each protected command. The delay grows propotionally to
+ // the SEC value up to a maximum delay of OPTIGA_T_MAX_MS.
+ if (*optiga_sec > 127) {
+ *total_time_ms += (*optiga_sec - 127) * OPTIGA_T_MAX_MS / 128;
+ }
+ }
+
+ *total_time_ms += operation_time_ms;
+}
+
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) {
@@ -357,6 +392,15 @@ optiga_result optiga_get_data_object(uint16_t oid, bool get_metadata,
return process_output_varlen(data, max_data_size, data_size);
}
+void optiga_get_data_object_time(bool is_metadata, uint32_t *time_ms) {
+ if (is_metadata) {
+ operation_add_time(time_ms, NULL, NULL, 17);
+ } else {
+ // Assuming the data size is 32 bytes
+ operation_add_time(time_ms, NULL, NULL, 23);
+ }
+}
+
/*
* https://github.com/Infineon/optiga-trust-m/blob/develop/documents/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#setdataobject
*/
@@ -392,6 +436,15 @@ optiga_result optiga_set_data_object(uint16_t oid, bool set_metadata,
return ret;
}
+void optiga_set_data_object_time(bool is_metadata, uint32_t *time_ms) {
+ if (is_metadata) {
+ operation_add_time(time_ms, NULL, NULL, 49);
+ } else {
+ // Assuming the data size is 32 bytes
+ operation_add_time(time_ms, NULL, NULL, 35);
+ }
+}
+
/*
* https://github.com/Infineon/optiga-trust-m/blob/develop/documents/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#setdataobject
*/
@@ -452,6 +505,11 @@ optiga_result optiga_get_random(uint8_t *random, size_t random_size) {
return process_output_fixedlen(random, random_size);
}
+void optiga_get_random_time(uint32_t *time_ms) {
+ // Assuming the random size is 32 bytes
+ operation_add_time(time_ms, NULL, NULL, 16);
+}
+
/*
* https://github.com/Infineon/optiga-trust-m/blob/develop/documents/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#encryptsym
* Returns 0x61, mac_size (2 bytes), mac.
@@ -486,6 +544,24 @@ optiga_result optiga_encrypt_sym(optiga_sym_mode mode, uint16_t oid,
return ret;
}
+void optiga_encrypt_sym_time(optiga_sym_mode mode, uint32_t *time_ms,
+ uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {
+ switch (mode) {
+ case OPTIGA_SYM_MODE_CMAC:
+ operation_add_time(time_ms, optiga_sec, optiga_last_time_decreased_ms,
+ 56);
+ break;
+ case OPTIGA_SYM_MODE_HMAC_SHA256:
+ operation_add_time(time_ms, optiga_sec, optiga_last_time_decreased_ms,
+ 122);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+}
+
/*
* https://github.com/Infineon/optiga-trust-m/blob/develop/documents/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#decryptsym
*/
@@ -549,6 +625,12 @@ optiga_result optiga_set_auto_state(uint16_t nonce_oid, uint16_t key_oid,
return process_output_fixedlen(NULL, 0);
}
+void optiga_set_auto_state_time(uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {
+ // Assuming the key size is 32 bytes
+ operation_add_time(time_ms, optiga_sec, optiga_last_time_decreased_ms, 131);
+}
+
optiga_result optiga_clear_auto_state(uint16_t key_oid) {
tx_size = 12;
uint8_t *ptr = tx_buffer;
@@ -580,6 +662,10 @@ optiga_result optiga_clear_auto_state(uint16_t key_oid) {
return OPTIGA_SUCCESS;
}
+void optiga_clear_auto_state_time(uint32_t *time_ms) {
+ operation_add_time(time_ms, NULL, NULL, 13);
+}
+
/*
* https://github.com/Infineon/optiga-trust-m/blob/develop/documents/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#calcsign
* Returns a signature pair (r,s) encoded as two DER INTEGERs.
@@ -716,6 +802,11 @@ optiga_result optiga_gen_key_pair(optiga_curve curve, optiga_key_usage usage,
public_key_size);
}
+void optiga_gen_key_pair_time(uint32_t *time_ms) {
+ // Assuming the curve is OPTIGA_CURVE_P256
+ operation_add_time(time_ms, NULL, NULL, 151);
+}
+
/*
* https://github.com/Infineon/optiga-trust-m/blob/develop/documents/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#gensymkey
*/
@@ -745,6 +836,11 @@ optiga_result optiga_gen_sym_key(optiga_aes algorithm, optiga_key_usage usage,
return process_output_fixedlen(NULL, 0);
}
+void optiga_gen_sym_key_time(uint32_t *time_ms) {
+ // Assuming the key type is OPTIGA_AES_256
+ operation_add_time(time_ms, NULL, NULL, 36);
+}
+
/*
* https://github.com/Infineon/optiga-trust-m/blob/develop/documents/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#calcssec
* The public_key is encoded as a DER BIT STRING.
@@ -790,6 +886,12 @@ optiga_result optiga_calc_ssec(optiga_curve curve, uint16_t oid,
return process_output_varlen(secret, max_secret_size, secret_size);
}
+void optiga_calc_ssec_time(uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {
+ // Assuming the curve is OPTIGA_CURVE_P256
+ operation_add_time(time_ms, optiga_sec, optiga_last_time_decreased_ms, 150);
+}
+
/*
* https://github.com/Infineon/optiga-trust-m/blob/develop/documents/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#derivekey
*/
@@ -1008,4 +1110,8 @@ optiga_result optiga_reset_counter(uint16_t oid, uint32_t limit) {
return optiga_set_data_object(oid, false, value_array, sizeof(value_array));
}
+void optiga_reset_counter_time(uint32_t *time_ms) {
+ operation_add_time(time_ms, NULL, NULL, 24);
+}
+
#endif // SECURE_MODE
diff --git a/core/embed/sec/optiga/unix/optiga.c b/core/embed/sec/optiga/unix/optiga.c
index 63de7ed3..0eff6c49 100644
--- a/core/embed/sec/optiga/unix/optiga.c
+++ b/core/embed/sec/optiga/unix/optiga.c
@@ -101,12 +101,20 @@ void optiga_set_sec_max(void) {}
bool optiga_pin_init(optiga_ui_progress_t ui_progress) { return true; }
+void optiga_pin_init_time(uint32_t *time_ms) {}
+
+bool optiga_is_initialized() { return false; }
+
bool optiga_pin_stretch_cmac_ecdh(
optiga_ui_progress_t ui_progress,
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]) {
return true;
}
+void optiga_pin_stretch_cmac_ecdh_time(
+ uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {}
+
uint32_t optiga_estimate_time_ms(storage_pin_op_t op, uint8_t slot_index) {
return 0;
}
@@ -116,6 +124,8 @@ bool optiga_random_buffer(uint8_t *dest, size_t size) {
return true;
}
+void optiga_random_buffer_time(uint32_t *time_ms) {}
+
bool optiga_pin_set(
optiga_ui_progress_t ui_progress,
uint8_t stretched_pins[STRETCHED_PIN_COUNT][OPTIGA_PIN_SECRET_SIZE],
@@ -123,6 +133,9 @@ bool optiga_pin_set(
return true;
}
+void optiga_pin_set_time(uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {}
+
optiga_pin_result optiga_pin_verify_v4(
optiga_ui_progress_t ui_progress,
const uint8_t pin_secret[OPTIGA_PIN_SECRET_SIZE],
@@ -137,12 +150,19 @@ optiga_pin_result optiga_pin_verify(
return OPTIGA_PIN_SUCCESS;
}
+void optiga_pin_verify_time(uint8_t pin_index, uint32_t *time_ms,
+ uint8_t *optiga_sec, uint32_t *optiga_last_time) {}
+
bool optiga_pin_reset_hmac_counter(
optiga_ui_progress_t ui_progress,
const uint8_t hmac_reset_key[OPTIGA_PIN_SECRET_SIZE]) {
return true;
}
+void optiga_pin_reset_hmac_counter_time(
+ uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {}
+
bool optiga_pin_get_rem_v4(uint32_t *ctr) {
*ctr = PIN_MAX_TRIES;
return true;
diff --git a/core/embed/sec/rng/inc/sec/rng.h b/core/embed/sec/rng/inc/sec/rng.h
index f526bbc6..72b1dfe2 100644
--- a/core/embed/sec/rng/inc/sec/rng.h
+++ b/core/embed/sec/rng/inc/sec/rng.h
@@ -72,3 +72,5 @@ static inline uint32_t rng_get(void) {
* @return True on success, false on failure.
*/
bool __wur rng_fill_buffer_strong(void* buffer, size_t buffer_size);
+
+void rng_fill_buffer_strong_time(uint32_t* time_ms);
diff --git a/core/embed/sec/rng/rng_common.c b/core/embed/sec/rng/rng_common.c
index 69341af1..66188d52 100644
--- a/core/embed/sec/rng/rng_common.c
+++ b/core/embed/sec/rng/rng_common.c
@@ -70,4 +70,14 @@ bool rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
return true;
}
+void rng_fill_buffer_strong_time(uint32_t* time_ms) {
+ // Assuming the buffer size is 32 bytes
+#ifdef USE_OPTIGA
+ optiga_random_buffer_time(time_ms);
+#endif
+#ifdef USE_TROPIC
+ tropic_random_buffer_time(time_ms);
+#endif
+}
+
#endif // SECURE_MODE
diff --git a/core/embed/sec/time_estimate/inc/sec/time_estimate.h b/core/embed/sec/time_estimate/inc/sec/time_estimate.h
index b23396c6..8d8d9b9d 100644
--- a/core/embed/sec/time_estimate/inc/sec/time_estimate.h
+++ b/core/embed/sec/time_estimate/inc/sec/time_estimate.h
@@ -22,6 +22,8 @@
#include <trezor_types.h>
+uint32_t time_estimate_clock_cycles_ms(uint32_t cycles);
uint32_t time_estimate_pbkdf2_ms(uint32_t iterations);
+uint32_t time_estimate_hash_to_curve_ms();
#endif
diff --git a/core/embed/sec/time_estimate/stm32/time_estimate.c b/core/embed/sec/time_estimate/stm32/time_estimate.c
index 02d647cd..9fab834b 100644
--- a/core/embed/sec/time_estimate/stm32/time_estimate.c
+++ b/core/embed/sec/time_estimate/stm32/time_estimate.c
@@ -22,7 +22,18 @@
// The number of CPU cycles required to execute one iteration of PBKDF2.
#define PIN_PBKDF2_CYCLES_PER_ITER 11100
-uint32_t time_estimate_pbkdf2_ms(uint32_t iterations) {
+// The number of CPU cycles required to execute hash_to_curve_optiga()
+#define HASH_TO_CURVE_CYCLES_PER_ITER 9450000
+
+uint32_t time_estimate_clock_cycles_ms(uint32_t cycles) {
extern uint32_t SystemCoreClock;
- return PIN_PBKDF2_CYCLES_PER_ITER * iterations / (SystemCoreClock / 1000);
+ return cycles / (SystemCoreClock / 1000);
+}
+
+uint32_t time_estimate_pbkdf2_ms(uint32_t iterations) {
+ return time_estimate_clock_cycles_ms(PIN_PBKDF2_CYCLES_PER_ITER * iterations);
+}
+
+uint32_t time_estimate_hash_to_curve_ms() {
+ return time_estimate_clock_cycles_ms(HASH_TO_CURVE_CYCLES_PER_ITER);
}
diff --git a/core/embed/sec/time_estimate/unix/time_estimate.c b/core/embed/sec/time_estimate/unix/time_estimate.c
index 22f1ca83..7719a822 100644
--- a/core/embed/sec/time_estimate/unix/time_estimate.c
+++ b/core/embed/sec/time_estimate/unix/time_estimate.c
@@ -23,3 +23,5 @@ uint32_t time_estimate_pbkdf2_ms(uint32_t iterations) {
(void)iterations;
return 500;
}
+
+uint32_t time_estimate_clock_cycles_ms(uint32_t cycles) { return 0; }
diff --git a/core/embed/sec/tropic/inc/sec/tropic.h b/core/embed/sec/tropic/inc/sec/tropic.h
index fc0edae8..26da3640 100644
--- a/core/embed/sec/tropic/inc/sec/tropic.h
+++ b/core/embed/sec/tropic/inc/sec/tropic.h
@@ -104,28 +104,40 @@ bool tropic_data_multi_read(uint16_t first_slot, uint16_t slot_count,
bool tropic_random_buffer(void* buffer, size_t length);
+void tropic_random_buffer_time(uint32_t* time_ms);
+
#ifdef USE_STORAGE
+void tropic_session_start_time(uint32_t* time_ms);
+
bool tropic_pin_stretch(tropic_ui_progress_t ui_progress, uint16_t pin_index,
uint8_t stretched_pin[TROPIC_MAC_AND_DESTROY_SIZE]);
+void tropic_pin_stretch_time(uint32_t* time_ms);
+
bool tropic_pin_reset_slots(
tropic_ui_progress_t ui_progress, uint16_t pin_index,
const uint8_t reset_key[TROPIC_MAC_AND_DESTROY_SIZE]);
+void tropic_pin_reset_slots_time(uint32_t* time_ms, uint16_t pin_index);
+
bool tropic_pin_set(
tropic_ui_progress_t ui_progress,
uint8_t stretched_pins[PIN_MAX_TRIES][TROPIC_MAC_AND_DESTROY_SIZE],
uint8_t reset_key[TROPIC_MAC_AND_DESTROY_SIZE]);
+void tropic_pin_set_time(uint32_t* time_ms);
+
bool tropic_pin_set_kek_masks(
tropic_ui_progress_t ui_progress,
const uint8_t kek[TROPIC_MAC_AND_DESTROY_SIZE],
const uint8_t stretched_pins[PIN_MAX_TRIES][TROPIC_MAC_AND_DESTROY_SIZE]);
+void tropic_pin_set_kek_masks_time(uint32_t* time_ms);
+
bool tropic_pin_unmask_kek(
tropic_ui_progress_t ui_progress, uint16_t pin_index,
const uint8_t stretched_pin[TROPIC_MAC_AND_DESTROY_SIZE],
uint8_t kek[TROPIC_MAC_AND_DESTROY_SIZE]);
-uint32_t tropic_estimate_time_ms(storage_pin_op_t op, uint16_t pin_index);
+void tropic_pin_unmask_kek_time(uint32_t* time_ms);
#endif
diff --git a/core/embed/sec/tropic/tropic.c b/core/embed/sec/tropic/tropic.c
index 04ba6ec7..f710c835 100644
--- a/core/embed/sec/tropic/tropic.c
+++ b/core/embed/sec/tropic/tropic.c
@@ -185,6 +185,12 @@ bool tropic_session_start(void) {
return false;
}
+void tropic_session_start_time(uint32_t *time_ms) {
+ if (!g_tropic_driver.session_started) {
+ *time_ms += 210;
+ }
+}
+
#ifdef TREZOR_EMULATOR
bool tropic_init(uint16_t port) {
#else
@@ -354,6 +360,11 @@ bool tropic_random_buffer(void *buffer, size_t length) {
return true;
}
+void tropic_random_buffer_time(uint32_t *time_ms) {
+ // Assuming the data size is 32 bytes
+ *time_ms += 50;
+}
+
#ifdef USE_STORAGE
static mac_and_destroy_slot_t get_first_mac_and_destroy_slot(
@@ -369,10 +380,22 @@ static uint16_t get_kek_masks_slot(tropic_driver_t *drv) {
: TROPIC_KEK_MASKS_PRIVILEGED_SLOT;
}
+static void lt_mac_and_destroy_time(uint32_t *time_ms) { *time_ms += 51; }
+
+static void lt_r_mem_data_read_time(uint32_t *time_ms) {
+ // Assuming the data size is 320 bytes
+ *time_ms += 100;
+}
+
+static void lt_r_mem_data_write_time(uint32_t *time_ms) {
+ // Assuming the data size is 320 bytes
+ *time_ms += 77;
+}
+
+static void lt_r_mem_data_erase_time(uint32_t *time_ms) { *time_ms += 55; }
+
bool tropic_pin_stretch(tropic_ui_progress_t ui_progress, uint16_t pin_index,
uint8_t stretched_pin[TROPIC_MAC_AND_DESTROY_SIZE]) {
- // Time: 50 ms
-
if (pin_index >= PIN_MAX_TRIES) {
return false;
}
@@ -403,11 +426,13 @@ bool tropic_pin_stretch(tropic_ui_progress_t ui_progress, uint16_t pin_index,
return res == LT_OK;
}
+void tropic_pin_stretch_time(uint32_t *time_ms) {
+ lt_mac_and_destroy_time(time_ms);
+}
+
bool tropic_pin_reset_slots(
tropic_ui_progress_t ui_progress, uint16_t pin_index,
const uint8_t reset_key[TROPIC_MAC_AND_DESTROY_SIZE]) {
- // Time: (pin_index + 1) * 50 ms
-
if (pin_index >= PIN_MAX_TRIES) {
return false;
}
@@ -441,12 +466,16 @@ cleanup:
return res == LT_OK;
}
+void tropic_pin_reset_slots_time(uint32_t *time_ms, uint16_t pin_index) {
+ for (int i = 0; i <= pin_index; i++) {
+ lt_mac_and_destroy_time(time_ms);
+ }
+}
+
bool tropic_pin_set(
tropic_ui_progress_t ui_progress,
uint8_t stretched_pins[PIN_MAX_TRIES][TROPIC_MAC_AND_DESTROY_SIZE],
uint8_t reset_key[TROPIC_MAC_AND_DESTROY_SIZE]) {
- // Time: 65 ms + PIN_MAX_TRIES * 155 ms
-
tropic_driver_t *drv = &g_tropic_driver;
if (!tropic_session_start()) {
@@ -504,12 +533,19 @@ cleanup:
return res == LT_OK;
}
+void tropic_pin_set_time(uint32_t *time_ms) {
+ rng_fill_buffer_strong_time(time_ms);
+ for (int i = 0; i < PIN_MAX_TRIES; i++) {
+ lt_mac_and_destroy_time(time_ms);
+ lt_mac_and_destroy_time(time_ms);
+ lt_mac_and_destroy_time(time_ms);
+ }
+}
+
bool tropic_pin_set_kek_masks(
tropic_ui_progress_t ui_progress,
const uint8_t kek[TROPIC_MAC_AND_DESTROY_SIZE],
const uint8_t stretched_pins[PIN_MAX_TRIES][TROPIC_MAC_AND_DESTROY_SIZE]) {
- // Time: 130 ms
-
tropic_driver_t *drv = &g_tropic_driver;
if (!tropic_session_start()) {
@@ -551,12 +587,15 @@ cleanup:
return ret == LT_OK;
}
+void tropic_pin_set_kek_masks_time(uint32_t *time_ms) {
+ lt_r_mem_data_erase_time(time_ms);
+ lt_r_mem_data_write_time(time_ms);
+}
+
bool tropic_pin_unmask_kek(
tropic_ui_progress_t ui_progress, uint16_t pin_index,
const uint8_t stretched_pin[TROPIC_MAC_AND_DESTROY_SIZE],
uint8_t kek[TROPIC_MAC_AND_DESTROY_SIZE]) {
- // Time: 100 ms
-
tropic_driver_t *drv = &g_tropic_driver;
if (!tropic_session_start()) {
@@ -591,26 +630,8 @@ bool tropic_pin_unmask_kek(
return true;
}
-uint32_t tropic_estimate_time_ms(storage_pin_op_t op, uint16_t pin_index) {
- const int set_time = 65 + PIN_MAX_TRIES * 155;
- const int set_kek_masks_time = 130;
- const int stretch_time = 50;
- const int unmask_kek_time = 100;
- const int reset_slots_time = (pin_index + 1) * 50;
-
- const int pin_verify_time = stretch_time + unmask_kek_time + reset_slots_time;
- const int pin_set_time = set_time + set_kek_masks_time;
-
- switch (op) {
- case STORAGE_PIN_OP_SET:
- return pin_set_time;
- case STORAGE_PIN_OP_VERIFY:
- return pin_verify_time;
- case STORAGE_PIN_OP_CHANGE:
- return pin_set_time + pin_verify_time;
- default:
- return 0;
- }
+void tropic_pin_unmask_kek_time(uint32_t *time_ms) {
+ lt_r_mem_data_read_time(time_ms);
}
#endif // USE_STORAGE
diff --git a/legacy/sec/rng.h b/legacy/sec/rng.h
index 2cd4528b..d92a5d72 100644
--- a/legacy/sec/rng.h
+++ b/legacy/sec/rng.h
@@ -30,6 +30,10 @@ static inline bool rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
return true;
}
+static inline void rng_fill_buffer_strong_time(uint32_t* time) {
+ (void)time; // Suppress unused parameter warning
+}
+
static inline bool rng_fill_buffer(void* buffer, size_t buffer_size) {
random_buffer((uint8_t*)buffer, buffer_size);
return true;
diff --git a/legacy/time_estimate.c b/legacy/time_estimate.c
index d5efed7e..24132c67 100644
--- a/legacy/time_estimate.c
+++ b/legacy/time_estimate.c
@@ -25,11 +25,15 @@
// MCU clock 120 MHz
#define MCU_CLOCK 120000000
+uint32_t time_estimate_clock_cycles_ms(uint32_t cycles) {
+ return cycles / (MCU_CLOCK / 1000);
+}
+
uint32_t time_estimate_pbkdf2_ms(uint32_t iterations) {
#if EMULATOR
(void)iterations;
return 500;
#else
- return PIN_PBKDF2_CYCLES_PER_ITER * iterations / (MCU_CLOCK / 1000);
+ return time_estimate_clock_cycles_ms(PIN_PBKDF2_CYCLES_PER_ITER * iterations);
#endif
}
diff --git a/legacy/time_estimate.h b/legacy/time_estimate.h
index 68baa309..853ce72f 100644
--- a/legacy/time_estimate.h
+++ b/legacy/time_estimate.h
@@ -22,6 +22,7 @@
#include <stdint.h>
+uint32_t time_estimate_clock_cycles_ms(uint32_t cycles);
uint32_t time_estimate_pbkdf2_ms(uint32_t iterations);
#endif
diff --git a/storage/storage.c b/storage/storage.c
index 4eceade6..ba4f7c66 100644
--- a/storage/storage.c
+++ b/storage/storage.c
@@ -474,34 +474,48 @@ static secbool is_not_wipe_code(const uint8_t *pin, size_t pin_len) {
return sectrue;
}
+void set_pin_time(uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms);
+void unlock_time(uint16_t pin_index, uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms);
+
static uint32_t ui_estimate_time_ms(storage_pin_op_t op) {
uint32_t time_ms = 0;
-#if USE_OPTIGA || USE_TROPIC
+
uint32_t pin_index = 0;
#if STRETCHED_PIN_COUNT > 1
- if (sectrue != pin_get_fails(&pin_index)) {
+ if (pin_get_fails(&pin_index) != sectrue) {
return 0;
}
#endif
-#endif
+
+ uint8_t optiga_sec = 0;
+ uint32_t optiga_last_time_decreased_ms = 0;
#if USE_OPTIGA
- time_ms += optiga_estimate_time_ms(op, pin_index);
+ if (!optiga_read_sec(&optiga_sec)) {
+ return 0;
+ }
#endif
+
#if USE_TROPIC
- time_ms += tropic_estimate_time_ms(op, pin_index);
+ tropic_session_start_time(&time_ms);
#endif
- uint32_t pbkdf2_ms = time_estimate_pbkdf2_ms(PIN_ITER_COUNT);
switch (op) {
case STORAGE_PIN_OP_SET:
+ set_pin_time(&time_ms, &optiga_sec, &optiga_last_time_decreased_ms);
+ break;
case STORAGE_PIN_OP_VERIFY:
- time_ms += pbkdf2_ms;
+ unlock_time(pin_index, &time_ms, &optiga_sec,
+ &optiga_last_time_decreased_ms);
break;
case STORAGE_PIN_OP_CHANGE:
- time_ms += 2 * pbkdf2_ms;
+ set_pin_time(&time_ms, &optiga_sec, &optiga_last_time_decreased_ms);
+ unlock_time(pin_index, &time_ms, &optiga_sec,
+ &optiga_last_time_decreased_ms);
break;
default:
- return 1;
+ assert(false);
}
return time_ms;
@@ -512,8 +526,6 @@ static void ui_progress_init(storage_pin_op_t op) {
ui_next_update = 0;
}
-static void ui_progress_add(uint32_t added_ms) { ui_total += added_ms; }
-
static secbool ui_progress(void) {
uint32_t now = hal_ticks_ms();
if (ui_callback == NULL || ui_message == 0 || now < ui_next_update) {
@@ -887,6 +899,38 @@ static secbool set_pin(const uint8_t *pin, size_t pin_len,
return ret;
}
+void set_pin_time(uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {
+ // Suppress unused parameter warnings if USE_OPTIGA is not defined
+ (void)optiga_sec;
+ (void)optiga_last_time_decreased_ms;
+
+ rng_fill_buffer_strong_time(time_ms); // rand_salt
+
+ { // From derive_kek_set()
+ *time_ms += time_estimate_pbkdf2_ms(PIN_ITER_COUNT);
+#if USE_OPTIGA
+ optiga_pin_init_time(time_ms);
+ optiga_pin_stretch_cmac_ecdh_time(time_ms, optiga_sec,
+ optiga_last_time_decreased_ms);
+#endif
+#if USE_TROPIC
+ tropic_pin_set_time(time_ms);
+#endif
+#if USE_OPTIGA
+ optiga_pin_set_time(time_ms, optiga_sec, optiga_last_time_decreased_ms);
+#endif
+#if USE_TROPIC
+ rng_fill_buffer_strong_time(time_ms); // kek
+ tropic_pin_set_kek_masks_time(time_ms);
+#endif
+ }
+
+ // The value was obtained as the difference between the estimated and measured
+ // time on T3W1
+ *time_ms += time_estimate_clock_cycles_ms(2250000);
+}
+
/*
* Initializes the values of VERSION_KEY, EDEK_PVC_KEY, PIN_NOT_SET_KEY and
* PIN_LOGS_KEY using an empty PIN. This function should be called to initialize
@@ -1124,6 +1168,11 @@ static void ensure_not_wipe_code(const uint8_t *pin, size_t pin_len) {
}
}
+static uint32_t get_backoff_time_ms(uint32_t fail_ctr) {
+ // 2 ^ fail_ctr - 1 seconds
+ return 1000 * ((1 << fail_ctr) - 1);
+}
+
static secbool unlock(const uint8_t *pin, size_t pin_len,
const uint8_t *ext_salt) {
const uint8_t *unlock_pin = pin;
@@ -1140,14 +1189,6 @@ static secbool unlock(const uint8_t *pin, size_t pin_len,
}
#endif
-#if NORCOW_MIN_VERSION <= 5
- // In case of an upgrade from version 5 or earlier bump the total time of UI
- // progress to account for the set_pin() call in storage_upgrade_unlocked().
- if (get_lock_version() <= 5) {
- ui_progress_add(ui_estimate_time_ms(STORAGE_PIN_OP_SET));
- }
-#endif
-
// Now we can check for wipe code.
ensure_not_wipe_code(unlock_pin, unlock_pin_len);
@@ -1166,13 +1207,11 @@ static secbool unlock(const uint8_t *pin, size_t pin_len,
return secfalse;
}
- // Sleep for 2^ctr - 1 seconds before checking the PIN.
- uint32_t wait_ms = 1000 * ((1 << ctr) - 1);
- ui_progress_add(wait_ms);
ui_progress();
+ // Sleep before checking the PIN.
uint32_t begin = hal_ticks_ms();
- while (hal_ticks_ms() - begin < wait_ms) {
+ while (hal_ticks_ms() - begin < get_backoff_time_ms(ctr)) {
if (sectrue == ui_progress()) {
memzero(&legacy_pin, sizeof(legacy_pin));
return secfalse;
@@ -1272,6 +1311,57 @@ static secbool unlock(const uint8_t *pin, size_t pin_len,
return pin_fails_reset();
}
+void unlock_time(uint16_t pin_index, uint32_t *time_ms, uint8_t *optiga_sec,
+ uint32_t *optiga_last_time_decreased_ms) {
+ // Suppress unused parameter warnings if USE_OPTIGA is not defined
+ (void)optiga_sec;
+ (void)optiga_last_time_decreased_ms;
+ (void)pin_index;
+
+ uint32_t fail_ctr = 0;
+ (void)pin_get_fails(&fail_ctr);
+ *time_ms += get_backoff_time_ms(fail_ctr);
+
+ *time_ms += time_estimate_pbkdf2_ms(PIN_ITER_COUNT);
+#if USE_OPTIGA
+ optiga_pin_stretch_cmac_ecdh_time(time_ms, optiga_sec,
+ optiga_last_time_decreased_ms);
+#endif
+#if USE_TROPIC
+ tropic_pin_stretch_time(time_ms);
+#endif
+#if USE_OPTIGA
+ optiga_pin_verify_time(pin_index, time_ms, optiga_sec,
+ optiga_last_time_decreased_ms);
+#endif
+#if USE_TROPIC
+ tropic_pin_unmask_kek_time(time_ms);
+#endif
+
+#if NORCOW_MIN_VERSION <= 5
+ // In case of an upgrade from version 5 or earlier bump the total time of UI
+ // progress to account for the set_pin() call in storage_upgrade_unlocked().
+ if (get_lock_version() <= 5) {
+ set_pin_time(time_ms, optiga_sec, optiga_last_time_decreased_ms);
+ }
+#endif
+
+#if USE_OPTIGA && STRETCHED_PIN_COUNT > 1
+ if (pin_index != 0) {
+ optiga_pin_reset_hmac_counter_time(time_ms, optiga_sec,
+ optiga_last_time_decreased_ms);
+ }
+#endif
+
+#if USE_TROPIC
+ tropic_pin_reset_slots_time(time_ms, pin_index);
+#endif
+
+ // The value was obtained as the difference between the estimated and measured
+ // time on T3W1
+ *time_ms += time_estimate_clock_cycles_ms(13500000);
+}
+
secbool storage_unlock(const uint8_t *pin, size_t pin_len,
const uint8_t *ext_salt) {
if (sectrue != initialized || pin == NULL) {
diff --git a/storage/tests/c/sec/rng.h b/storage/tests/c/sec/rng.h
index 61fb4fef..bee1be65 100644
--- a/storage/tests/c/sec/rng.h
+++ b/storage/tests/c/sec/rng.h
@@ -30,6 +30,10 @@ static inline bool rng_fill_buffer_strong(void* buffer, size_t buffer_size) {
return true;
}
+static inline void rng_fill_buffer_strong_time(uint32_t* time) {
+ (void)time; // Suppress unused parameter warning
+}
+
static inline bool rng_fill_buffer(void* buffer, size_t buffer_size) {
random_buffer((uint8_t*)buffer, buffer_size);
return true;
diff --git a/storage/tests/c/time_estimate.h b/storage/tests/c/time_estimate.h
index 2e487b3c..977b4815 100644
--- a/storage/tests/c/time_estimate.h
+++ b/storage/tests/c/time_estimate.h
@@ -27,4 +27,9 @@ uint32_t time_estimate_pbkdf2_ms(uint32_t iterations) {
return 500;
}
+uint32_t time_estimate_clock_cycles_ms(uint32_t cycles) {
+ (void)cycles; // Suppress unused variable warning
+ return 0;
+}
+
#endif
Why this scored 19/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.