@@ -40,6 +40,10 @@ const ENCRYPTION_OVERHEAD: usize = 64;
4040// to roughly seven seconds so we don't assume communication was lost mid-unlock.
4141const LONG_TIMEOUT : i16 = -70 ;
4242
43+ // Must match MAX_UNLOCK_ATTEMPTS in bitbox02_smarteeprom.h and
44+ // SMALL_MONOTONIC_COUNTER_MAX_USE in optiga.c.
45+ const MAX_UNLOCK_ATTEMPTS : u8 = 10 ;
46+
4347#[ derive( Debug ) ]
4448pub enum Error {
4549 InvalidState ,
@@ -337,7 +341,7 @@ pub async fn unlock(
337341 if !hal. memory ( ) . is_seeded ( ) {
338342 return Err ( Error :: Unseeded ) ;
339343 }
340- if get_remaining_unlock_attempts ( ) == 0 {
344+ if get_remaining_unlock_attempts ( hal ) == 0 {
341345 //
342346 // We reset the device as soon as the MAX_UNLOCK_ATTEMPTSth attempt
343347 // is made. So we should never enter this branch...
@@ -347,11 +351,11 @@ pub async fn unlock(
347351 return Err ( Error :: MaxAttemptsExceeded ) ;
348352 }
349353 bitbox02:: usb_processing:: timeout_reset ( LONG_TIMEOUT ) ;
350- bitbox02 :: memory:: smarteeprom_increment_unlock_attempts ( ) ;
354+ hal . memory ( ) . increment_unlock_attempts ( ) ;
351355 let seed = match get_and_decrypt_seed ( hal, password) {
352356 Ok ( seed) => seed,
353357 err @ Err ( _) => {
354- if get_remaining_unlock_attempts ( ) == 0 {
358+ if get_remaining_unlock_attempts ( hal ) == 0 {
355359 crate :: reset:: reset ( hal, false ) . await ;
356360 return Err ( Error :: MaxAttemptsExceeded ) ;
357361 }
@@ -367,15 +371,15 @@ pub async fn unlock(
367371 } else {
368372 retain_seed ( hal, & seed) ?;
369373 }
370- bitbox02 :: memory:: smarteeprom_reset_unlock_attempts ( ) ;
374+ hal . memory ( ) . reset_unlock_attempts ( ) ;
371375 Ok ( seed)
372376}
373377
374378/// Returns the number of remaining unlock attempts (calls to `unlock()`) that are allowed before
375379/// the device resets itself.
376- pub fn get_remaining_unlock_attempts ( ) -> u8 {
377- let failed_attempts: u8 = bitbox02 :: memory:: smarteeprom_get_unlock_attempts ( ) ;
378- bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS . saturating_sub ( failed_attempts)
380+ pub fn get_remaining_unlock_attempts ( hal : & mut impl crate :: hal :: Hal ) -> u8 {
381+ let failed_attempts: u8 = hal . memory ( ) . get_unlock_attempts ( ) ;
382+ MAX_UNLOCK_ATTEMPTS . saturating_sub ( failed_attempts)
379383}
380384
381385/// Unlocks the bip39 seed. The input seed must be the keystore seed (i.e. must match the output
@@ -1173,14 +1177,14 @@ mod tests {
11731177 assert_eq ! ( decrypted. as_slice( ) , seed. as_slice( ) ) ;
11741178
11751179 // First 9 wrong attempts.
1176- for i in 1 ..bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS {
1180+ for i in 1 ..MAX_UNLOCK_ATTEMPTS {
11771181 assert ! ( matches!(
11781182 block_on( unlock( & mut mock_hal, "invalid password" ) ) ,
11791183 Err ( Error :: IncorrectPassword )
11801184 ) ) ;
11811185 assert_eq ! (
1182- get_remaining_unlock_attempts( ) ,
1183- bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS - i
1186+ get_remaining_unlock_attempts( & mut mock_hal ) ,
1187+ MAX_UNLOCK_ATTEMPTS - i
11841188 ) ;
11851189 // Still seeded.
11861190 assert ! ( mock_hal. memory. is_seeded( ) ) ;
@@ -1218,15 +1222,15 @@ mod tests {
12181222 assert ! ( is_locked( ) ) ;
12191223 assert ! ( copy_seed( & mut mock_hal) . is_err( ) ) ;
12201224
1221- for attempt in 1 ..bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS {
1225+ for attempt in 1 ..MAX_UNLOCK_ATTEMPTS {
12221226 assert ! ( matches!(
12231227 block_on( unlock( & mut mock_hal, "invalid password" ) ) ,
12241228 Err ( Error :: IncorrectPassword ) ,
12251229 ) ) ;
12261230
12271231 assert_eq ! (
1228- get_remaining_unlock_attempts( ) ,
1229- bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS - attempt
1232+ get_remaining_unlock_attempts( & mut mock_hal ) ,
1233+ MAX_UNLOCK_ATTEMPTS - attempt
12301234 ) ;
12311235 assert ! ( is_locked( ) ) ;
12321236 assert ! ( copy_seed( & mut mock_hal) . is_err( ) ) ;
@@ -1265,12 +1269,11 @@ mod tests {
12651269 lock ( ) ;
12661270 assert ! ( is_locked( ) ) ;
12671271
1268- bitbox02:: memory:: set_unlock_attempts_for_testing ( bitbox02:: memory:: MAX_UNLOCK_ATTEMPTS ) ;
1269- assert_eq ! ( get_remaining_unlock_attempts( ) , 0 ) ;
1270- assert_eq ! (
1271- bitbox02:: memory:: smarteeprom_get_unlock_attempts( ) ,
1272- bitbox02:: memory:: MAX_UNLOCK_ATTEMPTS
1273- ) ;
1272+ let max = MAX_UNLOCK_ATTEMPTS ;
1273+ mock_hal. memory . set_unlock_attempts_for_testing ( max) ;
1274+
1275+ assert_eq ! ( get_remaining_unlock_attempts( & mut mock_hal) , 0 ) ;
1276+ assert_eq ! ( mock_hal. memory. get_unlock_attempts( ) , MAX_UNLOCK_ATTEMPTS ) ;
12741277
12751278 assert ! ( matches!(
12761279 block_on( unlock( & mut mock_hal, "password" ) ) ,
@@ -1303,10 +1306,7 @@ mod tests {
13031306 block_on( unlock( hal, "wrong" ) ) ,
13041307 Err ( Error :: IncorrectPassword )
13051308 ) ) ;
1306- assert_eq ! (
1307- get_remaining_unlock_attempts( ) ,
1308- bitbox02:: memory:: MAX_UNLOCK_ATTEMPTS - 1
1309- ) ;
1309+ assert_eq ! ( get_remaining_unlock_attempts( hal) , MAX_UNLOCK_ATTEMPTS - 1 ) ;
13101310 }
13111311
13121312 wrong_attempt ( & mut mock_hal) ;
@@ -1358,10 +1358,7 @@ mod tests {
13581358 block_on( unlock( hal, "wrong" ) ) ,
13591359 Err ( Error :: IncorrectPassword )
13601360 ) ) ;
1361- assert_eq ! (
1362- get_remaining_unlock_attempts( ) ,
1363- bitbox02:: memory:: MAX_UNLOCK_ATTEMPTS - 1
1364- ) ;
1361+ assert_eq ! ( get_remaining_unlock_attempts( hal) , MAX_UNLOCK_ATTEMPTS - 1 ) ;
13651362 }
13661363
13671364 wrong_attempt ( & mut mock_hal) ;
@@ -2049,7 +2046,7 @@ mod tests {
20492046 block_on( unlock( & mut mock_hal, "bar" ) ) ,
20502047 Err ( Error :: IncorrectPassword )
20512048 ) ) ;
2052- assert_eq ! ( get_remaining_unlock_attempts( ) , 9 ) ;
2049+ assert_eq ! ( get_remaining_unlock_attempts( & mut mock_hal ) , 9 ) ;
20532050
20542051 // Correct password. First time: unlock. After unlock, it becomes a password check.
20552052 for _ in 0 ..3 {
0 commit comments