Skip to content

Commit 23f192b

Browse files
committed
Merge branch 'reduce-events'
2 parents dea7eff + 212bf39 commit 23f192b

File tree

8 files changed

+180
-133
lines changed

8 files changed

+180
-133
lines changed

src/keystore.c

Lines changed: 130 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ static uint8_t _unstretched_retained_seed_encryption_key[32] = {0};
3737
// Stores the encrypted seed after unlock.
3838
static uint8_t _retained_seed_encrypted[KEYSTORE_MAX_SEED_LENGTH + 64] = {0};
3939
static size_t _retained_seed_encrypted_len = 0;
40+
// A hash of the unencrypted retained seed, used for comparing seeds without knowing their
41+
// plaintext.
42+
static uint8_t _retained_seed_hash[32] = {0};
4043

4144
// Change this ONLY via keystore_unlock_bip39().
4245
static bool _is_unlocked_bip39 = false;
@@ -205,103 +208,47 @@ static keystore_error_t _get_and_decrypt_seed(
205208
}
206209

207210
static bool _verify_seed(
208-
const char* password,
211+
const uint8_t* encryption_key,
209212
const uint8_t* expected_seed,
210213
size_t expected_seed_len)
211214
{
212-
uint8_t decrypted_seed[KEYSTORE_MAX_SEED_LENGTH] = {0};
213-
size_t seed_len;
214-
UTIL_CLEANUP_32(decrypted_seed);
215-
if (_get_and_decrypt_seed(password, decrypted_seed, &seed_len, NULL) != KEYSTORE_OK) {
215+
uint8_t encrypted_seed_and_hmac[96];
216+
UTIL_CLEANUP_32(encrypted_seed_and_hmac);
217+
uint8_t encrypted_len;
218+
if (!memory_get_encrypted_seed_and_hmac(encrypted_seed_and_hmac, &encrypted_len)) {
216219
return false;
217220
}
218-
if (expected_seed_len != seed_len) {
219-
return false;
221+
if (encrypted_len < 49) {
222+
Abort("_verify_seed: underflow / zero size");
220223
}
221-
if (!MEMEQ(expected_seed, decrypted_seed, seed_len)) {
224+
size_t decrypted_len = encrypted_len - 48;
225+
uint8_t decrypted[decrypted_len];
226+
bool password_correct = cipher_aes_hmac_decrypt(
227+
encrypted_seed_and_hmac, encrypted_len, decrypted, &decrypted_len, encryption_key);
228+
if (!password_correct) {
222229
return false;
223230
}
224-
return true;
225-
}
226-
227-
keystore_error_t keystore_encrypt_and_store_seed(
228-
const uint8_t* seed,
229-
size_t seed_length,
230-
const char* password)
231-
{
232-
if (memory_is_initialized()) {
233-
return KEYSTORE_ERR_MEMORY;
234-
}
235-
keystore_lock();
236-
if (!_validate_seed_length(seed_length)) {
237-
return KEYSTORE_ERR_SEED_SIZE;
238-
}
239-
if (securechip_init_new_password(password)) {
240-
return KEYSTORE_ERR_SECURECHIP;
241-
}
242-
uint8_t secret[32] = {0};
243-
UTIL_CLEANUP_32(secret);
244-
if (securechip_stretch_password(password, secret)) {
245-
return KEYSTORE_ERR_SECURECHIP;
246-
}
247-
248-
size_t encrypted_seed_len = seed_length + 64;
249-
uint8_t encrypted_seed[encrypted_seed_len];
250-
UTIL_CLEANUP_32(encrypted_seed);
251-
if (!cipher_aes_hmac_encrypt(seed, seed_length, encrypted_seed, &encrypted_seed_len, secret)) {
252-
return KEYSTORE_ERR_ENCRYPT;
253-
}
254-
if (encrypted_seed_len > 255) { // sanity check, can't happen
255-
Abort("keystore_encrypt_and_store_seed");
256-
}
257-
uint8_t encrypted_seed_len_u8 = (uint8_t)encrypted_seed_len;
258-
if (!memory_set_encrypted_seed_and_hmac(encrypted_seed, encrypted_seed_len_u8)) {
259-
return KEYSTORE_ERR_MEMORY;
231+
if (expected_seed_len != decrypted_len) {
232+
util_zero(decrypted, sizeof(decrypted));
233+
return false;
260234
}
261-
if (!_verify_seed(password, seed, seed_length)) {
262-
if (!memory_reset_hww()) {
263-
return KEYSTORE_ERR_MEMORY;
264-
}
265-
return KEYSTORE_ERR_MEMORY;
235+
if (!MEMEQ(expected_seed, decrypted, expected_seed_len)) {
236+
util_zero(decrypted, sizeof(decrypted));
237+
return false;
266238
}
267-
return KEYSTORE_OK;
239+
util_zero(decrypted, sizeof(decrypted));
240+
return true;
268241
}
269242

270-
keystore_error_t keystore_create_and_store_seed(
271-
const char* password,
272-
const uint8_t* host_entropy,
273-
size_t host_entropy_size)
243+
static keystore_error_t _hash_seed(const uint8_t* seed, size_t seed_len, uint8_t* out)
274244
{
275-
if (host_entropy_size != 16 && host_entropy_size != 32) {
276-
return KEYSTORE_ERR_SEED_SIZE;
277-
}
278-
if (KEYSTORE_MAX_SEED_LENGTH != RANDOM_NUM_SIZE) {
279-
Abort("keystore create: size mismatch");
280-
}
281-
uint8_t seed[KEYSTORE_MAX_SEED_LENGTH];
282-
UTIL_CLEANUP_32(seed);
283-
random_32_bytes(seed);
284-
285-
// Mix in Host entropy.
286-
for (size_t i = 0; i < host_entropy_size; i++) {
287-
seed[i] ^= host_entropy[i];
288-
}
289-
290-
// Mix in entropy derived from the user password.
291-
uint8_t password_salted_hashed[KEYSTORE_MAX_SEED_LENGTH] = {0};
292-
UTIL_CLEANUP_32(password_salted_hashed);
293-
if (!salt_hash_data(
294-
(const uint8_t*)password,
295-
strlen(password),
296-
"keystore_seed_generation",
297-
password_salted_hashed)) {
245+
uint8_t salted_key[32] = {0};
246+
if (!salt_hash_data(NULL, 0, "keystore_retain_seed_hash", salted_key)) {
298247
return KEYSTORE_ERR_SALT;
299248
}
300249

301-
for (size_t i = 0; i < host_entropy_size; i++) {
302-
seed[i] ^= password_salted_hashed[i];
303-
}
304-
return keystore_encrypt_and_store_seed(seed, host_entropy_size, password);
250+
rust_hmac_sha256(salted_key, sizeof(salted_key), seed, seed_len, out);
251+
return KEYSTORE_OK;
305252
}
306253

307254
USE_RESULT static keystore_error_t _retain_seed(const uint8_t* seed, size_t seed_len)
@@ -333,7 +280,8 @@ USE_RESULT static keystore_error_t _retain_seed(const uint8_t* seed, size_t seed
333280
return KEYSTORE_ERR_ENCRYPT;
334281
}
335282
_retained_seed_encrypted_len = len;
336-
return KEYSTORE_OK;
283+
284+
return _hash_seed(seed, seed_len, _retained_seed_hash);
337285
}
338286

339287
USE_RESULT static bool _retain_bip39_seed(const uint8_t* bip39_seed)
@@ -378,13 +326,102 @@ static void _delete_retained_seeds(void)
378326
sizeof(_unstretched_retained_seed_encryption_key));
379327
util_zero(_retained_seed_encrypted, sizeof(_retained_seed_encrypted));
380328
_retained_seed_encrypted_len = 0;
329+
util_zero(_retained_seed_hash, sizeof(_retained_seed_hash));
330+
381331
util_zero(
382332
_unstretched_retained_bip39_seed_encryption_key,
383333
sizeof(_unstretched_retained_seed_encryption_key));
384334
util_zero(_retained_bip39_seed_encrypted, sizeof(_retained_bip39_seed_encrypted));
385335
_retained_bip39_seed_encrypted_len = 0;
386336
}
387337

338+
keystore_error_t keystore_encrypt_and_store_seed(
339+
const uint8_t* seed,
340+
size_t seed_length,
341+
const char* password)
342+
{
343+
if (memory_is_initialized()) {
344+
return KEYSTORE_ERR_MEMORY;
345+
}
346+
keystore_lock();
347+
if (!_validate_seed_length(seed_length)) {
348+
return KEYSTORE_ERR_SEED_SIZE;
349+
}
350+
if (securechip_init_new_password(password)) {
351+
return KEYSTORE_ERR_SECURECHIP;
352+
}
353+
uint8_t secret[32] = {0};
354+
UTIL_CLEANUP_32(secret);
355+
if (securechip_stretch_password(password, secret)) {
356+
return KEYSTORE_ERR_SECURECHIP;
357+
}
358+
359+
size_t encrypted_seed_len = seed_length + 64;
360+
uint8_t encrypted_seed[encrypted_seed_len];
361+
UTIL_CLEANUP_32(encrypted_seed);
362+
if (!cipher_aes_hmac_encrypt(seed, seed_length, encrypted_seed, &encrypted_seed_len, secret)) {
363+
return KEYSTORE_ERR_ENCRYPT;
364+
}
365+
if (encrypted_seed_len > 255) { // sanity check, can't happen
366+
Abort("keystore_encrypt_and_store_seed");
367+
}
368+
uint8_t encrypted_seed_len_u8 = (uint8_t)encrypted_seed_len;
369+
if (!memory_set_encrypted_seed_and_hmac(encrypted_seed, encrypted_seed_len_u8)) {
370+
return KEYSTORE_ERR_MEMORY;
371+
}
372+
if (!_verify_seed(secret, seed, seed_length)) {
373+
if (!memory_reset_hww()) {
374+
return KEYSTORE_ERR_MEMORY;
375+
}
376+
return KEYSTORE_ERR_MEMORY;
377+
}
378+
379+
keystore_error_t retain_seed_result = _retain_seed(seed, seed_length);
380+
if (retain_seed_result != KEYSTORE_OK) {
381+
return retain_seed_result;
382+
}
383+
_is_unlocked_device = true;
384+
385+
return KEYSTORE_OK;
386+
}
387+
388+
keystore_error_t keystore_create_and_store_seed(
389+
const char* password,
390+
const uint8_t* host_entropy,
391+
size_t host_entropy_size)
392+
{
393+
if (host_entropy_size != 16 && host_entropy_size != 32) {
394+
return KEYSTORE_ERR_SEED_SIZE;
395+
}
396+
if (KEYSTORE_MAX_SEED_LENGTH != RANDOM_NUM_SIZE) {
397+
Abort("keystore create: size mismatch");
398+
}
399+
uint8_t seed[KEYSTORE_MAX_SEED_LENGTH];
400+
UTIL_CLEANUP_32(seed);
401+
random_32_bytes(seed);
402+
403+
// Mix in Host entropy.
404+
for (size_t i = 0; i < host_entropy_size; i++) {
405+
seed[i] ^= host_entropy[i];
406+
}
407+
408+
// Mix in entropy derived from the user password.
409+
uint8_t password_salted_hashed[KEYSTORE_MAX_SEED_LENGTH] = {0};
410+
UTIL_CLEANUP_32(password_salted_hashed);
411+
if (!salt_hash_data(
412+
(const uint8_t*)password,
413+
strlen(password),
414+
"keystore_seed_generation",
415+
password_salted_hashed)) {
416+
return KEYSTORE_ERR_SALT;
417+
}
418+
419+
for (size_t i = 0; i < host_entropy_size; i++) {
420+
seed[i] ^= password_salted_hashed[i];
421+
}
422+
return keystore_encrypt_and_store_seed(seed, host_entropy_size, password);
423+
}
424+
388425
keystore_error_t keystore_unlock(
389426
const char* password,
390427
uint8_t* remaining_attempts_out,
@@ -448,17 +485,23 @@ keystore_error_t keystore_unlock(
448485
return result;
449486
}
450487

451-
bool keystore_unlock_bip39(const char* mnemonic_passphrase, uint8_t* root_fingerprint_out)
488+
bool keystore_unlock_bip39(
489+
const uint8_t* seed,
490+
size_t seed_length,
491+
const char* mnemonic_passphrase,
492+
uint8_t* root_fingerprint_out)
452493
{
453494
if (!_is_unlocked_device) {
454495
return false;
455496
}
456497
usb_processing_timeout_reset(LONG_TIMEOUT);
457498

458-
uint8_t seed[KEYSTORE_MAX_SEED_LENGTH] = {0};
459-
UTIL_CLEANUP_32(seed);
460-
size_t seed_length = 0;
461-
if (!keystore_copy_seed(seed, &seed_length)) {
499+
uint8_t seed_hashed[32] = {0};
500+
UTIL_CLEANUP_32(seed_hashed);
501+
if (_hash_seed(seed, seed_length, seed_hashed) != KEYSTORE_OK) {
502+
return false;
503+
}
504+
if (!MEMEQ(seed_hashed, _retained_seed_hash, sizeof(_retained_seed_hash))) {
462505
return false;
463506
}
464507

src/keystore.h

Lines changed: 8 additions & 2 deletions
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
*/
@@ -103,14 +104,19 @@ USE_RESULT keystore_error_t keystore_create_and_store_seed(
103104
USE_RESULT keystore_error_t
104105
keystore_unlock(const char* password, uint8_t* remaining_attempts_out, int* securechip_result_out);
105106

106-
/** Unlocks the bip39 seed.
107+
/** Unlocks the bip39 seed. The input seed must be the keystore seed (i.e. must match the output
108+
* of `keystore_copy_seed()`).
109+
* @param[in] seed the input seed to BIP39.
110+
* @param[in] seed_length the size of the seed
107111
* @param[in] mnemonic_passphrase bip39 passphrase used in the derivation. Use the
108112
* empty string if no passphrase is needed or provided.
109113
* @param[out] root_fingerprint_out must be 4 bytes long and will contain the root fingerprint of
110114
* the wallet.
111115
* @return returns false if there was a critital memory error, otherwise true.
112116
*/
113117
USE_RESULT bool keystore_unlock_bip39(
118+
const uint8_t* seed,
119+
size_t seed_length,
114120
const char* mnemonic_passphrase,
115121
uint8_t* root_fingerprint_out);
116122

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

Lines changed: 15 additions & 13 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

@@ -66,7 +65,8 @@ pub async fn from_file(
6665
}
6766

6867
let password = password::enter_twice(hal).await?;
69-
if let Err(err) = bitbox02::keystore::encrypt_and_store_seed(data.get_seed(), &password) {
68+
let seed = data.get_seed();
69+
if let Err(err) = bitbox02::keystore::encrypt_and_store_seed(seed, &password) {
7070
hal.ui()
7171
.status(&format!("Could not\nrestore backup\n{:?}", err), false)
7272
.await;
@@ -84,14 +84,11 @@ pub async fn from_file(
8484
}
8585

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

9188
// Ignore non-critical error.
9289
let _ = bitbox02::memory::set_device_name(&metadata.name);
9390

94-
unlock::unlock_bip39(hal).await;
91+
unlock::unlock_bip39(hal, seed).await;
9592
Ok(Response::Success(pb::Success {}))
9693
}
9794

@@ -160,12 +157,8 @@ pub async fn from_mnemonic(
160157
}
161158

162159
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-
};
167160

168-
unlock::unlock_bip39(hal).await;
161+
unlock::unlock_bip39(hal, &seed).await;
169162
Ok(Response::Success(pb::Success {}))
170163
}
171164

@@ -207,11 +200,20 @@ mod tests {
207200
)),
208201
Ok(Response::Success(pb::Success {}))
209202
);
210-
assert_eq!(bitbox02::securechip::fake_event_counter(), 19);
203+
assert_eq!(bitbox02::securechip::fake_event_counter(), 8);
211204
drop(mock_hal); // to remove mutable borrow of counter
212205
assert_eq!(counter, 2);
213206
assert!(!keystore::is_locked());
214207
assert!(memory::is_initialized());
215-
assert!(keystore::copy_seed().unwrap().len() == 32);
208+
// Seed of hardcoded phrase used in unit tests:
209+
// boring mistake dish oyster truth pigeon viable emerge sort crash wire portion cannon couple enact box walk height pull today solid off enable tide
210+
assert_eq!(
211+
hex::encode(keystore::copy_seed().unwrap()),
212+
"19f1bcfccf3e9d497cd245cf864ff0d42216625258d4f68d56b571aceb329257"
213+
);
214+
assert_eq!(
215+
hex::encode(keystore::copy_bip39_seed().unwrap()),
216+
"257724bccc8858cfe565b456b01263a4a6a45184fab4531f5c199649207a74e74c399a01d4f957258c05cee818369b31404c884a4b7a29ff6886bae6700fb56a"
217+
);
216218
}
217219
}

0 commit comments

Comments
 (0)