Skip to content

Commit 47e9aff

Browse files
committed
Merge branch 'hal-sc'
2 parents 4d05a15 + b0a8953 commit 47e9aff

File tree

7 files changed

+128
-37
lines changed

7 files changed

+128
-37
lines changed

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

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,20 @@ pub trait Random {
3838
fn random_32_bytes(&mut self) -> Box<zeroize::Zeroizing<[u8; 32]>>;
3939
}
4040

41+
pub trait SecureChip {
42+
fn init_new_password(&mut self, password: &str) -> Result<(), bitbox02::securechip::Error>;
43+
fn stretch_password(
44+
&mut self,
45+
password: &str,
46+
) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error>;
47+
}
48+
4149
/// Hardware abstraction layer for BitBox devices.
4250
pub trait Hal {
4351
fn ui(&mut self) -> &mut impl Ui;
4452
fn sd(&mut self) -> &mut impl Sd;
4553
fn random(&mut self) -> &mut impl Random;
54+
fn securechip(&mut self) -> &mut impl SecureChip;
4655
}
4756

4857
pub struct BitBox02Sd;
@@ -97,10 +106,26 @@ impl Random for BitBox02Random {
97106
}
98107
}
99108

109+
pub struct BitBox02SecureChip;
110+
111+
impl SecureChip for BitBox02SecureChip {
112+
fn init_new_password(&mut self, password: &str) -> Result<(), bitbox02::securechip::Error> {
113+
bitbox02::securechip::init_new_password(password)
114+
}
115+
116+
fn stretch_password(
117+
&mut self,
118+
password: &str,
119+
) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error> {
120+
bitbox02::securechip::stretch_password(password)
121+
}
122+
}
123+
100124
pub struct BitBox02Hal {
101125
ui: RealWorkflows,
102126
sd: BitBox02Sd,
103127
random: BitBox02Random,
128+
securechip: BitBox02SecureChip,
104129
}
105130

106131
impl BitBox02Hal {
@@ -109,6 +134,7 @@ impl BitBox02Hal {
109134
ui: crate::workflow::RealWorkflows,
110135
sd: BitBox02Sd,
111136
random: BitBox02Random,
137+
securechip: BitBox02SecureChip,
112138
}
113139
}
114140
}
@@ -123,6 +149,9 @@ impl Hal for BitBox02Hal {
123149
fn random(&mut self) -> &mut impl Random {
124150
&mut self.random
125151
}
152+
fn securechip(&mut self) -> &mut impl SecureChip {
153+
&mut self.securechip
154+
}
126155
}
127156

128157
#[cfg(feature = "testing")]
@@ -223,10 +252,62 @@ pub mod testing {
223252
}
224253
}
225254

255+
pub struct TestingSecureChip {
256+
// Count how man seceurity events happen. The numbers were obtained by reading the security
257+
// event counter slot (0xE0C5) on a real device. We can use this to assert how many events
258+
// were used in unit tests. The number is relevant due to Optiga's throttling mechanism.
259+
event_counter: u32,
260+
}
261+
262+
impl TestingSecureChip {
263+
pub fn new() -> Self {
264+
TestingSecureChip { event_counter: 0 }
265+
}
266+
267+
/// Resets the event counter.
268+
pub fn event_counter_reset(&mut self) {
269+
self.event_counter = 0;
270+
// TODO: remove once all unit tests use the SecureChip HAL.
271+
bitbox02::securechip::fake_event_counter_reset()
272+
}
273+
274+
/// Retrieves the event counter.
275+
pub fn get_event_counter(&self) -> u32 {
276+
// TODO: remove fake_event_counter() once all unit tests use the SecureChip HAL.
277+
bitbox02::securechip::fake_event_counter() + self.event_counter
278+
}
279+
}
280+
281+
impl super::SecureChip for TestingSecureChip {
282+
fn init_new_password(
283+
&mut self,
284+
_password: &str,
285+
) -> Result<(), bitbox02::securechip::Error> {
286+
self.event_counter += 1;
287+
Ok(())
288+
}
289+
290+
fn stretch_password(
291+
&mut self,
292+
password: &str,
293+
) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error> {
294+
self.event_counter += 5;
295+
296+
use bitcoin::hashes::{HashEngine, Hmac, HmacEngine, sha256};
297+
let mut engine = HmacEngine::<sha256::Hash>::new(b"unit-test");
298+
engine.input(password.as_bytes());
299+
let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
300+
Ok(zeroize::Zeroizing::new(
301+
hmac_result.to_byte_array().to_vec(),
302+
))
303+
}
304+
}
305+
226306
pub struct TestingHal<'a> {
227307
pub ui: crate::workflow::testing::TestingWorkflows<'a>,
228308
pub sd: TestingSd,
229309
pub random: TestingRandom,
310+
pub securechip: TestingSecureChip,
230311
}
231312

232313
impl TestingHal<'_> {
@@ -235,6 +316,7 @@ pub mod testing {
235316
ui: crate::workflow::testing::TestingWorkflows::new(),
236317
sd: TestingSd::new(),
237318
random: TestingRandom::new(),
319+
securechip: TestingSecureChip::new(),
238320
}
239321
}
240322
}
@@ -249,6 +331,9 @@ pub mod testing {
249331
fn random(&mut self) -> &mut impl super::Random {
250332
&mut self.random
251333
}
334+
fn securechip(&mut self) -> &mut impl super::SecureChip {
335+
&mut self.securechip
336+
}
252337
}
253338

254339
#[cfg(test)]

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ mod tests {
187187

188188
let mut mock_hal = TestingHal::new();
189189
mock_hal.sd.inserted = Some(true);
190-
bitbox02::securechip::fake_event_counter_reset();
190+
mock_hal.securechip.event_counter_reset();
191191
assert_eq!(
192192
block_on(create(
193193
&mut mock_hal,
@@ -198,7 +198,7 @@ mod tests {
198198
)),
199199
Ok(Response::Success(pb::Success {}))
200200
);
201-
assert_eq!(bitbox02::securechip::fake_event_counter(), 1);
201+
assert_eq!(mock_hal.securechip.get_event_counter(), 1);
202202
assert_eq!(EXPECTED_TIMESTMAP, bitbox02::memory::get_seed_birthdate());
203203
assert_eq!(
204204
mock_hal.ui.screens,
@@ -246,7 +246,7 @@ mod tests {
246246
password_entered = true;
247247
Ok("password".into())
248248
}));
249-
bitbox02::securechip::fake_event_counter_reset();
249+
mock_hal.securechip.event_counter_reset();
250250
assert_eq!(
251251
block_on(create(
252252
&mut mock_hal,
@@ -257,7 +257,7 @@ mod tests {
257257
)),
258258
Ok(Response::Success(pb::Success {}))
259259
);
260-
assert_eq!(bitbox02::securechip::fake_event_counter(), 5);
260+
assert_eq!(mock_hal.securechip.get_event_counter(), 5);
261261
assert_eq!(
262262
mock_hal.ui.screens,
263263
vec![

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ mod tests {
189189
Ok("password".into())
190190
}));
191191

192-
bitbox02::securechip::fake_event_counter_reset();
192+
mock_hal.securechip.event_counter_reset();
193193
assert_eq!(
194194
block_on(from_mnemonic(
195195
&mut mock_hal,
@@ -200,7 +200,7 @@ mod tests {
200200
)),
201201
Ok(Response::Success(pb::Success {}))
202202
);
203-
assert_eq!(bitbox02::securechip::fake_event_counter(), 8);
203+
assert_eq!(mock_hal.securechip.get_event_counter(), 8);
204204
drop(mock_hal); // to remove mutable borrow of counter
205205
assert_eq!(counter, 2);
206206
assert!(!crate::keystore::is_locked());

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ mod tests {
7070
Ok("password".into())
7171
}));
7272

73-
bitbox02::securechip::fake_event_counter_reset();
73+
mock_hal.securechip.event_counter_reset();
7474
assert_eq!(
7575
block_on(process(
7676
&mut mock_hal,
@@ -80,7 +80,7 @@ mod tests {
8080
)),
8181
Ok(Response::Success(pb::Success {}))
8282
);
83-
assert_eq!(bitbox02::securechip::fake_event_counter(), 9);
83+
assert_eq!(mock_hal.securechip.get_event_counter(), 9);
8484
drop(mock_hal); // to remove mutable borrow of counter
8585
assert_eq!(counter, 2);
8686
assert!(!keystore::is_locked());

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,13 @@ mod tests {
9797
panic!("unexpected call to enter password")
9898
}));
9999

100-
bitbox02::securechip::fake_event_counter_reset();
100+
mock_hal.securechip.event_counter_reset();
101101
assert_eq!(
102102
block_on(process(&mut mock_hal)),
103103
Ok(Response::Success(pb::Success {}))
104104
);
105105
// 1 operation for one copy_seed() to get the seed to display it.
106-
assert_eq!(bitbox02::securechip::fake_event_counter(), 1);
106+
assert_eq!(mock_hal.securechip.get_event_counter(), 1);
107107

108108
assert_eq!(
109109
mock_hal.ui.screens,
@@ -151,12 +151,12 @@ mod tests {
151151
Ok("password".into())
152152
}));
153153

154-
bitbox02::securechip::fake_event_counter_reset();
154+
mock_hal.securechip.event_counter_reset();
155155
assert_eq!(
156156
block_on(process(&mut mock_hal)),
157157
Ok(Response::Success(pb::Success {}))
158158
);
159-
assert_eq!(bitbox02::securechip::fake_event_counter(), 5);
159+
assert_eq!(mock_hal.securechip.get_event_counter(), 5);
160160

161161
assert_eq!(
162162
mock_hal.ui.screens,
@@ -206,9 +206,9 @@ mod tests {
206206
.ui
207207
.set_enter_string(Box::new(|_params| Ok("wrong password".into())));
208208

209-
bitbox02::securechip::fake_event_counter_reset();
209+
mock_hal.securechip.event_counter_reset();
210210
assert_eq!(block_on(process(&mut mock_hal)), Err(Error::Generic));
211-
assert_eq!(bitbox02::securechip::fake_event_counter(), 5);
211+
assert_eq!(mock_hal.securechip.get_event_counter(), 5);
212212

213213
assert_eq!(
214214
mock_hal.ui.screens,

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

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use alloc::string::String;
1919
use alloc::vec::Vec;
2020

2121
use crate::bip32;
22-
use crate::hal::Random;
22+
use crate::hal::{Random, SecureChip};
2323
pub use bitbox02::keystore::SignResult;
2424
use bitbox02::{keystore, securechip};
2525

@@ -211,9 +211,10 @@ pub fn encrypt_and_store_seed(
211211

212212
bitbox02::usb_processing::timeout_reset(LONG_TIMEOUT);
213213

214-
securechip::init_new_password(password)?;
214+
hal.securechip().init_new_password(password)?;
215+
216+
let secret = hal.securechip().stretch_password(password)?;
215217

216-
let secret = securechip::stretch_password(password)?;
217218
let iv_rand = hal.random().random_32_bytes();
218219
let iv: &[u8; 16] = iv_rand.first_chunk::<16>().unwrap();
219220
let encrypted = bitbox_aes::encrypt_with_hmac(iv, &secret, seed);
@@ -243,13 +244,16 @@ fn check_retained_seed(seed: &[u8]) -> Result<(), ()> {
243244
Ok(())
244245
}
245246

246-
fn get_and_decrypt_seed(password: &str) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
247+
fn get_and_decrypt_seed(
248+
hal: &mut impl crate::hal::Hal,
249+
password: &str,
250+
) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
247251
let encrypted = bitbox02::memory::get_encrypted_seed_and_hmac().map_err(|_| Error::Memory)?;
248252
// Our Optiga securechip implementation fails password stretching if the password is
249253
// wrong, so it already returns an error here. The ATECC stretches the password without checking
250254
// if the password is correct, and we determine if it is correct in the seed decryption
251255
// step below.
252-
let secret = securechip::stretch_password(password)?;
256+
let secret = hal.securechip().stretch_password(password)?;
253257
let seed = match bitbox_aes::decrypt_with_hmac(&secret, &encrypted) {
254258
Ok(seed) => seed,
255259
Err(()) => return Err(Error::IncorrectPassword),
@@ -279,7 +283,7 @@ pub fn unlock(
279283
}
280284
bitbox02::usb_processing::timeout_reset(LONG_TIMEOUT);
281285
bitbox02::memory::smarteeprom_increment_unlock_attempts();
282-
let seed = match get_and_decrypt_seed(password) {
286+
let seed = match get_and_decrypt_seed(hal, password) {
283287
Ok(seed) => seed,
284288
err @ Err(_) => {
285289
if get_remaining_unlock_attempts() == 0 {
@@ -892,17 +896,17 @@ mod tests {
892896
));
893897

894898
// First call: unlock. The first one does a seed rentention (1 securechip event).
895-
bitbox02::securechip::fake_event_counter_reset();
899+
mock_hal.securechip.event_counter_reset();
896900
assert_eq!(unlock(&mut mock_hal, "password").unwrap().as_slice(), seed);
897-
assert_eq!(bitbox02::securechip::fake_event_counter(), 6);
901+
assert_eq!(mock_hal.securechip.get_event_counter(), 6);
898902

899903
// Loop to check that unlocking works while unlocked.
900904
for _ in 0..2 {
901905
// Further calls perform a password check.The password check does not do the retention
902906
// so it ends up needing one secure chip operation less.
903-
bitbox02::securechip::fake_event_counter_reset();
907+
mock_hal.securechip.event_counter_reset();
904908
assert_eq!(unlock(&mut mock_hal, "password").unwrap().as_slice(), seed);
905-
assert_eq!(bitbox02::securechip::fake_event_counter(), 5);
909+
assert_eq!(mock_hal.securechip.get_event_counter(), 5);
906910
}
907911

908912
// Also check that the retained seed was encrypted with the expected encryption key.
@@ -1510,33 +1514,35 @@ mod tests {
15101514
lock();
15111515
let seed = &seed[..test.seed_len];
15121516

1517+
let mut mock_hal = crate::hal::testing::TestingHal::new();
1518+
15131519
assert!(
15141520
block_on(unlock_bip39(
1515-
&mut crate::hal::testing::TestingRandom::new(),
1521+
&mut mock_hal.random,
15161522
seed,
15171523
test.mnemonic_passphrase,
15181524
async || {}
15191525
))
15201526
.is_err()
15211527
);
15221528

1523-
bitbox02::securechip::fake_event_counter_reset();
1524-
assert!(encrypt_and_store_seed(&mut TestingHal::new(), seed, "foo").is_ok());
1525-
assert_eq!(bitbox02::securechip::fake_event_counter(), 7);
1529+
mock_hal.securechip.event_counter_reset();
1530+
assert!(encrypt_and_store_seed(&mut mock_hal, seed, "foo").is_ok());
1531+
assert_eq!(mock_hal.securechip.get_event_counter(), 7);
15261532

15271533
assert!(is_locked());
15281534

1529-
bitbox02::securechip::fake_event_counter_reset();
1535+
mock_hal.securechip.event_counter_reset();
15301536
assert!(
15311537
block_on(unlock_bip39(
1532-
&mut crate::hal::testing::TestingRandom::new(),
1538+
&mut mock_hal.random,
15331539
seed,
15341540
test.mnemonic_passphrase,
15351541
async || {}
15361542
))
15371543
.is_ok()
15381544
);
1539-
assert_eq!(bitbox02::securechip::fake_event_counter(), 1);
1545+
assert_eq!(mock_hal.securechip.get_event_counter(), 1);
15401546

15411547
assert!(!is_locked());
15421548
assert_eq!(
@@ -1545,9 +1551,9 @@ mod tests {
15451551
);
15461552
let keypath = &[44 + HARDENED, 0 + HARDENED, 0 + HARDENED];
15471553

1548-
bitbox02::securechip::fake_event_counter_reset();
1554+
mock_hal.securechip.event_counter_reset();
15491555
let xpub = get_xpub_once(keypath).unwrap();
1550-
assert_eq!(bitbox02::securechip::fake_event_counter(), 1);
1556+
assert_eq!(mock_hal.securechip.get_event_counter(), 1);
15511557

15521558
assert_eq!(
15531559
xpub.serialize_str(crate::bip32::XPubType::Xpub).unwrap(),

0 commit comments

Comments
 (0)