optiga: decouple memory from pal_os_datastore
What changed, and why it matters
This commit is a code cleanup that moves where the device fetches a 32-byte secret key used to talk to the secure chip (Optiga). Previously, a low-level C module directly called a BB02-specific memory function. Now it goes through a Rust abstraction layer so the same module can be reused in a future BB03 device. The change does not alter how the key is stored, generated, or protected; it only reroutes the function call. There is no indication this fixes a security bug.
No immediate security action required. Treat as normal refactoring. If reviewing for BB03 reuse, verify the new Rust HAL implementation of `get_io_protection_key` preserves the same zeroization and access controls as the BB02 original.
Security signals we found
Refactor of secret-key retrieval path for secure-chip binding secret
No change to key storage, generation, or access-control logic
No bounds check changes; output length remains hardcoded 32 bytes
No vendor disclosure or CVE references present
Evidence from the diff
The patch decouples pal_os_datastore.c from memory/memory.h and instead calls rust_memory_get_io_protection_key() via the Rust firmware C API. A new get_io_protection_key method is added to the Memory trait and implemented for BB02 real and testing memory HALs. The underlying C function memory_get_io_protection_key remains unchanged and is now pub(crate) rather than test-only. This is an architectural refactor for hardware portability (BB02 → BB03 reuse), not a cryptographic or access-control change.
Changed components
src/optiga/pal/pal_os_datastore.csrc/rust/bitbox-hal/src/memory.rssrc/rust/bitbox02-rust-c/src/firmware_c_api.rssrc/rust/bitbox02-rust/src/hal/testing/memory.rssrc/rust/bitbox02/src/hal/memory.rssrc/rust/bitbox02/src/memory.rsInspect captured patch +31 / −4
diff --git a/src/optiga/pal/pal_os_datastore.c b/src/optiga/pal/pal_os_datastore.c
index 1b78702..6b394b2 100644
--- a/src/optiga/pal/pal_os_datastore.c
+++ b/src/optiga/pal/pal_os_datastore.c
@@ -36,7 +36,7 @@
*/
#include "pal_os_datastore.h"
-#include "memory/memory.h"
+#include <rust/rust.h>
#include <util.h>
/// @cond hidden
@@ -64,7 +64,7 @@ pal_status_t pal_os_datastore_read(
switch (datastore_id) {
case OPTIGA_PLATFORM_BINDING_SHARED_SECRET_ID: {
- memory_get_io_protection_key(p_buffer);
+ rust_memory_get_io_protection_key(rust_util_bytes_mut(p_buffer, 32));
*p_buffer_length = 32;
return_status = PAL_STATUS_SUCCESS;
break;
diff --git a/src/rust/bitbox-hal/src/memory.rs b/src/rust/bitbox-hal/src/memory.rs
index 1845088..5fc425c 100644
--- a/src/rust/bitbox-hal/src/memory.rs
+++ b/src/rust/bitbox-hal/src/memory.rs
@@ -97,6 +97,7 @@ pub trait Memory {
fn get_unlock_attempts(&mut self) -> u8;
fn increment_unlock_attempts(&mut self);
fn reset_unlock_attempts(&mut self);
+ fn get_io_protection_key(&mut self, out: &mut [u8; 32]);
fn get_salt_root(&mut self) -> Result<zeroize::Zeroizing<Vec<u8>>, ()>;
fn get_attestation_pubkey_and_certificate(
&mut self,
diff --git a/src/rust/bitbox02-rust-c/src/firmware_c_api.rs b/src/rust/bitbox02-rust-c/src/firmware_c_api.rs
index ff6110f..7cfd6fe 100644
--- a/src/rust/bitbox02-rust-c/src/firmware_c_api.rs
+++ b/src/rust/bitbox02-rust-c/src/firmware_c_api.rs
@@ -87,6 +87,13 @@ pub extern "C" fn rust_memory_get_securechip_type() -> rust_memory_securechip_ty
}
}
+#[unsafe(no_mangle)]
+pub extern "C" fn rust_memory_get_io_protection_key(mut key_out: BytesMut) {
+ let mut hal = crate::HalImpl::new();
+ hal.memory()
+ .get_io_protection_key(key_out.as_mut().try_into().unwrap());
+}
+
#[cfg(feature = "app-u2f")]
#[unsafe(no_mangle)]
pub extern "C" fn rust_keystore_get_u2f_seed(mut seed_out: util::bytes::BytesMut) -> bool {
@@ -144,4 +151,16 @@ mod tests {
};
assert_eq!(rust_memory_get_securechip_type(), expected);
}
+
+ #[test]
+ fn test_rust_memory_get_io_protection_key() {
+ let mut hal = crate::HalImpl::new();
+ let mut expected = [0u8; 32];
+ hal.memory().get_io_protection_key(&mut expected);
+ let mut actual = [0u8; 32];
+ rust_memory_get_io_protection_key(unsafe {
+ util::bytes::rust_util_bytes_mut(actual.as_mut_ptr(), actual.len())
+ });
+ assert_eq!(actual, expected);
+ }
}
diff --git a/src/rust/bitbox02-rust/src/hal/testing/memory.rs b/src/rust/bitbox02-rust/src/hal/testing/memory.rs
index 6d6a725..5f207eb 100644
--- a/src/rust/bitbox02-rust/src/hal/testing/memory.rs
+++ b/src/rust/bitbox02-rust/src/hal/testing/memory.rs
@@ -262,6 +262,10 @@ impl crate::hal::Memory for TestingMemory {
self.unlock_attempts = 0;
}
+ 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(())
diff --git a/src/rust/bitbox02/src/hal/memory.rs b/src/rust/bitbox02/src/hal/memory.rs
index 3ea4995..a633353 100644
--- a/src/rust/bitbox02/src/hal/memory.rs
+++ b/src/rust/bitbox02/src/hal/memory.rs
@@ -227,6 +227,10 @@ impl Memory for BitBox02Memory {
crate::memory::smarteeprom_reset_unlock_attempts()
}
+ fn get_io_protection_key(&mut self, out: &mut [u8; 32]) {
+ crate::memory::get_io_protection_key(out)
+ }
+
fn get_salt_root(&mut self) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
crate::memory::get_salt_root()
}
diff --git a/src/rust/bitbox02/src/memory.rs b/src/rust/bitbox02/src/memory.rs
index b4ce8df..88e94a0 100644
--- a/src/rust/bitbox02/src/memory.rs
+++ b/src/rust/bitbox02/src/memory.rs
@@ -335,8 +335,7 @@ fn set_attestation_certificate(
}
}
-#[cfg(test)]
-fn get_io_protection_key(out: &mut [u8; 32]) {
+pub(crate) fn get_io_protection_key(out: &mut [u8; 32]) {
unsafe { bitbox02_sys::memory_get_io_protection_key(out.as_mut_ptr()) }
}
Why this scored 17/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.