Move "get_attestation_*" functions to HAL
What changed, and why it matters
This commit is a code cleanup and test-improvement change. It moves two device-attestation helper functions from a direct hardware call into a software 'hardware abstraction layer' (HAL) so the code can be tested without the real device. It also adds unit tests that simulate the attestation process. There is no indication this fixes a security bug or introduces a new vulnerability.
No security action required. Treat as normal code-quality/test-coverage improvement. Continue standard review and CI testing.
Security signals we found
Refactor only: no change to cryptographic operations, trust model, or data flow
Attestation still uses SHA-256 of host challenge and secure-chip signing
Added unit tests increase coverage for attestation success and missing-certificate failure paths
Evidence from the diff
The change refactors attestation.rs to call memory operations through the HAL trait (Memory::get_attestation_pubkey_and_certificate and Memory::get_attestation_bootloader_hash) instead of calling bitbox02::memory directly. The production implementation delegates to the same underlying C memory functions, so runtime behavior is unchanged. A test-only implementation (TestingMemory/TestingSecureChip) is added so the attestation flow can be exercised in unit tests. The previously stubbed attestation_sign in the test secure chip is now implemented to record the challenge and return a mock signature.
Changed components
src/rust/bitbox02-rust/src/attestation.rssrc/rust/bitbox02-rust/src/hal.rsInspect captured patch +147 / −6
diff --git a/src/rust/bitbox02-rust/src/attestation.rs b/src/rust/bitbox02-rust/src/attestation.rs
index b57d686..81125a1 100644
--- a/src/rust/bitbox02-rust/src/attestation.rs
+++ b/src/rust/bitbox02-rust/src/attestation.rs
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::hal::SecureChip;
+use crate::hal::{Memory, SecureChip};
use sha2::{Digest, Sha256};
pub struct Data {
@@ -31,14 +31,69 @@ pub fn perform(hal: &mut impl crate::hal::Hal, host_challenge: [u8; 32]) -> Resu
root_pubkey_identifier: [0; 32],
challenge_signature: [0; 64],
};
- bitbox02::memory::get_attestation_pubkey_and_certificate(
+ hal.memory().get_attestation_pubkey_and_certificate(
&mut result.device_pubkey,
&mut result.certificate,
&mut result.root_pubkey_identifier,
)?;
+ result.bootloader_hash = hal.memory().get_attestation_bootloader_hash();
let hash: [u8; 32] = Sha256::digest(host_challenge).into();
- result.bootloader_hash = bitbox02::memory::get_attestation_bootloader_hash();
hal.securechip()
.attestation_sign(&hash, &mut result.challenge_signature)?;
Ok(result)
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::hal::testing::TestingHal;
+ use sha2::{Digest, Sha256};
+
+ #[test]
+ fn test_perform_success() {
+ let mut hal = TestingHal::new();
+
+ let expected_pubkey = [0x55u8; 64];
+ let expected_certificate = [0x66u8; 64];
+ let expected_root_id = [0x77u8; 32];
+ let expected_bootloader_hash = [0x88u8; 32];
+ let expected_signature = [0x99u8; 64];
+
+ hal.memory.set_attestation_certificate(
+ &expected_pubkey,
+ &expected_certificate,
+ &expected_root_id,
+ );
+ hal.memory
+ .set_attestation_bootloader_hash(&expected_bootloader_hash);
+ hal.securechip
+ .set_mock_attestation_signature(&expected_signature);
+
+ let host_challenge = [0x42u8; 32];
+
+ let data = perform(&mut hal, host_challenge).unwrap();
+
+ assert_eq!(data.device_pubkey, expected_pubkey);
+ assert_eq!(data.certificate, expected_certificate);
+ assert_eq!(data.root_pubkey_identifier, expected_root_id);
+ assert_eq!(data.bootloader_hash, expected_bootloader_hash);
+ assert_eq!(data.challenge_signature, expected_signature);
+
+ let expected_hash: [u8; 32] = Sha256::digest(host_challenge).into();
+ assert_eq!(
+ hal.securechip.last_attestation_challenge().unwrap(),
+ expected_hash
+ );
+ }
+
+ #[test]
+ fn test_perform_attestation_not_set() {
+ let mut hal = TestingHal::new();
+ let host_challenge = [0u8; 32];
+
+ // No attestation data configured on hal.memory(),
+ // so get_attestation_pubkey_and_certificate should fail
+ // and perform() should propagate Err(()).
+ assert!(perform(&mut hal, host_challenge).is_err());
+ }
+}
diff --git a/src/rust/bitbox02-rust/src/hal.rs b/src/rust/bitbox02-rust/src/hal.rs
index 51df79e..03ff17c 100644
--- a/src/rust/bitbox02-rust/src/hal.rs
+++ b/src/rust/bitbox02-rust/src/hal.rs
@@ -79,6 +79,13 @@ pub trait Memory {
fn increment_unlock_attempts(&mut self);
fn reset_unlock_attempts(&mut self);
fn get_salt_root(&mut self) -> Result<zeroize::Zeroizing<Vec<u8>>, ()>;
+ 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<(), ()>;
+ fn get_attestation_bootloader_hash(&mut self) -> [u8; 32];
}
/// Hardware abstraction layer for BitBox devices.
@@ -261,6 +268,23 @@ impl Memory for BitBox02Memory {
fn get_salt_root(&mut self) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
bitbox02::memory::get_salt_root()
}
+
+ 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<(), ()> {
+ bitbox02::memory::get_attestation_pubkey_and_certificate(
+ pubkey_out,
+ certificate_out,
+ root_pubkey_identifier_out,
+ )
+ }
+
+ fn get_attestation_bootloader_hash(&mut self) -> [u8; 32] {
+ bitbox02::memory::get_attestation_bootloader_hash()
+ }
}
pub struct BitBox02Hal {
@@ -410,6 +434,8 @@ pub mod testing {
reset_keys_fail_once: bool,
#[cfg(feature = "app-u2f")]
u2f_counter: u32,
+ mock_attestation_signature: [u8; 64],
+ last_attestation_challenge: Option<[u8; 32]>,
}
pub struct TestingMemory {
@@ -423,6 +449,10 @@ pub mod testing {
device_name: Option<String>,
unlock_attempts: u8,
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],
}
impl TestingSecureChip {
@@ -432,6 +462,8 @@ pub mod testing {
reset_keys_fail_once: false,
#[cfg(feature = "app-u2f")]
u2f_counter: 0,
+ mock_attestation_signature: [0u8; 64],
+ last_attestation_challenge: None,
}
}
@@ -454,6 +486,14 @@ pub mod testing {
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 super::SecureChip for TestingSecureChip {
@@ -499,11 +539,13 @@ pub mod testing {
fn attestation_sign(
&mut self,
- _challenge: &[u8; 32],
- _signature: &mut [u8; 64],
+ challenge: &[u8; 32],
+ signature: &mut [u8; 64],
) -> Result<(), ()> {
self.event_counter += 1;
- todo!()
+ self.last_attestation_challenge = Some(*challenge);
+ *signature = self.mock_attestation_signature;
+ Ok(())
}
fn monotonic_increments_remaining(&mut self) -> Result<u32, ()> {
@@ -544,6 +586,10 @@ pub mod testing {
device_name: None,
unlock_attempts: 0,
salt_root: *b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ attestation_device_pubkey: None,
+ attestation_certificate: None,
+ attestation_root_pubkey_identifier: None,
+ attestation_bootloader_hash: [0; 32],
}
}
@@ -562,6 +608,21 @@ pub mod testing {
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;
+ }
}
impl super::Memory for TestingMemory {
@@ -658,6 +719,31 @@ pub mod testing {
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
+ }
}
pub struct TestingHal<'a> {
Why this scored 18/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.