Skip to content

Commit a4d8f41

Browse files
committed
keystore: fewer securechip calls when checking password
The sanity check to see if the seed has changed does not need a securechip operation, it can use the retained seed hash instead, same as `unlock_bip39()`. This reduces the number of securechip operations needed to do a password check, which reduces the risk of running into the Optiga throttling security mechanism.
1 parent b94372e commit a4d8f41

File tree

3 files changed

+29
-20
lines changed

3 files changed

+29
-20
lines changed

src/keystore.c

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,23 @@ keystore_error_t keystore_create_and_store_seed(
422422
return keystore_encrypt_and_store_seed(seed, host_entropy_size, password);
423423
}
424424

425+
// Checks if the retained seed matches the passed seed.
426+
static bool _check_retained_seed(const uint8_t* seed, size_t seed_length)
427+
{
428+
if (!_is_unlocked_device) {
429+
return false;
430+
}
431+
uint8_t seed_hashed[32] = {0};
432+
UTIL_CLEANUP_32(seed_hashed);
433+
if (_hash_seed(seed, seed_length, seed_hashed) != KEYSTORE_OK) {
434+
return false;
435+
}
436+
if (!MEMEQ(seed_hashed, _retained_seed_hash, sizeof(_retained_seed_hash))) {
437+
return false;
438+
}
439+
return true;
440+
}
441+
425442
keystore_error_t keystore_unlock(
426443
const char* password,
427444
uint8_t* remaining_attempts_out,
@@ -455,12 +472,7 @@ keystore_error_t keystore_unlock(
455472
if (result == KEYSTORE_OK) {
456473
if (_is_unlocked_device) {
457474
// Already unlocked. Fail if the seed changed under our feet (should never happen).
458-
uint8_t current_seed[KEYSTORE_MAX_SEED_LENGTH] = {0};
459-
size_t current_seed_len = 0;
460-
if (!keystore_copy_seed(current_seed, &current_seed_len)) {
461-
return KEYSTORE_ERR_DECRYPT;
462-
}
463-
if (seed_len != current_seed_len || !MEMEQ(current_seed, seed, current_seed_len)) {
475+
if (!_check_retained_seed(seed, seed_len)) {
464476
Abort("Seed has suddenly changed. This should never happen.");
465477
}
466478
} else {
@@ -491,15 +503,9 @@ bool keystore_unlock_bip39_check(const uint8_t* seed, size_t seed_length)
491503
return false;
492504
}
493505

494-
uint8_t seed_hashed[32] = {0};
495-
UTIL_CLEANUP_32(seed_hashed);
496-
if (_hash_seed(seed, seed_length, seed_hashed) != KEYSTORE_OK) {
506+
if (!_check_retained_seed(seed, seed_length)) {
497507
return false;
498508
}
499-
if (!MEMEQ(seed_hashed, _retained_seed_hash, sizeof(_retained_seed_hash))) {
500-
return false;
501-
}
502-
503509
usb_processing_timeout_reset(LONG_TIMEOUT);
504510

505511
return true;

src/rust/bitbox02-rust/src/hww/api/show_mnemonic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ mod tests {
149149
block_on(process(&mut mock_hal)),
150150
Ok(Response::Success(pb::Success {}))
151151
);
152-
assert_eq!(bitbox02::securechip::fake_event_counter(), 7);
152+
assert_eq!(bitbox02::securechip::fake_event_counter(), 6);
153153

154154
assert_eq!(
155155
mock_hal.ui.screens,

src/rust/bitbox02/src/keystore.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -466,15 +466,18 @@ mod tests {
466466
assert!(encrypt_and_store_seed(&seed, "password").is_ok());
467467
lock();
468468

469+
// First call: unlock. The first one does a seed rentention (1 securechip event).
470+
crate::securechip::fake_event_counter_reset();
471+
assert!(unlock("password").is_ok());
472+
assert_eq!(crate::securechip::fake_event_counter(), 6);
473+
469474
// Loop to check that unlocking works while unlocked.
470-
for _ in 0..3 {
471-
// First call: unlock Further calls perform a password check. The first onedoes a seed
472-
// rentention (1 securechip event). The password check does not do the rentention but a
473-
// copy_seed() instead to check the seed, so they end up hacing the same number of
474-
// events.crate::securechip::fake_event_counter_reset();
475+
for _ in 0..2 {
476+
// Further calls perform a password check.The password check does not do the retention
477+
// so it ends up needing one secure chip operation less.
475478
crate::securechip::fake_event_counter_reset();
476479
assert!(unlock("password").is_ok());
477-
assert_eq!(crate::securechip::fake_event_counter(), 6);
480+
assert_eq!(crate::securechip::fake_event_counter(), 5);
478481
}
479482

480483
// Also check that the retained seed was encrypted with the expected encryption key.

0 commit comments

Comments
 (0)