Skip to content

Commit f8c1614

Browse files
committed
reduce number of secure chip security events when creating/restoring
Before, `keystore_encrypt_and_store_seed()` (called when creating/restoring a seed) would always be followed by `keystore_unlock(<password>)` with the password the user just chose, so unlock could never fail. The unlocking part costs many secure chip operations (for stretching the password). By making the first function already unlock the keystore, we can avoid calling `keystore_unlock()`, reducing the number of secure chip events by 5. This effort is part of mitigating Optiga's throttling mechanism that kicks in after 133 events - users can run into this by repeatedly resetting/restoring).
1 parent b3e384f commit f8c1614

File tree

6 files changed

+20
-30
lines changed

6 files changed

+20
-30
lines changed

src/keystore.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,13 @@ keystore_error_t keystore_encrypt_and_store_seed(
345345
}
346346
return KEYSTORE_ERR_MEMORY;
347347
}
348+
349+
keystore_error_t retain_seed_result = _retain_seed(seed, seed_length);
350+
if (retain_seed_result != KEYSTORE_OK) {
351+
return retain_seed_result;
352+
}
353+
_is_unlocked_device = true;
354+
348355
return KEYSTORE_OK;
349356
}
350357

src/keystore.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ USE_RESULT bool keystore_copy_seed(uint8_t* seed_out, size_t* length_out);
6363
USE_RESULT bool keystore_copy_bip39_seed(uint8_t* bip32_seed_out);
6464

6565
/**
66-
* Restores a seed.
66+
* Restores a seed. This also unlocks the keystore with this seed.
6767
* @param[in] seed The seed that is to be restored.
6868
* @param[in] seed_length The length of the seed (max. 32 bytes).
6969
* @param[in] password The password with which we encrypt the seed.
@@ -75,6 +75,7 @@ keystore_encrypt_and_store_seed(const uint8_t* seed, size_t seed_length, const c
7575
Generates the seed, mixes it with host_entropy, and stores it encrypted with the
7676
password. The size of the host entropy determines the size of the seed. Can be either 16 or 32
7777
bytes, resulting in 12 or 24 BIP39 recovery words.
78+
This also unlocks the keystore with the new seed.
7879
@param[in] host_entropy bytes of entropy to be mixed in.
7980
@param[in] host_entropy_size must be 16 or 32.
8081
*/

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use crate::pb;
1717

1818
use pb::response::Response;
1919

20-
use crate::general::abort;
2120
use crate::hal::Ui;
2221
use crate::workflow::{confirm, mnemonic, password, unlock};
2322

@@ -84,9 +83,6 @@ pub async fn from_file(
8483
}
8584

8685
bitbox02::memory::set_initialized().or(Err(Error::Memory))?;
87-
if bitbox02::keystore::unlock(&password).is_err() {
88-
abort("restore_from_file: unlock failed");
89-
};
9086

9187
// Ignore non-critical error.
9288
let _ = bitbox02::memory::set_device_name(&metadata.name);
@@ -160,10 +156,6 @@ pub async fn from_mnemonic(
160156
}
161157

162158
bitbox02::memory::set_initialized().or(Err(Error::Memory))?;
163-
// This should never fail.
164-
if bitbox02::keystore::unlock(&password).is_err() {
165-
abort("restore_from_mnemonic: unlock failed");
166-
};
167159

168160
unlock::unlock_bip39(hal).await;
169161
Ok(Response::Success(pb::Success {}))
@@ -207,7 +199,7 @@ mod tests {
207199
)),
208200
Ok(Response::Success(pb::Success {}))
209201
);
210-
assert_eq!(bitbox02::securechip::fake_event_counter(), 19);
202+
assert_eq!(bitbox02::securechip::fake_event_counter(), 14);
211203
drop(mock_hal); // to remove mutable borrow of counter
212204
assert_eq!(counter, 2);
213205
assert!(!keystore::is_locked());

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ pub async fn process(
4040
hal.ui().status(&format!("Error\n{:?}", err), false).await;
4141
return Err(Error::Generic);
4242
}
43-
if keystore::unlock(&password).is_err() {
44-
panic!("Unexpected error during restore: unlock failed.");
45-
}
4643
unlock::unlock_bip39(hal).await;
4744
Ok(Response::Success(pb::Success {}))
4845
}
@@ -83,7 +80,7 @@ mod tests {
8380
)),
8481
Ok(Response::Success(pb::Success {}))
8582
);
86-
assert_eq!(bitbox02::securechip::fake_event_counter(), 19);
83+
assert_eq!(bitbox02::securechip::fake_event_counter(), 14);
8784
drop(mock_hal); // to remove mutable borrow of counter
8885
assert_eq!(counter, 2);
8986
assert!(!keystore::is_locked());

src/rust/bitbox02-rust/src/keystore.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -577,14 +577,7 @@ mod tests {
577577

578578
bitbox02::securechip::fake_event_counter_reset();
579579
assert!(keystore::encrypt_and_store_seed(seed, "foo").is_ok());
580-
assert_eq!(bitbox02::securechip::fake_event_counter(), 11);
581-
582-
assert!(keystore::unlock_bip39(test.mnemonic_passphrase).is_err());
583-
assert!(keystore::is_locked());
584-
585-
bitbox02::securechip::fake_event_counter_reset();
586-
assert!(keystore::unlock("foo").is_ok());
587-
assert_eq!(bitbox02::securechip::fake_event_counter(), 6);
580+
assert_eq!(bitbox02::securechip::fake_event_counter(), 12);
588581

589582
assert!(keystore::is_locked());
590583

src/rust/bitbox02/src/keystore.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,6 @@ mod tests {
408408
let seed = hex::decode("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044")
409409
.unwrap();
410410
assert!(encrypt_and_store_seed(&seed, "password").is_ok());
411-
assert!(unlock("password").is_ok());
412411
assert!(is_locked()); // still locked, it is only unlocked after unlock_bip39.
413412
assert!(unlock_bip39("foo").is_ok());
414413
assert!(!is_locked());
@@ -432,6 +431,7 @@ mod tests {
432431
crate::memory::set_salt_root(mock_salt_root.as_slice().try_into().unwrap()).unwrap();
433432

434433
assert!(encrypt_and_store_seed(&seed, "password").is_ok());
434+
lock();
435435

436436
// Loop to check that unlocking works while unlocked.
437437
for _ in 0..3 {
@@ -497,7 +497,6 @@ mod tests {
497497

498498
assert!(root_fingerprint().is_err());
499499
assert!(encrypt_and_store_seed(&seed, "password").is_ok());
500-
assert!(unlock("password").is_ok());
501500
assert!(root_fingerprint().is_err());
502501
assert!(unlock_bip39("foo").is_ok());
503502
assert_eq!(root_fingerprint(), Ok(vec![0xf1, 0xbc, 0x3c, 0x46]),);
@@ -572,7 +571,6 @@ mod tests {
572571
lock();
573572

574573
assert!(create_and_store_seed("password", &host_entropy[..size]).is_ok());
575-
assert!(unlock("password").is_ok());
576574
assert_eq!(copy_seed().unwrap().as_slice(), &expected_seed[..size]);
577575
// Check the seed has been stored encrypted with the expected encryption key.
578576
// Decrypt and check seed.
@@ -603,14 +601,12 @@ mod tests {
603601
let seed2 = hex::decode("c28135734876aff9ccf4f1d60df8d19a0a38fd02085883f65fc608eb769a635d")
604602
.unwrap();
605603
assert!(encrypt_and_store_seed(&seed, "password").is_ok());
606-
assert!(unlock("password").is_ok());
607604
// Create new (different) seed.
608605
assert!(encrypt_and_store_seed(&seed2, "password").is_ok());
609-
assert!(unlock("password").is_ok());
610606
assert_eq!(copy_seed().unwrap().as_slice(), &seed2);
611607
}
612608

613-
// Functional test to store seeds, unlock, retrieve seed.
609+
// Functional test to store seeds, lock/unlock, retrieve seed.
614610
#[test]
615611
fn test_seeds() {
616612
let seed = hex::decode("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044")
@@ -624,6 +620,12 @@ mod tests {
624620
for _ in 0..2 {
625621
assert!(encrypt_and_store_seed(&seed[..seed_size], "foo").is_ok());
626622
}
623+
// Also unlocks, so we can get the retained seed.
624+
assert_eq!(copy_seed().unwrap().as_slice(), &seed[..seed_size]);
625+
626+
lock();
627+
// Can't get seed before unlock.
628+
assert!(copy_seed().is_err());
627629

628630
// Wrong password.
629631
assert!(matches!(
@@ -633,8 +635,6 @@ mod tests {
633635
})
634636
));
635637

636-
// Can't get seed before unlock.
637-
assert!(copy_seed().is_err());
638638
// Correct password. First time: unlock. After unlock, it becomes a password check.
639639
for _ in 0..3 {
640640
assert!(unlock("foo").is_ok());

0 commit comments

Comments
 (0)