hal: move Testing subsystems to new bitbox-platform-host crate
What changed, and why it matters
This commit is a straightforward code reorganization: it moves fake/test-only implementations of hardware subsystems (EEPROM, memory, SD card, secure chip) from one internal Rust crate into a new reusable crate named bitbox-platform-host. The code itself is almost identical to what was removed, just relocated and renamed. There is no change to the real device firmware or to any security-sensitive behavior visible to users.
No security action required. Treat as normal code-review for a refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change extracts the TestingEeprom, TestingMemory, TestingSd, and TestingSecureChip mocks out of bitbox02-rust/src/hal/testing into a new workspace crate bitbox-platform-host, renaming them FakeEeprom, FakeMemory, FakeSd, and FakeSecureChip. The new crate is added as an optional dependency of bitbox02-rust under the testing feature. The implementations are preserved byte-for-byte, including test cases and feature-gated U2F code. This is purely a refactor to allow simulators to reuse the same host-platform mocks.
Changed components
src/rust/bitbox02-rust/src/hal/testing.rssrc/rust/bitbox-platform-host/src/eeprom.rssrc/rust/bitbox-platform-host/src/memory.rssrc/rust/bitbox-platform-host/src/sd.rssrc/rust/bitbox-platform-host/src/securechip.rsInspect captured patch +718 / −677
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index edf1dbf..60a6b0a 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -165,6 +165,17 @@ dependencies = [
"cmake",
]
+[[package]]
+name = "bitbox-platform-host"
+version = "0.1.0"
+dependencies = [
+ "bitbox-hal",
+ "bitcoin",
+ "hex_lit",
+ "util",
+ "zeroize",
+]
+
[[package]]
name = "bitbox-secp256k1"
version = "0.1.0"
@@ -228,6 +239,7 @@ dependencies = [
"bitbox-da14531",
"bitbox-executor",
"bitbox-hal",
+ "bitbox-platform-host",
"bitbox-secp256k1",
"bitbox-u2fhid",
"bitbox-usb-report-queue",
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index 4492324..dec8477 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -9,6 +9,7 @@ members = [
"bitbox-usb-report-queue",
"bitbox-bytequeue",
"bitbox-da14531",
+ "bitbox-platform-host",
"bitbox-hal",
"bitbox-framed-serial-link",
"util",
diff --git a/src/rust/bitbox-platform-host/Cargo.toml b/src/rust/bitbox-platform-host/Cargo.toml
new file mode 100644
index 0000000..0c59eae
--- /dev/null
+++ b/src/rust/bitbox-platform-host/Cargo.toml
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: Apache-2.0
+
+[package]
+name = "bitbox-platform-host"
+version = "0.1.0"
+authors = ["Shift Crypto AG <support@bitbox.swiss>"]
+edition = "2024"
+license = "Apache-2.0"
+
+[dependencies]
+bitbox-hal = { path = "../bitbox-hal" }
+bitcoin = { workspace = true }
+hex_lit = { workspace = true, features = ["rust_v_1_46"] }
+zeroize = { workspace = true }
+
+[features]
+app-u2f = ["bitbox-hal/app-u2f"]
+
+[dev-dependencies]
+util = { path = "../util", features = ["testing"] }
diff --git a/src/rust/bitbox-platform-host/src/eeprom.rs b/src/rust/bitbox-platform-host/src/eeprom.rs
new file mode 100644
index 0000000..d831bfe
--- /dev/null
+++ b/src/rust/bitbox-platform-host/src/eeprom.rs
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pub struct FakeEeprom {
+ pub enabled: bool,
+ unlock_attempts: u8,
+}
+
+impl FakeEeprom {
+ pub fn new() -> Self {
+ Self {
+ enabled: true,
+ unlock_attempts: 0,
+ }
+ }
+
+ pub fn set_unlock_attempts_for_testing(&mut self, attempts: u8) {
+ self.unlock_attempts = attempts;
+ }
+}
+
+impl bitbox_hal::Eeprom for FakeEeprom {
+ fn setup(&mut self) {
+ self.enabled = true;
+ self.unlock_attempts = 0;
+ }
+
+ fn init(&mut self) {}
+
+ fn is_enabled(&mut self) -> bool {
+ self.enabled
+ }
+
+ fn disable(&mut self) {
+ self.enabled = false;
+ }
+
+ fn get_unlock_attempts(&mut self) -> u8 {
+ self.unlock_attempts
+ }
+
+ fn increment_unlock_attempts(&mut self) {
+ self.unlock_attempts += 1;
+ }
+
+ fn reset_unlock_attempts(&mut self) {
+ self.unlock_attempts = 0;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use bitbox_hal::Eeprom;
+
+ #[test]
+ fn test_disable() {
+ let mut eeprom = FakeEeprom::new();
+ assert!(eeprom.enabled);
+ eeprom.disable();
+ assert!(!eeprom.enabled);
+ eeprom.setup();
+ assert!(eeprom.enabled);
+ }
+
+ #[test]
+ fn test_unlock_attempts() {
+ let mut eeprom = FakeEeprom::new();
+ assert_eq!(eeprom.get_unlock_attempts(), 0);
+ eeprom.increment_unlock_attempts();
+ eeprom.increment_unlock_attempts();
+ assert_eq!(eeprom.get_unlock_attempts(), 2);
+ eeprom.reset_unlock_attempts();
+ assert_eq!(eeprom.get_unlock_attempts(), 0);
+ }
+
+ #[test]
+ fn test_is_enabled() {
+ let mut eeprom = FakeEeprom::new();
+ assert!(eeprom.is_enabled());
+ eeprom.disable();
+ assert!(!eeprom.is_enabled());
+ }
+}
diff --git a/src/rust/bitbox-platform-host/src/lib.rs b/src/rust/bitbox-platform-host/src/lib.rs
new file mode 100644
index 0000000..4342a38
--- /dev/null
+++ b/src/rust/bitbox-platform-host/src/lib.rs
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#![no_std]
+
+#[macro_use]
+extern crate alloc;
+
+pub mod eeprom;
+pub mod memory;
+pub mod sd;
+pub mod securechip;
diff --git a/src/rust/bitbox-platform-host/src/memory.rs b/src/rust/bitbox-platform-host/src/memory.rs
new file mode 100644
index 0000000..c460726
--- /dev/null
+++ b/src/rust/bitbox-platform-host/src/memory.rs
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use alloc::string::String;
+use alloc::vec::Vec;
+
+use bitbox_hal::memory::{
+ BleFirmwareSlot, BleMetadata, Error, OptigaConfigVersion, PasswordStretchAlgo, Platform,
+ SecurechipType,
+};
+
+pub struct FakeMemory {
+ ble_enabled: bool,
+ ble_metadata: BleMetadata,
+ ble_firmware_slots: [Vec<u8>; 2],
+ active_ble_firmware_version: String,
+ securechip_type: SecurechipType,
+ optiga_config_version: OptigaConfigVersion,
+ platform: Platform,
+ initialized: bool,
+ is_seeded: bool,
+ mnemonic_passphrase_enabled: bool,
+ seed_birthdate: u32,
+ encrypted_seed_and_hmac: Option<(Vec<u8>, PasswordStretchAlgo)>,
+ device_name: Option<String>,
+ salt_root: [u8; 32],
+ attestation_device_pubkey: Option<[u8; 64]>,
+ attestation_certificate: Option<[u8; 64]>,
+ attestation_root_pubkey_identifier: Option<[u8; 32]>,
+ attestation_bootloader_hash: [u8; 32],
+ multisig_entries: Vec<([u8; 32], String)>,
+}
+
+// Same as MEMORY_MULTISIG_NUM_ENTRIES in memory.h.
+const MULTISIG_LIMIT: usize = 25;
+
+impl FakeMemory {
+ pub fn new() -> Self {
+ Self {
+ ble_enabled: true,
+ ble_metadata: BleMetadata {
+ allowed_firmware_hash: [0; 32],
+ active_index: 0,
+ firmware_sizes: [0; 2],
+ firmware_checksums: [0; 2],
+ },
+ ble_firmware_slots: [
+ vec![0xff; bitbox_hal::memory::BLE_FIRMWARE_MAX_SIZE],
+ vec![0xff; bitbox_hal::memory::BLE_FIRMWARE_MAX_SIZE],
+ ],
+ active_ble_firmware_version: "0.0.0".into(),
+ securechip_type: SecurechipType::Optiga,
+ optiga_config_version: OptigaConfigVersion::V0,
+ platform: Platform::BitBox02,
+ initialized: false,
+ is_seeded: false,
+ mnemonic_passphrase_enabled: false,
+ seed_birthdate: 0,
+ encrypted_seed_and_hmac: None,
+ device_name: None,
+ salt_root: *b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ attestation_device_pubkey: None,
+ attestation_certificate: None,
+ attestation_root_pubkey_identifier: None,
+ attestation_bootloader_hash: [0; 32],
+ multisig_entries: Vec::new(),
+ }
+ }
+
+ pub fn set_securechip_type(&mut self, securechip_type: SecurechipType) {
+ self.securechip_type = securechip_type;
+ }
+
+ pub fn set_platform(&mut self, platform: Platform) {
+ self.platform = platform;
+ }
+
+ pub fn set_salt_root(&mut self, salt_root: &[u8; 32]) {
+ self.salt_root = *salt_root;
+ }
+
+ pub fn set_attestation_certificate(
+ &mut self,
+ pubkey: &[u8; 64],
+ certificate: &[u8; 64],
+ root_pubkey_identifier: &[u8; 32],
+ ) {
+ self.attestation_device_pubkey = Some(*pubkey);
+ self.attestation_certificate = Some(*certificate);
+ self.attestation_root_pubkey_identifier = Some(*root_pubkey_identifier);
+ }
+
+ pub fn set_attestation_bootloader_hash(&mut self, hash: &[u8; 32]) {
+ self.attestation_bootloader_hash = *hash;
+ }
+
+ pub fn ble_firmware_slot_data(&self, slot: BleFirmwareSlot) -> &[u8] {
+ match slot {
+ BleFirmwareSlot::First => &self.ble_firmware_slots[0],
+ BleFirmwareSlot::Second => &self.ble_firmware_slots[1],
+ }
+ }
+}
+
+impl bitbox_hal::Memory for FakeMemory {
+ const BLE_FW_FLASH_CHUNK_SIZE: u32 = 4096;
+
+ fn ble_enabled(&mut self) -> bool {
+ self.ble_enabled
+ }
+
+ fn ble_enable(&mut self, enable: bool) -> Result<(), ()> {
+ self.ble_enabled = enable;
+ Ok(())
+ }
+
+ fn get_active_ble_firmware_version(&mut self) -> Result<String, Error> {
+ Ok(self.active_ble_firmware_version.clone())
+ }
+
+ fn ble_firmware_flash_chunk(
+ &mut self,
+ slot: BleFirmwareSlot,
+ chunk_index: u32,
+ chunk: &[u8],
+ ) -> Result<(), Error> {
+ if chunk.len() > Self::BLE_FW_FLASH_CHUNK_SIZE as usize {
+ return Err(Error::InvalidInput);
+ }
+
+ let chunk_offset = (chunk_index as usize)
+ .checked_mul(Self::BLE_FW_FLASH_CHUNK_SIZE as usize)
+ .ok_or(Error::InvalidInput)?;
+ let chunk_end = chunk_offset
+ .checked_add(chunk.len())
+ .ok_or(Error::InvalidInput)?;
+
+ let slot_data = match slot {
+ BleFirmwareSlot::First => &mut self.ble_firmware_slots[0],
+ BleFirmwareSlot::Second => &mut self.ble_firmware_slots[1],
+ };
+ if chunk_end > slot_data.len() {
+ return Err(Error::InvalidInput);
+ }
+ slot_data[chunk_offset..chunk_end].copy_from_slice(chunk);
+ Ok(())
+ }
+
+ fn ble_get_metadata(&mut self) -> BleMetadata {
+ self.ble_metadata
+ }
+
+ fn set_ble_metadata(&mut self, metadata: &BleMetadata) -> Result<(), Error> {
+ self.ble_metadata = *metadata;
+ Ok(())
+ }
+
+ fn get_securechip_type(&mut self) -> Result<SecurechipType, ()> {
+ Ok(self.securechip_type)
+ }
+
+ fn get_optiga_config_version(&mut self) -> Result<OptigaConfigVersion, ()> {
+ Ok(self.optiga_config_version)
+ }
+
+ fn set_optiga_config_version(&mut self, version: OptigaConfigVersion) -> Result<(), ()> {
+ self.optiga_config_version = version;
+ Ok(())
+ }
+
+ fn get_platform(&mut self) -> Result<Platform, ()> {
+ Ok(self.platform)
+ }
+
+ fn get_device_name(&mut self) -> String {
+ self.device_name
+ .clone()
+ .unwrap_or_else(|| "My BitBox".into())
+ }
+
+ fn set_device_name(&mut self, name: &str) -> Result<(), Error> {
+ self.device_name = Some(name.into());
+ Ok(())
+ }
+
+ fn is_mnemonic_passphrase_enabled(&mut self) -> bool {
+ self.mnemonic_passphrase_enabled
+ }
+
+ fn set_mnemonic_passphrase_enabled(&mut self, enabled: bool) -> Result<(), ()> {
+ self.mnemonic_passphrase_enabled = enabled;
+ Ok(())
+ }
+
+ fn set_seed_birthdate(&mut self, timestamp: u32) -> Result<(), ()> {
+ self.seed_birthdate = timestamp;
+ Ok(())
+ }
+
+ fn get_seed_birthdate(&mut self) -> u32 {
+ self.seed_birthdate
+ }
+
+ fn is_seeded(&mut self) -> bool {
+ self.is_seeded
+ }
+
+ fn is_initialized(&mut self) -> bool {
+ self.initialized
+ }
+
+ fn set_initialized(&mut self) -> Result<(), ()> {
+ self.initialized = true;
+ Ok(())
+ }
+
+ fn get_encrypted_seed_and_hmac(&mut self) -> Result<(Vec<u8>, PasswordStretchAlgo), ()> {
+ self.encrypted_seed_and_hmac.clone().ok_or(())
+ }
+
+ fn set_encrypted_seed_and_hmac(
+ &mut self,
+ data: &[u8],
+ password_stretch_algo: PasswordStretchAlgo,
+ ) -> Result<(), ()> {
+ // 96 is the max space allocated in BitBox02's memory for this.
+ if data.len() > 96 {
+ return Err(());
+ }
+ self.encrypted_seed_and_hmac = Some((data.to_vec(), password_stretch_algo));
+ self.is_seeded = true;
+ Ok(())
+ }
+
+ fn reset_hww(&mut self) -> Result<(), ()> {
+ self.initialized = false;
+ self.is_seeded = false;
+ self.mnemonic_passphrase_enabled = false;
+ self.seed_birthdate = 0;
+ self.encrypted_seed_and_hmac = None;
+ self.device_name = None;
+ self.multisig_entries = Vec::new();
+ Ok(())
+ }
+
+ fn get_io_protection_key(&mut self, _out: &mut [u8; 32]) {
+ panic!("unused")
+ }
+
+ fn get_salt_root(&mut self) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+ if self.salt_root.iter().all(|&b| b == 0xff) {
+ Err(())
+ } else {
+ Ok(zeroize::Zeroizing::new(self.salt_root.to_vec()))
+ }
+ }
+
+ fn get_attestation_pubkey_and_certificate(
+ &mut self,
+ pubkey_out: &mut [u8; 64],
+ certificate_out: &mut [u8; 64],
+ root_pubkey_identifier_out: &mut [u8; 32],
+ ) -> Result<(), ()> {
+ match (
+ self.attestation_device_pubkey,
+ self.attestation_certificate,
+ self.attestation_root_pubkey_identifier,
+ ) {
+ (Some(pubkey), Some(certificate), Some(root_id)) => {
+ *pubkey_out = pubkey;
+ *certificate_out = certificate;
+ *root_pubkey_identifier_out = root_id;
+ Ok(())
+ }
+ _ => Err(()),
+ }
+ }
+
+ fn get_attestation_bootloader_hash(&mut self) -> [u8; 32] {
+ self.attestation_bootloader_hash
+ }
+
+ fn multisig_set_by_hash(&mut self, hash: &[u8; 32], name: &str) -> Result<(), Error> {
+ // Validate input
+ if name.is_empty() {
+ return Err(Error::InvalidInput);
+ }
+ // Check for duplicate name with different hash
+ for (existing_hash, existing_name) in &self.multisig_entries {
+ if existing_name == name {
+ if existing_hash != hash {
+ // Mirror bitbox02::memory multisig_set_by_hash semantics (duplicate-name / full-table),
+ // even if these branches are not currently exercised in bitbox02-rust tests.
+ return Err(Error::DuplicateName);
+ }
+ // same name, same hash (already stored)
+ return Ok(());
+ }
+ }
+ // Try to find existing entry with same hash
+ if let Some((_, existing_name)) = self
+ .multisig_entries
+ .iter_mut()
+ .find(|(existing_hash, _)| existing_hash == hash)
+ {
+ // rename: same hash, new name
+ *existing_name = String::from(name);
+ return Ok(());
+ }
+ if self.multisig_entries.len() >= MULTISIG_LIMIT {
+ // See comment above about mirroring bitbox02::memory semantics.
+ return Err(Error::Full);
+ }
+ // Insert new entry
+ self.multisig_entries.push((*hash, String::from(name)));
+ Ok(())
+ }
+
+ fn multisig_get_by_hash(&self, hash: &[u8; 32]) -> Option<String> {
+ self.multisig_entries
+ .iter()
+ .find(|(existing_hash, _)| existing_hash == hash)
+ .map(|(_, name)| name.clone())
+ }
+}
diff --git a/src/rust/bitbox-platform-host/src/sd.rs b/src/rust/bitbox-platform-host/src/sd.rs
new file mode 100644
index 0000000..57c83b0
--- /dev/null
+++ b/src/rust/bitbox-platform-host/src/sd.rs
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use alloc::collections::BTreeMap;
+use alloc::string::String;
+use alloc::vec::Vec;
+
+pub struct FakeSd {
+ pub inserted: Option<bool>,
+ files: BTreeMap<String, BTreeMap<String, Vec<u8>>>,
+}
+
+impl FakeSd {
+ pub fn new() -> Self {
+ Self {
+ inserted: None,
+ files: BTreeMap::new(),
+ }
+ }
+}
+
+impl bitbox_hal::Sd for FakeSd {
+ async fn sdcard_inserted(&mut self) -> bool {
+ self.inserted.unwrap()
+ }
+
+ async fn list_subdir(&mut self, subdir: Option<&str>) -> Result<Vec<String>, ()> {
+ match subdir {
+ Some(key) => Ok(self
+ .files
+ .get(key)
+ .map(|files| files.keys().cloned().collect())
+ .unwrap_or_default()),
+ None => Ok(self.files.keys().cloned().collect()),
+ }
+ }
+
+ async fn erase_file_in_subdir(&mut self, filename: &str, dir: &str) -> Result<(), ()> {
+ self.files
+ .get_mut(dir)
+ .and_then(|files| files.remove(filename).map(|_| ()))
+ .ok_or(())
+ }
+
+ async fn load_bin(
+ &mut self,
+ filename: &str,
+ dir: &str,
+ ) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+ self.files
+ .get(dir)
+ .and_then(|files| files.get(filename))
+ .map(|data| zeroize::Zeroizing::new(data.clone()))
+ .ok_or(())
+ }
+
+ async fn write_bin(&mut self, filename: &str, dir: &str, data: &[u8]) -> Result<(), ()> {
+ self.files
+ .entry(dir.into())
+ .or_default()
+ .insert(filename.into(), data.to_vec());
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use bitbox_hal::Sd;
+ use util::bb02_async::block_on;
+
+ // Quick check if our mock FakeSd implementation makes sense.
+ #[test]
+ fn test_sd_list_write_read_erase() {
+ let mut sd = FakeSd::new();
+ assert_eq!(block_on(sd.list_subdir(None)), Ok(vec![]));
+ assert_eq!(block_on(sd.list_subdir(Some("dir1"))), Ok(vec![]));
+
+ assert!(block_on(sd.load_bin("file1.txt", "dir1")).is_err());
+ assert!(block_on(sd.write_bin("file1.txt", "dir1", b"data")).is_ok());
+ assert_eq!(block_on(sd.list_subdir(None)), Ok(vec!["dir1".into()]));
+ assert_eq!(
+ block_on(sd.list_subdir(Some("dir1"))),
+ Ok(vec!["file1.txt".into()])
+ );
+ assert_eq!(
+ block_on(sd.load_bin("file1.txt", "dir1"))
+ .unwrap()
+ .as_slice(),
+ b"data"
+ );
+ assert!(block_on(sd.write_bin("file1.txt", "dir1", b"replaced data")).is_ok());
+ assert_eq!(
+ block_on(sd.load_bin("file1.txt", "dir1"))
+ .unwrap()
+ .as_slice(),
+ b"replaced data"
+ );
+ assert!(block_on(sd.erase_file_in_subdir("doesnt-exist.txt", "dir1")).is_err());
+ assert!(block_on(sd.erase_file_in_subdir("file1.txt", "dir1")).is_ok());
+ assert_eq!(block_on(sd.list_subdir(Some("dir1"))), Ok(vec![]));
+ }
+}
diff --git a/src/rust/bitbox-platform-host/src/securechip.rs b/src/rust/bitbox-platform-host/src/securechip.rs
new file mode 100644
index 0000000..a2e3beb
--- /dev/null
+++ b/src/rust/bitbox-platform-host/src/securechip.rs
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use alloc::vec::Vec;
+
+use bitcoin::hashes::Hash;
+use hex_lit::hex;
+
+use bitbox_hal::memory::PasswordStretchAlgo;
+use bitbox_hal::securechip::{Error, Model};
+
+pub struct FakeSecureChip {
+ // Count how many security events happen. The numbers were obtained by reading the security
+ // event counter slot (0xE0C5) on a real device. We can use this to assert how many events
+ // were used in unit tests. The number is relevant due to Optiga's throttling mechanism.
+ event_counter: u32,
+ reset_keys_fail_once: bool,
+ #[cfg(feature = "app-u2f")]
+ u2f_counter: u32,
+ mock_attestation_signature: [u8; 64],
+ last_attestation_challenge: Option<[u8; 32]>,
+}
+
+impl FakeSecureChip {
+ pub fn new() -> Self {
+ FakeSecureChip {
+ event_counter: 0,
+ reset_keys_fail_once: false,
+ #[cfg(feature = "app-u2f")]
+ u2f_counter: 0,
+ mock_attestation_signature: [0u8; 64],
+ last_attestation_challenge: None,
+ }
+ }
+
+ /// Resets the event counter.
+ pub fn event_counter_reset(&mut self) {
+ self.event_counter = 0;
+ }
+
+ /// Retrieves the event counter.
+ pub fn get_event_counter(&self) -> u32 {
+ self.event_counter
+ }
+
+ /// Make the next `reset_keys()` call return an error once. Subsequent calls succeed.
+ pub fn mock_reset_keys_fails(&mut self) {
+ self.reset_keys_fail_once = true;
+ }
+
+ #[cfg(feature = "app-u2f")]
+ pub fn get_u2f_counter(&self) -> u32 {
+ self.u2f_counter
+ }
+
+ pub fn set_mock_attestation_signature(&mut self, sig: &[u8; 64]) {
+ self.mock_attestation_signature = *sig;
+ }
+
+ pub fn last_attestation_challenge(&self) -> Option<[u8; 32]> {
+ self.last_attestation_challenge
+ }
+}
+
+impl bitbox_hal::SecureChip for FakeSecureChip {
+ fn init_new_password(
+ &mut self,
+ password: &str,
+ password_stretch_algo: PasswordStretchAlgo,
+ ) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
+ self.event_counter += 3;
+
+ let key: &'static [u8] = match password_stretch_algo {
+ PasswordStretchAlgo::V0 => b"unit-test-v0",
+ PasswordStretchAlgo::V1 => b"unit-test",
+ };
+ use bitcoin::hashes::{HashEngine, Hmac, HmacEngine, sha256};
+ let mut engine = HmacEngine::<sha256::Hash>::new(key);
+ engine.input(password.as_bytes());
+ let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
+ Ok(zeroize::Zeroizing::new(
+ hmac_result.to_byte_array().to_vec(),
+ ))
+ }
+
+ fn stretch_password(
+ &mut self,
+ password: &str,
+ password_stretch_algo: PasswordStretchAlgo,
+ ) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
+ self.event_counter += match password_stretch_algo {
+ PasswordStretchAlgo::V0 => 5,
+ PasswordStretchAlgo::V1 => 4,
+ };
+
+ let key: &'static [u8] = match password_stretch_algo {
+ PasswordStretchAlgo::V0 => b"unit-test-v0",
+ PasswordStretchAlgo::V1 => b"unit-test",
+ };
+
+ use bitcoin::hashes::{HashEngine, Hmac, HmacEngine, sha256};
+ let mut engine = HmacEngine::<sha256::Hash>::new(key);
+ engine.input(password.as_bytes());
+ let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
+ Ok(zeroize::Zeroizing::new(
+ hmac_result.to_byte_array().to_vec(),
+ ))
+ }
+
+ fn kdf(&mut self, msg: &[u8]) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
+ self.event_counter += 1;
+
+ use bitcoin::hashes::{HashEngine, Hmac, HmacEngine, sha256};
+ let mut engine = HmacEngine::<sha256::Hash>::new(&hex!(
+ "d2e1e6b18b6c6b08433edbc1d168c1a0043774a4221877e79ed56684be5ac01b"
+ ));
+ engine.input(msg);
+ let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
+ Ok(zeroize::Zeroizing::new(
+ hmac_result.to_byte_array().to_vec(),
+ ))
+ }
+
+ fn attestation_sign(
+ &mut self,
+ challenge: &[u8; 32],
+ signature: &mut [u8; 64],
+ ) -> Result<(), ()> {
+ self.event_counter += 1;
+ self.last_attestation_challenge = Some(*challenge);
+ *signature = self.mock_attestation_signature;
+ Ok(())
+ }
+
+ fn monotonic_increments_remaining(&mut self) -> Result<u32, ()> {
+ Ok(1)
+ }
+
+ fn model(&mut self) -> Result<Model, ()> {
+ Ok(Model::Atecc608B)
+ }
+
+ fn reset_keys(&mut self) -> Result<(), ()> {
+ if self.reset_keys_fail_once {
+ self.reset_keys_fail_once = false;
+ Err(())
+ } else {
+ self.event_counter += 3;
+ Ok(())
+ }
+ }
+
+ #[cfg(feature = "app-u2f")]
+ fn u2f_counter_set(&mut self, counter: u32) -> Result<(), ()> {
+ self.u2f_counter = counter;
+ Ok(())
+ }
+}
diff --git a/src/rust/bitbox02-rust/Cargo.toml b/src/rust/bitbox02-rust/Cargo.toml
index d57c9d7..43c62ac 100644
--- a/src/rust/bitbox02-rust/Cargo.toml
+++ b/src/rust/bitbox02-rust/Cargo.toml
@@ -17,6 +17,7 @@ doctest = false
bitbox-hal = { path = "../bitbox-hal" }
bitbox-da14531 = { path = "../bitbox-da14531" }
bitbox02 = { path = "../bitbox02" }
+bitbox-platform-host = { path = "../bitbox-platform-host", optional = true }
bitbox-u2fhid = { path = "../bitbox-u2fhid" }
bitbox-bytequeue = { path = "../bitbox-bytequeue" }
bitbox-usb-report-queue = { path = "../bitbox-usb-report-queue" }
@@ -85,6 +86,7 @@ app-litecoin = [
app-u2f = [
"bitbox-hal/app-u2f",
+ "bitbox-platform-host?/app-u2f",
"bitbox02/app-u2f",
]
@@ -96,6 +98,7 @@ app-cardano = [
]
testing = [
+ "dep:bitbox-platform-host",
"bitbox02/testing",
"bitbox-secp256k1/testing",
"util/testing"
diff --git a/src/rust/bitbox02-rust/src/hal/testing.rs b/src/rust/bitbox02-rust/src/hal/testing.rs
index 74f4c9c..c3ddeaf 100644
--- a/src/rust/bitbox02-rust/src/hal/testing.rs
+++ b/src/rust/bitbox02-rust/src/hal/testing.rs
@@ -1,18 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
-pub mod eeprom;
-pub mod memory;
pub mod random;
-pub mod sd;
-pub mod securechip;
pub mod system;
pub mod ui;
-pub use eeprom::TestingEeprom;
-pub use memory::TestingMemory;
+pub use bitbox_platform_host::eeprom::FakeEeprom as TestingEeprom;
+pub use bitbox_platform_host::memory::FakeMemory as TestingMemory;
+pub use bitbox_platform_host::sd::FakeSd as TestingSd;
+pub use bitbox_platform_host::securechip::FakeSecureChip as TestingSecureChip;
pub use random::TestingRandom;
-pub use sd::TestingSd;
-pub use securechip::TestingSecureChip;
pub use system::TestingSystem;
pub use ui::{Screen, TestingUi};
diff --git a/src/rust/bitbox02-rust/src/hal/testing/eeprom.rs b/src/rust/bitbox02-rust/src/hal/testing/eeprom.rs
deleted file mode 100644
index dc681a5..0000000
--- a/src/rust/bitbox02-rust/src/hal/testing/eeprom.rs
+++ /dev/null
@@ -1,84 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-pub struct TestingEeprom {
- pub enabled: bool,
- unlock_attempts: u8,
-}
-
-impl TestingEeprom {
- pub fn new() -> Self {
- Self {
- enabled: true,
- unlock_attempts: 0,
- }
- }
-
- pub fn set_unlock_attempts_for_testing(&mut self, attempts: u8) {
- self.unlock_attempts = attempts;
- }
-}
-
-impl crate::hal::Eeprom for TestingEeprom {
- fn setup(&mut self) {
- self.enabled = true;
- self.unlock_attempts = 0;
- }
-
- fn init(&mut self) {}
-
- fn is_enabled(&mut self) -> bool {
- self.enabled
- }
-
- fn disable(&mut self) {
- self.enabled = false;
- }
-
- fn get_unlock_attempts(&mut self) -> u8 {
- self.unlock_attempts
- }
-
- fn increment_unlock_attempts(&mut self) {
- self.unlock_attempts += 1;
- }
-
- fn reset_unlock_attempts(&mut self) {
- self.unlock_attempts = 0;
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- use crate::hal::Eeprom;
-
- #[test]
- fn test_disable() {
- let mut eeprom = TestingEeprom::new();
- assert!(eeprom.enabled);
- eeprom.disable();
- assert!(!eeprom.enabled);
- eeprom.setup();
- assert!(eeprom.enabled);
- }
-
- #[test]
- fn test_unlock_attempts() {
- let mut eeprom = TestingEeprom::new();
- assert_eq!(eeprom.get_unlock_attempts(), 0);
- eeprom.increment_unlock_attempts();
- eeprom.increment_unlock_attempts();
- assert_eq!(eeprom.get_unlock_attempts(), 2);
- eeprom.reset_unlock_attempts();
- assert_eq!(eeprom.get_unlock_attempts(), 0);
- }
-
- #[test]
- fn test_is_enabled() {
- let mut eeprom = TestingEeprom::new();
- assert!(eeprom.is_enabled());
- eeprom.disable();
- assert!(!eeprom.is_enabled());
- }
-}
diff --git a/src/rust/bitbox02-rust/src/hal/testing/memory.rs b/src/rust/bitbox02-rust/src/hal/testing/memory.rs
deleted file mode 100644
index 3ebc6f5..0000000
--- a/src/rust/bitbox02-rust/src/hal/testing/memory.rs
+++ /dev/null
@@ -1,326 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-use alloc::string::String;
-use alloc::vec::Vec;
-
-use crate::hal::memory::{
- BleFirmwareSlot, BleMetadata, Error, OptigaConfigVersion, PasswordStretchAlgo, Platform,
- SecurechipType,
-};
-
-pub struct TestingMemory {
- ble_enabled: bool,
- ble_metadata: BleMetadata,
- ble_firmware_slots: [Vec<u8>; 2],
- active_ble_firmware_version: String,
- securechip_type: SecurechipType,
- optiga_config_version: OptigaConfigVersion,
- platform: Platform,
- initialized: bool,
- is_seeded: bool,
- mnemonic_passphrase_enabled: bool,
- seed_birthdate: u32,
- encrypted_seed_and_hmac: Option<(Vec<u8>, PasswordStretchAlgo)>,
- device_name: Option<String>,
- salt_root: [u8; 32],
- attestation_device_pubkey: Option<[u8; 64]>,
- attestation_certificate: Option<[u8; 64]>,
- attestation_root_pubkey_identifier: Option<[u8; 32]>,
- attestation_bootloader_hash: [u8; 32],
- multisig_entries: Vec<([u8; 32], String)>,
-}
-
-// Same as MEMORY_MULTISIG_NUM_ENTRIES in memory.h.
-const MULTISIG_LIMIT: usize = 25;
-
-impl TestingMemory {
- pub fn new() -> Self {
- Self {
- ble_enabled: true,
- ble_metadata: BleMetadata {
- allowed_firmware_hash: [0; 32],
- active_index: 0,
- firmware_sizes: [0; 2],
- firmware_checksums: [0; 2],
- },
- ble_firmware_slots: [
- vec![0xff; crate::hal::memory::BLE_FIRMWARE_MAX_SIZE],
- vec![0xff; crate::hal::memory::BLE_FIRMWARE_MAX_SIZE],
- ],
- active_ble_firmware_version: "0.0.0".into(),
- securechip_type: SecurechipType::Optiga,
- optiga_config_version: OptigaConfigVersion::V0,
- platform: Platform::BitBox02,
- initialized: false,
- is_seeded: false,
- mnemonic_passphrase_enabled: false,
- seed_birthdate: 0,
- encrypted_seed_and_hmac: None,
- device_name: None,
- salt_root: *b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
- attestation_device_pubkey: None,
- attestation_certificate: None,
- attestation_root_pubkey_identifier: None,
- attestation_bootloader_hash: [0; 32],
- multisig_entries: Vec::new(),
- }
- }
-
- pub fn set_securechip_type(&mut self, securechip_type: SecurechipType) {
- self.securechip_type = securechip_type;
- }
-
- pub fn set_platform(&mut self, platform: Platform) {
- self.platform = platform;
- }
-
- pub fn set_salt_root(&mut self, salt_root: &[u8; 32]) {
- self.salt_root = *salt_root;
- }
-
- pub fn set_attestation_certificate(
- &mut self,
- pubkey: &[u8; 64],
- certificate: &[u8; 64],
- root_pubkey_identifier: &[u8; 32],
- ) {
- self.attestation_device_pubkey = Some(*pubkey);
- self.attestation_certificate = Some(*certificate);
- self.attestation_root_pubkey_identifier = Some(*root_pubkey_identifier);
- }
-
- pub fn set_attestation_bootloader_hash(&mut self, hash: &[u8; 32]) {
- self.attestation_bootloader_hash = *hash;
- }
-
- pub fn ble_firmware_slot_data(&self, slot: BleFirmwareSlot) -> &[u8] {
- match slot {
- BleFirmwareSlot::First => &self.ble_firmware_slots[0],
- BleFirmwareSlot::Second => &self.ble_firmware_slots[1],
- }
- }
-}
-
-impl crate::hal::Memory for TestingMemory {
- const BLE_FW_FLASH_CHUNK_SIZE: u32 = 4096;
-
- fn ble_enabled(&mut self) -> bool {
- self.ble_enabled
- }
-
- fn ble_enable(&mut self, enable: bool) -> Result<(), ()> {
- self.ble_enabled = enable;
- Ok(())
- }
-
- fn get_active_ble_firmware_version(&mut self) -> Result<String, Error> {
- Ok(self.active_ble_firmware_version.clone())
- }
-
- fn ble_firmware_flash_chunk(
- &mut self,
- slot: BleFirmwareSlot,
- chunk_index: u32,
- chunk: &[u8],
- ) -> Result<(), Error> {
- if chunk.len() > Self::BLE_FW_FLASH_CHUNK_SIZE as usize {
- return Err(Error::InvalidInput);
- }
-
- let chunk_offset = (chunk_index as usize)
- .checked_mul(Self::BLE_FW_FLASH_CHUNK_SIZE as usize)
- .ok_or(Error::InvalidInput)?;
- let chunk_end = chunk_offset
- .checked_add(chunk.len())
- .ok_or(Error::InvalidInput)?;
-
- let slot_data = match slot {
- BleFirmwareSlot::First => &mut self.ble_firmware_slots[0],
- BleFirmwareSlot::Second => &mut self.ble_firmware_slots[1],
- };
- if chunk_end > slot_data.len() {
- return Err(Error::InvalidInput);
- }
- slot_data[chunk_offset..chunk_end].copy_from_slice(chunk);
- Ok(())
- }
-
- fn ble_get_metadata(&mut self) -> BleMetadata {
- self.ble_metadata
- }
-
- fn set_ble_metadata(&mut self, metadata: &BleMetadata) -> Result<(), Error> {
- self.ble_metadata = *metadata;
- Ok(())
- }
-
- fn get_securechip_type(&mut self) -> Result<SecurechipType, ()> {
- Ok(self.securechip_type)
- }
-
- fn get_optiga_config_version(&mut self) -> Result<OptigaConfigVersion, ()> {
- Ok(self.optiga_config_version)
- }
-
- fn set_optiga_config_version(&mut self, version: OptigaConfigVersion) -> Result<(), ()> {
- self.optiga_config_version = version;
- Ok(())
- }
-
- fn get_platform(&mut self) -> Result<Platform, ()> {
- Ok(self.platform)
- }
-
- fn get_device_name(&mut self) -> String {
- self.device_name
- .clone()
- .unwrap_or_else(|| "My BitBox".into())
- }
-
- fn set_device_name(&mut self, name: &str) -> Result<(), Error> {
- self.device_name = Some(name.into());
- Ok(())
- }
-
- fn is_mnemonic_passphrase_enabled(&mut self) -> bool {
- self.mnemonic_passphrase_enabled
- }
-
- fn set_mnemonic_passphrase_enabled(&mut self, enabled: bool) -> Result<(), ()> {
- self.mnemonic_passphrase_enabled = enabled;
- Ok(())
- }
-
- fn set_seed_birthdate(&mut self, timestamp: u32) -> Result<(), ()> {
- self.seed_birthdate = timestamp;
- Ok(())
- }
-
- fn get_seed_birthdate(&mut self) -> u32 {
- self.seed_birthdate
- }
-
- fn is_seeded(&mut self) -> bool {
- self.is_seeded
- }
-
- fn is_initialized(&mut self) -> bool {
- self.initialized
- }
-
- fn set_initialized(&mut self) -> Result<(), ()> {
- self.initialized = true;
- Ok(())
- }
-
- fn get_encrypted_seed_and_hmac(
- &mut self,
- ) -> Result<(alloc::vec::Vec<u8>, PasswordStretchAlgo), ()> {
- self.encrypted_seed_and_hmac.clone().ok_or(())
- }
-
- fn set_encrypted_seed_and_hmac(
- &mut self,
- data: &[u8],
- password_stretch_algo: PasswordStretchAlgo,
- ) -> Result<(), ()> {
- // 96 is the max space allocated in BitBox02's memory for this.
- if data.len() > 96 {
- return Err(());
- }
- self.encrypted_seed_and_hmac = Some((data.to_vec(), password_stretch_algo));
- self.is_seeded = true;
- Ok(())
- }
-
- fn reset_hww(&mut self) -> Result<(), ()> {
- self.initialized = false;
- self.is_seeded = false;
- self.mnemonic_passphrase_enabled = false;
- self.seed_birthdate = 0;
- self.encrypted_seed_and_hmac = None;
- self.device_name = None;
- self.multisig_entries = Vec::new();
- Ok(())
- }
-
- fn get_io_protection_key(&mut self, _out: &mut [u8; 32]) {
- panic!("unused")
- }
-
- fn get_salt_root(&mut self) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- if self.salt_root.iter().all(|&b| b == 0xff) {
- Err(())
- } else {
- Ok(zeroize::Zeroizing::new(self.salt_root.to_vec()))
- }
- }
-
- fn get_attestation_pubkey_and_certificate(
- &mut self,
- pubkey_out: &mut [u8; 64],
- certificate_out: &mut [u8; 64],
- root_pubkey_identifier_out: &mut [u8; 32],
- ) -> Result<(), ()> {
- match (
- self.attestation_device_pubkey,
- self.attestation_certificate,
- self.attestation_root_pubkey_identifier,
- ) {
- (Some(pubkey), Some(certificate), Some(root_id)) => {
- *pubkey_out = pubkey;
- *certificate_out = certificate;
- *root_pubkey_identifier_out = root_id;
- Ok(())
- }
- _ => Err(()),
- }
- }
-
- fn get_attestation_bootloader_hash(&mut self) -> [u8; 32] {
- self.attestation_bootloader_hash
- }
-
- fn multisig_set_by_hash(&mut self, hash: &[u8; 32], name: &str) -> Result<(), Error> {
- // Validate input
- if name.is_empty() {
- return Err(Error::InvalidInput);
- }
- // Check for duplicate name with different hash
- for (existing_hash, existing_name) in &self.multisig_entries {
- if existing_name == name {
- if existing_hash != hash {
- // Mirror bitbox02::memory multisig_set_by_hash semantics (duplicate-name / full-table),
- // even if these branches are not currently exercised in bitbox02-rust tests.
- return Err(Error::DuplicateName);
- }
- // same name, same hash (already stored)
- return Ok(());
- }
- }
- // Try to find existing entry with same hash
- if let Some((_, existing_name)) = self
- .multisig_entries
- .iter_mut()
- .find(|(existing_hash, _)| existing_hash == hash)
- {
- // rename: same hash, new name
- *existing_name = String::from(name);
- return Ok(());
- }
- if self.multisig_entries.len() >= MULTISIG_LIMIT {
- // See comment above about mirroring bitbox02::memory semantics.
- return Err(Error::Full);
- }
- // Insert new entry
- self.multisig_entries.push((*hash, String::from(name)));
- Ok(())
- }
-
- fn multisig_get_by_hash(&self, hash: &[u8; 32]) -> Option<String> {
- self.multisig_entries
- .iter()
- .find(|(existing_hash, _)| existing_hash == hash)
- .map(|(_, name)| name.clone())
- }
-}
diff --git a/src/rust/bitbox02-rust/src/hal/testing/sd.rs b/src/rust/bitbox02-rust/src/hal/testing/sd.rs
deleted file mode 100644
index 3027c1b..0000000
--- a/src/rust/bitbox02-rust/src/hal/testing/sd.rs
+++ /dev/null
@@ -1,102 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-use alloc::collections::BTreeMap;
-use alloc::string::String;
-use alloc::vec::Vec;
-
-pub struct TestingSd {
- pub inserted: Option<bool>,
- files: BTreeMap<String, BTreeMap<String, Vec<u8>>>,
-}
-
-impl TestingSd {
- pub fn new() -> Self {
- Self {
- inserted: None,
- files: BTreeMap::new(),
- }
- }
-}
-
-impl crate::hal::Sd for TestingSd {
- async fn sdcard_inserted(&mut self) -> bool {
- self.inserted.unwrap()
- }
-
- async fn list_subdir(&mut self, subdir: Option<&str>) -> Result<Vec<String>, ()> {
- match subdir {
- Some(key) => Ok(self
- .files
- .get(key)
- .map(|files| files.keys().cloned().collect())
- .unwrap_or_default()),
- None => Ok(self.files.keys().cloned().collect()),
- }
- }
-
- async fn erase_file_in_subdir(&mut self, filename: &str, dir: &str) -> Result<(), ()> {
- self.files
- .get_mut(dir)
- .and_then(|files| files.remove(filename).map(|_| ()))
- .ok_or(())
- }
-
- async fn load_bin(
- &mut self,
- filename: &str,
- dir: &str,
- ) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- self.files
- .get(dir)
- .and_then(|files| files.get(filename))
- .map(|data| zeroize::Zeroizing::new(data.clone()))
- .ok_or(())
- }
-
- async fn write_bin(&mut self, filename: &str, dir: &str, data: &[u8]) -> Result<(), ()> {
- self.files
- .entry(dir.into())
- .or_default()
- .insert(filename.into(), data.to_vec());
- Ok(())
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::hal::Sd;
- use util::bb02_async::block_on;
-
- // Quick check if our mock TestingSd implementation makes sense.
- #[test]
- fn test_sd_list_write_read_erase() {
- let mut sd = TestingSd::new();
- assert_eq!(block_on(sd.list_subdir(None)), Ok(vec![]));
- assert_eq!(block_on(sd.list_subdir(Some("dir1"))), Ok(vec![]));
-
- assert!(block_on(sd.load_bin("file1.txt", "dir1")).is_err());
- assert!(block_on(sd.write_bin("file1.txt", "dir1", b"data")).is_ok());
- assert_eq!(block_on(sd.list_subdir(None)), Ok(vec!["dir1".into()]));
- assert_eq!(
- block_on(sd.list_subdir(Some("dir1"))),
- Ok(vec!["file1.txt".into()])
- );
- assert_eq!(
- block_on(sd.load_bin("file1.txt", "dir1"))
- .unwrap()
- .as_slice(),
- b"data"
- );
- assert!(block_on(sd.write_bin("file1.txt", "dir1", b"replaced data")).is_ok());
- assert_eq!(
- block_on(sd.load_bin("file1.txt", "dir1"))
- .unwrap()
- .as_slice(),
- b"replaced data"
- );
- assert!(block_on(sd.erase_file_in_subdir("doesnt-exist.txt", "dir1")).is_err());
- assert!(block_on(sd.erase_file_in_subdir("file1.txt", "dir1")).is_ok());
- assert_eq!(block_on(sd.list_subdir(Some("dir1"))), Ok(vec![]));
- }
-}
diff --git a/src/rust/bitbox02-rust/src/hal/testing/securechip.rs b/src/rust/bitbox02-rust/src/hal/testing/securechip.rs
deleted file mode 100644
index 219d86c..0000000
--- a/src/rust/bitbox02-rust/src/hal/testing/securechip.rs
+++ /dev/null
@@ -1,157 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-use alloc::vec::Vec;
-
-use bitcoin::hashes::Hash;
-use hex_lit::hex;
-
-use crate::hal::memory::PasswordStretchAlgo;
-use crate::hal::securechip::{Error, Model};
-
-pub struct TestingSecureChip {
- // Count how many security events happen. The numbers were obtained by reading the security
- // event counter slot (0xE0C5) on a real device. We can use this to assert how many events
- // were used in unit tests. The number is relevant due to Optiga's throttling mechanism.
- event_counter: u32,
- reset_keys_fail_once: bool,
- #[cfg(feature = "app-u2f")]
- u2f_counter: u32,
- mock_attestation_signature: [u8; 64],
- last_attestation_challenge: Option<[u8; 32]>,
-}
-
-impl TestingSecureChip {
- pub fn new() -> Self {
- TestingSecureChip {
- event_counter: 0,
- reset_keys_fail_once: false,
- #[cfg(feature = "app-u2f")]
- u2f_counter: 0,
- mock_attestation_signature: [0u8; 64],
- last_attestation_challenge: None,
- }
- }
-
- /// Resets the event counter.
- pub fn event_counter_reset(&mut self) {
- self.event_counter = 0;
- }
-
- /// Retrieves the event counter.
- pub fn get_event_counter(&self) -> u32 {
- self.event_counter
- }
-
- /// Make the next `reset_keys()` call return an error once. Subsequent calls succeed.
- pub fn mock_reset_keys_fails(&mut self) {
- self.reset_keys_fail_once = true;
- }
-
- #[cfg(feature = "app-u2f")]
- pub fn get_u2f_counter(&self) -> u32 {
- self.u2f_counter
- }
-
- pub fn set_mock_attestation_signature(&mut self, sig: &[u8; 64]) {
- self.mock_attestation_signature = *sig;
- }
-
- pub fn last_attestation_challenge(&self) -> Option<[u8; 32]> {
- self.last_attestation_challenge
- }
-}
-
-impl crate::hal::SecureChip for TestingSecureChip {
- fn init_new_password(
- &mut self,
- password: &str,
- password_stretch_algo: PasswordStretchAlgo,
- ) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
- self.event_counter += 3;
-
- let key: &'static [u8] = match password_stretch_algo {
- PasswordStretchAlgo::V0 => b"unit-test-v0",
- PasswordStretchAlgo::V1 => b"unit-test",
- };
- use bitcoin::hashes::{HashEngine, Hmac, HmacEngine, sha256};
- let mut engine = HmacEngine::<sha256::Hash>::new(key);
- engine.input(password.as_bytes());
- let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
- Ok(zeroize::Zeroizing::new(
- hmac_result.to_byte_array().to_vec(),
- ))
- }
-
- fn stretch_password(
- &mut self,
- password: &str,
- password_stretch_algo: PasswordStretchAlgo,
- ) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
- self.event_counter += match password_stretch_algo {
- PasswordStretchAlgo::V0 => 5,
- PasswordStretchAlgo::V1 => 4,
- };
-
- let key: &'static [u8] = match password_stretch_algo {
- PasswordStretchAlgo::V0 => b"unit-test-v0",
- PasswordStretchAlgo::V1 => b"unit-test",
- };
-
- use bitcoin::hashes::{HashEngine, Hmac, HmacEngine, sha256};
- let mut engine = HmacEngine::<sha256::Hash>::new(key);
- engine.input(password.as_bytes());
- let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
- Ok(zeroize::Zeroizing::new(
- hmac_result.to_byte_array().to_vec(),
- ))
- }
-
- fn kdf(&mut self, msg: &[u8]) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
- self.event_counter += 1;
-
- use bitcoin::hashes::{HashEngine, Hmac, HmacEngine, sha256};
- let mut engine = HmacEngine::<sha256::Hash>::new(&hex!(
- "d2e1e6b18b6c6b08433edbc1d168c1a0043774a4221877e79ed56684be5ac01b"
- ));
- engine.input(msg);
- let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
- Ok(zeroize::Zeroizing::new(
- hmac_result.to_byte_array().to_vec(),
- ))
- }
-
- fn attestation_sign(
- &mut self,
- challenge: &[u8; 32],
- signature: &mut [u8; 64],
- ) -> Result<(), ()> {
- self.event_counter += 1;
- self.last_attestation_challenge = Some(*challenge);
- *signature = self.mock_attestation_signature;
- Ok(())
- }
-
- fn monotonic_increments_remaining(&mut self) -> Result<u32, ()> {
- Ok(1)
- }
-
- fn model(&mut self) -> Result<Model, ()> {
- Ok(Model::Atecc608B)
- }
-
- fn reset_keys(&mut self) -> Result<(), ()> {
- if self.reset_keys_fail_once {
- self.reset_keys_fail_once = false;
- Err(())
- } else {
- self.event_counter += 3;
- Ok(())
- }
- }
-
- #[cfg(feature = "app-u2f")]
- fn u2f_counter_set(&mut self, counter: u32) -> Result<(), ()> {
- self.u2f_counter = counter;
- Ok(())
- }
-}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.