Skip to content

Commit d0db51a

Browse files
committed
Move salt_root functions to HAL
Centralizes salt root access behind the HAL abstraction, enabling full test isolation without C memory dependencies. TestingHal initializes with a default salt root to preserve existing test behavior without manual setup in each test. The 2 rust_salt_hash_data tests keep using mock_memory() + C-backed salt root because the extern "C" shim internally constructs BitBox02Hal—these tests verify the FFI path works.
1 parent 278da3a commit d0db51a

File tree

3 files changed

+65
-37
lines changed

3 files changed

+65
-37
lines changed

src/rust/bitbox02-rust/src/hal.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub trait Memory {
7878
fn get_unlock_attempts(&mut self) -> u8;
7979
fn increment_unlock_attempts(&mut self);
8080
fn reset_unlock_attempts(&mut self);
81+
fn get_salt_root(&mut self) -> Result<zeroize::Zeroizing<Vec<u8>>, ()>;
8182
}
8283

8384
/// Hardware abstraction layer for BitBox devices.
@@ -256,6 +257,10 @@ impl Memory for BitBox02Memory {
256257
fn reset_unlock_attempts(&mut self) {
257258
bitbox02::memory::smarteeprom_reset_unlock_attempts()
258259
}
260+
261+
fn get_salt_root(&mut self) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
262+
bitbox02::memory::get_salt_root()
263+
}
259264
}
260265

261266
pub struct BitBox02Hal {
@@ -417,6 +422,7 @@ pub mod testing {
417422
encrypted_seed_and_hmac: Option<Vec<u8>>,
418423
device_name: Option<String>,
419424
unlock_attempts: u8,
425+
salt_root: [u8; 32],
420426
}
421427

422428
impl TestingSecureChip {
@@ -537,6 +543,7 @@ pub mod testing {
537543
encrypted_seed_and_hmac: None,
538544
device_name: None,
539545
unlock_attempts: 0,
546+
salt_root: *b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
540547
}
541548
}
542549

@@ -551,6 +558,10 @@ pub mod testing {
551558
pub fn set_unlock_attempts_for_testing(&mut self, attempts: u8) {
552559
self.unlock_attempts = attempts;
553560
}
561+
562+
pub fn set_salt_root(&mut self, salt_root: &[u8; 32]) {
563+
self.salt_root = *salt_root;
564+
}
554565
}
555566

556567
impl super::Memory for TestingMemory {
@@ -639,6 +650,14 @@ pub mod testing {
639650
fn reset_unlock_attempts(&mut self) {
640651
self.unlock_attempts = 0;
641652
}
653+
654+
fn get_salt_root(&mut self) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
655+
if self.salt_root.iter().all(|&b| b == 0xff) {
656+
Err(())
657+
} else {
658+
Ok(zeroize::Zeroizing::new(self.salt_root.to_vec()))
659+
}
660+
}
642661
}
643662

644663
pub struct TestingHal<'a> {

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

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,9 @@ fn verify_seed(
192192
decrypted.as_slice() == expected_seed
193193
}
194194

195-
fn hash_seed(seed: &[u8]) -> Result<[u8; 32], Error> {
195+
fn hash_seed(hal: &mut impl crate::hal::Hal, seed: &[u8]) -> Result<[u8; 32], Error> {
196196
let salted_key =
197-
crate::salt::hash_data(&[], "keystore_retain_seed_hash").map_err(|_| Error::Salt)?;
197+
crate::salt::hash_data(hal, &[], "keystore_retain_seed_hash").map_err(|_| Error::Salt)?;
198198

199199
let mut engine = HmacEngine::<sha256::Hash>::new(salted_key.as_slice());
200200
engine.input(seed);
@@ -207,7 +207,7 @@ fn retain_seed(hal: &mut impl crate::hal::Hal, seed: &[u8]) -> Result<(), Error>
207207
seed,
208208
"keystore_retained_seed_access",
209209
)?));
210-
RETAINED_SEED_HASH.write(Some(hash_seed(seed)?));
210+
RETAINED_SEED_HASH.write(Some(hash_seed(hal, seed)?));
211211
Ok(())
212212
}
213213

@@ -300,11 +300,11 @@ pub fn re_encrypt_seed(
300300
}
301301

302302
// Checks if the retained seed matches the passed seed.
303-
fn check_retained_seed(seed: &[u8]) -> Result<(), ()> {
303+
fn check_retained_seed(hal: &mut impl crate::hal::Hal, seed: &[u8]) -> Result<(), ()> {
304304
if RETAINED_SEED.read().is_none() {
305305
return Err(());
306306
}
307-
if hash_seed(seed).map_err(|_| ())? != RETAINED_SEED_HASH.read().ok_or(())? {
307+
if hash_seed(hal, seed).map_err(|_| ())? != RETAINED_SEED_HASH.read().ok_or(())? {
308308
return Err(());
309309
}
310310
Ok(())
@@ -365,7 +365,7 @@ pub async fn unlock(
365365

366366
if RETAINED_SEED.read().is_some() {
367367
// Already unlocked. Fail if the seed changed under our feet (should never happen).
368-
if check_retained_seed(&seed).is_err() {
368+
if check_retained_seed(hal, &seed).is_err() {
369369
panic!("Seed has suddenly changed. This should never happen.");
370370
}
371371
} else {
@@ -392,7 +392,7 @@ pub async fn unlock_bip39(
392392
mnemonic_passphrase: &str,
393393
yield_now: impl AsyncFn(),
394394
) -> Result<(), Error> {
395-
check_retained_seed(seed).map_err(|_| Error::CannotUnlockBIP39)?;
395+
check_retained_seed(hal, seed).map_err(|_| Error::CannotUnlockBIP39)?;
396396

397397
let (bip39_seed, root_fingerprint) =
398398
crate::bip39::derive_seed(seed, mnemonic_passphrase, &yield_now).await;
@@ -449,7 +449,7 @@ pub fn create_and_store_seed(
449449

450450
// Mix in entropy derived from the user password.
451451
let password_salted_hashed =
452-
crate::salt::hash_data(password.as_bytes(), "keystore_seed_generation")
452+
crate::salt::hash_data(hal, password.as_bytes(), "keystore_seed_generation")
453453
.map_err(|_| Error::Salt)?;
454454

455455
for (i, &hash_byte) in password_salted_hashed.iter().take(seed_len).enumerate() {
@@ -585,12 +585,13 @@ pub fn stretch_retained_seed_encryption_key(
585585
purpose_in: &str,
586586
purpose_out: &str,
587587
) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
588-
let salted_in = crate::salt::hash_data(encryption_key, purpose_in).map_err(|_| Error::Salt)?;
588+
let salted_in =
589+
crate::salt::hash_data(hal, encryption_key, purpose_in).map_err(|_| Error::Salt)?;
589590

590591
let kdf = hal.securechip().kdf(salted_in.as_slice())?;
591592

592593
let salted_out =
593-
crate::salt::hash_data(encryption_key, purpose_out).map_err(|_| Error::Salt)?;
594+
crate::salt::hash_data(hal, encryption_key, purpose_out).map_err(|_| Error::Salt)?;
594595

595596
let mut engine = HmacEngine::<sha256::Hash>::new(salted_out.as_slice());
596597
engine.input(kdf.as_slice());
@@ -902,12 +903,9 @@ mod tests {
902903
.collect();
903904

904905
for size in [16, 32] {
905-
mock_memory();
906-
bitbox02::random::fake_reset();
907-
bitbox02::memory::set_salt_root(&mock_salt_root).unwrap();
908-
lock();
909-
910906
let mut hal = TestingHal::new();
907+
hal.memory.set_salt_root(&mock_salt_root);
908+
911909
hal.random.mock_next(seed_random);
912910
assert!(create_and_store_seed(&mut hal, "password", &host_entropy[..size]).is_ok());
913911
assert_eq!(
@@ -1130,7 +1128,7 @@ mod tests {
11301128

11311129
let mock_salt_root =
11321130
hex!("3333333333333333444444444444444411111111111111112222222222222222");
1133-
bitbox02::memory::set_salt_root(&mock_salt_root).unwrap();
1131+
mock_hal.memory.set_salt_root(&mock_salt_root);
11341132

11351133
assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
11361134
lock();
@@ -1215,7 +1213,7 @@ mod tests {
12151213
let seed = hex!("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044");
12161214
let mock_salt_root =
12171215
hex!("3333333333333333444444444444444411111111111111112222222222222222");
1218-
bitbox02::memory::set_salt_root(&mock_salt_root).unwrap();
1216+
mock_hal.memory.set_salt_root(&mock_salt_root);
12191217

12201218
assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
12211219
lock();
@@ -1263,7 +1261,7 @@ mod tests {
12631261
let seed = hex!("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044");
12641262
let mock_salt_root =
12651263
hex!("3333333333333333444444444444444411111111111111112222222222222222");
1266-
bitbox02::memory::set_salt_root(&mock_salt_root).unwrap();
1264+
mock_hal.memory.set_salt_root(&mock_salt_root);
12671265

12681266
assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
12691267
lock();
@@ -1297,7 +1295,7 @@ mod tests {
12971295
let seed = hex!("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044");
12981296
let mock_salt_root =
12991297
hex!("3333333333333333444444444444444411111111111111112222222222222222");
1300-
bitbox02::memory::set_salt_root(&mock_salt_root).unwrap();
1298+
mock_hal.memory.set_salt_root(&mock_salt_root);
13011299

13021300
assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
13031301
lock();
@@ -1341,7 +1339,7 @@ mod tests {
13411339
let seed = hex!("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044");
13421340
let mock_salt_root =
13431341
hex!("3333333333333333444444444444444411111111111111112222222222222222");
1344-
bitbox02::memory::set_salt_root(&mock_salt_root).unwrap();
1342+
mock_hal.memory.set_salt_root(&mock_salt_root);
13451343

13461344
assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
13471345
lock();
@@ -1382,28 +1380,29 @@ mod tests {
13821380
fn test_unlock_bip39() {
13831381
mock_memory();
13841382
lock();
1383+
let mut mock_hal = TestingHal::new();
13851384

13861385
let seed = hex!("1111111111111111222222222222222233333333333333334444444444444444");
13871386

13881387
let mock_salt_root =
13891388
hex!("3333333333333333444444444444444411111111111111112222222222222222");
1390-
bitbox02::memory::set_salt_root(&mock_salt_root).unwrap();
1389+
mock_hal.memory.set_salt_root(&mock_salt_root);
13911390

13921391
assert!(root_fingerprint().is_err());
1393-
assert!(encrypt_and_store_seed(&mut TestingHal::new(), &seed, "password").is_ok());
1392+
assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
13941393
assert!(root_fingerprint().is_err());
13951394
// Incorrect seed passed
13961395
assert!(
13971396
block_on(unlock_bip39(
1398-
&mut TestingHal::new(),
1397+
&mut mock_hal,
13991398
b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
14001399
"foo",
14011400
async || {}
14021401
))
14031402
.is_err()
14041403
);
14051404
// Correct seed passed.
1406-
let mut mock_hal = TestingHal::new();
1405+
14071406
// Mock random value used for creating the unstretched bip39 seed encryption key.
14081407
mock_hal.random.mock_next(hex!(
14091408
"9b44c7048893faaf6e2d7625d13d8f1cab0765fd61f159d9713e08155d06717c"
@@ -1640,14 +1639,15 @@ mod tests {
16401639
#[test]
16411640
fn test_stretch_retained_seed_encryption_key_success() {
16421641
mock_memory();
1642+
let mut mock_hal = TestingHal::new();
16431643
let salt_root = hex!("0000000000000000111111111111111122222222222222223333333333333333");
1644-
bitbox02::memory::set_salt_root(&salt_root).unwrap();
1644+
mock_hal.memory.set_salt_root(&salt_root);
16451645

16461646
let encryption_key =
16471647
hex!("00112233445566778899aabbccddeeff112233445566778899aabbccddeeff00");
16481648

16491649
let stretched = stretch_retained_seed_encryption_key(
1650-
&mut TestingHal::new(),
1650+
&mut mock_hal,
16511651
&encryption_key,
16521652
"keystore_retained_seed_access_in",
16531653
"keystore_retained_seed_access_out",
@@ -1661,11 +1661,12 @@ mod tests {
16611661
#[test]
16621662
fn test_stretch_retained_seed_encryption_key_salt_error() {
16631663
mock_memory();
1664-
bitbox02::memory::set_salt_root(&[0xffu8; 32]).unwrap();
1664+
let mut mock_hal = TestingHal::new();
1665+
mock_hal.memory.set_salt_root(&[0xffu8; 32]);
16651666

16661667
let encryption_key = [0u8; 32];
16671668
let result = stretch_retained_seed_encryption_key(
1668-
&mut TestingHal::new(),
1669+
&mut mock_hal,
16691670
&encryption_key,
16701671
"purpose_in",
16711672
"purpose_out",

src/rust/bitbox02-rust/src/salt.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use alloc::vec::Vec;
1616
use core::ffi::c_char;
1717

18-
use bitbox02::memory;
18+
use crate::hal::Memory;
1919
use sha2::Digest;
2020
use util::bytes::{Bytes, BytesMut};
2121
use zeroize::Zeroizing;
@@ -25,8 +25,12 @@ use zeroize::Zeroizing;
2525
/// the salt, and the provided `data` slice is hashed alongside it.
2626
///
2727
/// Returns `Err(())` if the salt root cannot be retrieved from persistent storage.
28-
pub fn hash_data(data: &[u8], purpose: &str) -> Result<Zeroizing<Vec<u8>>, ()> {
29-
let salt_root = memory::get_salt_root()?;
28+
pub fn hash_data(
29+
hal: &mut impl crate::hal::Hal,
30+
data: &[u8],
31+
purpose: &str,
32+
) -> Result<Zeroizing<Vec<u8>>, ()> {
33+
let salt_root = hal.memory().get_salt_root()?;
3034

3135
let mut hasher = sha2::Sha256::new();
3236
hasher.update(salt_root.as_slice());
@@ -49,7 +53,8 @@ pub unsafe extern "C" fn rust_salt_hash_data(
4953
Ok(purpose) => purpose,
5054
Err(()) => return false,
5155
};
52-
match hash_data(data.as_ref(), purpose_str) {
56+
let mut hal = crate::hal::BitBox02Hal::new();
57+
match hash_data(&mut hal, data.as_ref(), purpose_str) {
5358
Ok(hash) => {
5459
hash_out.as_mut()[..32].copy_from_slice(&hash);
5560
true
@@ -61,7 +66,8 @@ pub unsafe extern "C" fn rust_salt_hash_data(
6166
#[cfg(test)]
6267
mod tests {
6368
use super::*;
64-
use bitbox02::testing::mock_memory;
69+
use crate::hal::testing::TestingHal;
70+
use bitbox02::{memory, testing::mock_memory};
6571
use core::convert::TryInto;
6672
use core::ptr;
6773
use hex_lit::hex;
@@ -72,23 +78,25 @@ mod tests {
7278
#[test]
7379
fn test_hash_data() {
7480
mock_memory();
75-
memory::set_salt_root(&MOCK_SALT_ROOT).unwrap();
81+
let mut mock_hal = TestingHal::new();
82+
mock_hal.memory.set_salt_root(&MOCK_SALT_ROOT);
7683

7784
let data = hex!("001122334455667788");
7885
let expected = hex!("62db8dcd47ddf8e81809c377ed96643855d3052bb73237100ca81f0f5a7611e6");
7986

80-
let hash = hash_data(&data, "test purpose").unwrap();
87+
let hash = hash_data(&mut mock_hal, &data, "test purpose").unwrap();
8188
assert_eq!(hash.as_slice(), &expected);
8289
}
8390

8491
#[test]
8592
fn test_hash_data_empty_inputs() {
8693
mock_memory();
87-
memory::set_salt_root(&MOCK_SALT_ROOT).unwrap();
94+
let mut mock_hal = TestingHal::new();
95+
mock_hal.memory.set_salt_root(&MOCK_SALT_ROOT);
8896

8997
let expected = hex!("2dbb05dd73d94edba6946611aaca367f76c809e96f20499ad674e596050f9833");
9098

91-
let hash = hash_data(&[], "").unwrap();
99+
let hash = hash_data(&mut mock_hal, &[], "").unwrap();
92100
assert_eq!(hash.as_slice(), &expected);
93101
}
94102

0 commit comments

Comments
 (0)