What changed, and why it matters
This commit rewrites the secure-chip abstraction layer from C to Rust. It is a refactoring/porting change: the same underlying ATECC and Optiga hardware drivers are still used, but the dispatch logic that picks between them and the test fake is now implemented in Rust. No new security vulnerability is visible in the diff, but any rewrite of security-critical code carries a risk of subtle behavior changes.
Treat this as a high-risk refactoring: run full hardware-in-the-loop tests on both ATECC and Optiga variants, verify that backend selection, setup, KDF, password stretching, attestation, and U2F counter semantics are unchanged, and review the Rust unsafe blocks and SyncCell usage for soundness. No immediate patch is required for a disclosed vulnerability.
Security signals we found
Large rewrite of security-critical secure-chip abstraction layer
New unsafe FFI bindings to existing atecc_* and optiga_* functions
Test fake uses hardcoded HMAC keys (deterministic, expected for unit tests only)
Removal of C ABORT_IF_NULL guards; Rust code assumes backend is initialized before use
rust_securechip_init() now always returns true and writes a backend into a SyncCell
Evidence from the diff
The commit removes src/securechip/securechip.c and test/hardware-fakes/src/fake_securechip.c, replacing them with Rust modules bitbox02/src/securechip.rs, securechip/imp.rs and securechip/imp_fake.rs. C call sites are updated to use rust_securechip_ FFI entry points generated by cbindgen. The Rust implementation selects ATECC or Optiga backends based on memory::get_securechip_type() and forwards to the existing atecc_ and optiga_* C functions. The fake implementation for tests/simulators uses HMAC-SHA256 with hardcoded keys, matching the prior fake behavior. The public Rust API remains largely the same, with added doc comments and a new test for invalid password-stretch algorithm rejection.
Changed components
securechip abstraction layerRust bitbox02 crateATECC/Optiga secure chip dispatchU2F counter operationspassword stretching and KDF interfacesfactory setup toolinghardware test fakesInspect captured patch +460 / −451
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 16095a2..815a4c1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -140,7 +140,6 @@ set(PLATFORM-BITBOX02-SOURCES ${PLATFORM-BITBOX02-SOURCES} PARENT_SCOPE)
set(SECURECHIP-SOURCES
${CMAKE_SOURCE_DIR}/src/atecc/atecc.c
- ${CMAKE_SOURCE_DIR}/src/securechip/securechip.c
${CMAKE_SOURCE_DIR}/src/optiga/pal/pal.c
${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_gpio.c
${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_i2c.c
diff --git a/src/atecc/atecc.h b/src/atecc/atecc.h
index 69228d8..dbdf4ab 100644
--- a/src/atecc/atecc.h
+++ b/src/atecc/atecc.h
@@ -4,7 +4,6 @@
#define _ATECC_H_
/* ATECC implementation of the secure chip functions. */
-/* See securechip.h for the docstrings of the individual functions. */
#include "compiler_util.h"
#include "securechip/securechip.h"
diff --git a/src/common_main.c b/src/common_main.c
index 9eab488..a1d6f0e 100644
--- a/src/common_main.c
+++ b/src/common_main.c
@@ -11,6 +11,7 @@
#include "screen.h"
#include "securechip/securechip.h"
#include "util.h"
+#include <rust/rust.h>
extern void __attribute__((noreturn)) __stack_chk_fail(void);
void __attribute__((noreturn)) __stack_chk_fail(void)
@@ -49,12 +50,12 @@ void common_main(void)
/* Enable/configure SmartEEPROM. */
smarteeprom_bb02_config();
- if (!securechip_init()) {
+ if (!rust_securechip_init()) {
AbortAutoenter("Failed to detect securechip");
}
- // securechip_setup must come after memory_setup, so the io/auth keys to be
+ // rust_securechip_setup must come after memory_setup, so the io/auth keys to be
// used are already initialized.
- int securechip_result = securechip_setup(&_securechip_interface_functions);
+ int securechip_result = rust_securechip_setup(&_securechip_interface_functions);
if (securechip_result) {
char errmsg[100] = {0};
snprintf(
diff --git a/src/factorysetup.c b/src/factorysetup.c
index 583d5ca..be591eb 100644
--- a/src/factorysetup.c
+++ b/src/factorysetup.c
@@ -929,7 +929,7 @@ static void _api_msg(const uint8_t* input, size_t in_len, uint8_t* output, size_
case OP_GENKEY: {
screen_print_debug("generating pubkey...", 0);
uint8_t pubkey[64];
- if (!securechip_gen_attestation_key(pubkey)) {
+ if (!rust_securechip_gen_attestation_key(pubkey)) {
screen_print_debug("generating pubkey\nfailed", 0);
result = ERR_FAILED;
break;
@@ -985,13 +985,13 @@ static void _api_msg(const uint8_t* input, size_t in_len, uint8_t* output, size_
break;
}
case OP_SC_ROLLKEYS:
- if (!securechip_reset_keys()) {
+ if (!rust_securechip_reset_keys()) {
screen_print_debug("resetting securechip keys: failed", 0);
result = ERR_FAILED;
break;
}
screen_print_debug("resetting securechip keys: success", 100);
- if (!securechip_u2f_counter_set(0)) {
+ if (!rust_securechip_u2f_counter_set(0)) {
screen_print_debug("reset u2f counter", 0);
result = ERR_FAILED;
break;
diff --git a/src/optiga/optiga.h b/src/optiga/optiga.h
index d3f6fa6..1dca574 100644
--- a/src/optiga/optiga.h
+++ b/src/optiga/optiga.h
@@ -4,7 +4,6 @@
#define _OPTIGA_H_
/* Optiga Trust M implementation of the secure chip functions. */
-/* See securechip.h for the docstrings of the individual functions. */
#include "compiler_util.h"
#include "securechip/securechip.h"
diff --git a/src/random.c b/src/random.c
index 7f62d40..82780cb 100644
--- a/src/random.c
+++ b/src/random.c
@@ -60,8 +60,8 @@ static void random_32_bytes_sec(uint8_t* buf)
random[i] = rand();
}
#else
- if (!securechip_random(random)) {
- Abort("Abort: securechip_random");
+ if (!rust_securechip_random(random)) {
+ Abort("Abort: rust_securechip_random");
}
#endif
for (size_t i = 0; i < sizeof(random); i++) {
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 338a954..0f1dafc 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -214,6 +214,8 @@ dependencies = [
"futures-lite",
"grounded",
"hex_lit",
+ "hmac",
+ "sha2",
"util",
"zeroize",
]
diff --git a/src/rust/bitbox02-cbindgen.toml b/src/rust/bitbox02-cbindgen.toml
index 918e708..961f6af 100644
--- a/src/rust/bitbox02-cbindgen.toml
+++ b/src/rust/bitbox02-cbindgen.toml
@@ -8,6 +8,7 @@ include_version = true
header = '''
#include "platform/platform_config.h"
+#include "securechip/securechip.h"
#include "util.h"
#include <ui/components/confirm.h>
@@ -33,6 +34,7 @@ extra_bindings = ["bitbox02", "bitbox02-rust", "util", "bitbox-aes", "bitbox-da1
exclude = [
"malloc",
"free",
+ "Abort",
]
[defines]
diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs
index 619d1a4..3a947b1 100644
--- a/src/rust/bitbox02-sys/build.rs
+++ b/src/rust/bitbox02-sys/build.rs
@@ -45,6 +45,9 @@ const ALLOWLIST_TYPES: &[&str] = &[
"RustByteQueue",
"RustUsbReportQueue",
"securechip_error_t",
+ "securechip_interface_functions_t",
+ "securechip_model_t",
+ "securechip_password_stretch_algo_t",
"trinary_input_string_params_t",
"UG_COLOR",
"upside_down_t",
@@ -66,6 +69,18 @@ const ALLOWLIST_FNS: &[&str] = &[
"confirm_create",
"confirm_transaction_address_create",
"confirm_transaction_fee_create",
+ "atecc_attestation_sign",
+ "atecc_gen_attestation_key",
+ "atecc_init_new_password",
+ "atecc_kdf",
+ "atecc_model",
+ "atecc_monotonic_increments_remaining",
+ "atecc_random",
+ "atecc_reset_keys",
+ "atecc_setup",
+ "atecc_stretch_password",
+ "atecc_u2f_counter_inc",
+ "atecc_u2f_counter_set",
"delay_cancel",
"delay_init_ms",
"delay_ms",
@@ -126,6 +141,18 @@ const ALLOWLIST_FNS: &[&str] = &[
"memory_spi_get_active_ble_firmware_version",
"menu_create",
"orientation_arrows_create",
+ "optiga_attestation_sign",
+ "optiga_gen_attestation_key",
+ "optiga_init_new_password",
+ "optiga_kdf_external",
+ "optiga_model",
+ "optiga_monotonic_increments_remaining",
+ "optiga_random",
+ "optiga_reset_keys",
+ "optiga_setup",
+ "optiga_stretch_password",
+ "optiga_u2f_counter_inc",
+ "optiga_u2f_counter_set",
"platform_product",
"printf",
"progress_create",
@@ -154,14 +181,6 @@ const ALLOWLIST_FNS: &[&str] = &[
"sd_load_bin",
"sd_write_bin",
"sdcard_create",
- "securechip_attestation_sign",
- "securechip_init_new_password",
- "securechip_kdf",
- "securechip_reset_keys",
- "securechip_model",
- "securechip_monotonic_increments_remaining",
- "securechip_stretch_password",
- "securechip_u2f_counter_set",
"smarteeprom_bb02_config",
"smarteeprom_disable",
"smarteeprom_is_enabled",
@@ -294,7 +313,6 @@ const FAKEHARDWARE_SOURCES: &[&str] = &[
"test/hardware-fakes/src/fake_memory.c",
"test/hardware-fakes/src/fake_qtouch.c",
"test/hardware-fakes/src/fake_screen.c",
- "test/hardware-fakes/src/fake_securechip.c",
"test/hardware-fakes/src/fake_smarteeprom.c",
"test/hardware-fakes/src/fake_spi_mem.c",
];
diff --git a/src/rust/bitbox02-sys/wrapper.h b/src/rust/bitbox02-sys/wrapper.h
index 8d166c6..22519a5 100644
--- a/src/rust/bitbox02-sys/wrapper.h
+++ b/src/rust/bitbox02-sys/wrapper.h
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
+#include <atecc/atecc.h>
#include <da14531/da14531.h>
#include <da14531/da14531_handler.h>
#include <da14531/da14531_protocol.h>
@@ -11,13 +12,13 @@
#include <memory/memory_spi.h>
#include <memory/smarteeprom.h>
#include <memory/spi_mem.h>
+#include <optiga/optiga.h>
#include <platform/driver_init.h>
#include <platform/platform_init.h>
#include <random.h>
#include <reset.h>
#include <screen.h>
#include <sd.h>
-#include <securechip/securechip.h>
#include <system.h>
#include <time.h>
#include <u2f.h>
diff --git a/src/rust/bitbox02/Cargo.toml b/src/rust/bitbox02/Cargo.toml
index af54fc7..370a7fc 100644
--- a/src/rust/bitbox02/Cargo.toml
+++ b/src/rust/bitbox02/Cargo.toml
@@ -20,11 +20,13 @@ zeroize = { workspace = true }
bip39 = { workspace = true }
futures-lite = { workspace = true }
grounded = { workspace = true }
+hmac = { workspace = true }
+sha2 = { workspace = true }
+hex_lit = { workspace = true }
[dev-dependencies]
bitbox-aes = { path = "../bitbox-aes" }
bitbox-framed-serial-link = { path = "../bitbox-framed-serial-link" }
-hex_lit = { workspace = true }
[features]
# Only to be enabled in unit tests and simulators
diff --git a/src/rust/bitbox02/src/securechip.rs b/src/rust/bitbox02/src/securechip.rs
index 3b6e35e..388d793 100644
--- a/src/rust/bitbox02/src/securechip.rs
+++ b/src/rust/bitbox02/src/securechip.rs
@@ -59,105 +59,76 @@ impl Error {
}
}
+#[cfg_attr(
+ any(
+ test,
+ feature = "testing",
+ feature = "c-unit-testing",
+ feature = "simulator-graphical"
+ ),
+ path = "securechip/imp_fake.rs"
+)]
+mod imp;
+
+/// Signs a 32-byte attestation challenge and writes the raw 64-byte P-256 signature to
+/// `signature`.
pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Result<(), ()> {
- match unsafe {
- bitbox02_sys::securechip_attestation_sign(challenge.as_ptr(), signature.as_mut_ptr())
- } {
- true => Ok(()),
- false => Err(()),
- }
+ imp::attestation_sign(challenge, signature)
}
+/// Returns the remaining number of secure-chip monotonic counter increments.
pub fn monotonic_increments_remaining() -> Result<u32, ()> {
- let mut result: u32 = 0;
- match unsafe { bitbox02_sys::securechip_monotonic_increments_remaining(&mut result as _) } {
- true => Ok(result),
- false => Err(()),
- }
+ imp::monotonic_increments_remaining()
}
+/// Resets the secure-chip objects involved in password stretching.
pub fn reset_keys() -> Result<(), ()> {
- match unsafe { bitbox02_sys::securechip_reset_keys() } {
- true => Ok(()),
- false => Err(()),
- }
+ imp::reset_keys()
}
+/// Prepares the secure chip for a new password and returns the stretched password.
+///
+/// This reinitializes the secure-chip state used for password derivation and returns the same
+/// 32-byte value as [`stretch_password`] for the same `password` and
+/// `password_stretch_algo`, but may require fewer secure-chip operations.
pub fn init_new_password(
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Zeroizing<Vec<u8>>, Error> {
- let password = util::strings::str_to_cstr_vec_zeroizing(password)
- .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS))?;
- let mut stretched = Zeroizing::new(vec![0u8; 32]);
- let status = unsafe {
- bitbox02_sys::securechip_init_new_password(
- password.as_ptr().cast(),
- password_stretch_algo,
- stretched.as_mut_ptr(),
- )
- };
- if status == 0 {
- Ok(stretched)
- } else {
- Err(Error::from_status(status))
- }
+ imp::init_new_password(password, password_stretch_algo)
}
+/// Stretches `password` using secrets stored in the secure chip.
+///
+/// The returned value is always 32 bytes long. Calling this function increments the relevant
+/// secure-chip monotonic counter.
pub fn stretch_password(
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Zeroizing<Vec<u8>>, Error> {
- let password = util::strings::str_to_cstr_vec_zeroizing(password)
- .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS))?;
- let mut stretched = Zeroizing::new(vec![0u8; 32]);
- let status = unsafe {
- bitbox02_sys::securechip_stretch_password(
- password.as_ptr().cast(),
- password_stretch_algo,
- stretched.as_mut_ptr(),
- )
- };
- if status == 0 {
- Ok(stretched)
- } else {
- Err(Error::from_status(status))
- }
+ imp::stretch_password(password, password_stretch_algo)
}
-/// Perform the secure chip KDF with the message in `msg` and return the zeroizing 32-byte result.
+/// Runs the secure-chip KDF with `msg` and returns the zeroizing 32-byte result.
+///
+/// This must not increment a monotonic counter.
+///
+/// `msg` must be at most 127 bytes long.
pub fn kdf(msg: &[u8]) -> Result<Zeroizing<Vec<u8>>, Error> {
- let mut result = Zeroizing::new(vec![0u8; 32]);
- let status =
- unsafe { bitbox02_sys::securechip_kdf(msg.as_ptr(), msg.len(), result.as_mut_ptr()) };
- if status == 0 {
- Ok(result)
- } else {
- Err(Error::from_status(status))
- }
+ imp::kdf(msg)
}
#[cfg(feature = "app-u2f")]
-#[cfg(not(feature = "testing"))]
+/// Sets the U2F counter to `counter`.
+///
+/// This is intended for initialization only.
pub fn u2f_counter_set(counter: u32) -> Result<(), ()> {
- match unsafe { bitbox02_sys::securechip_u2f_counter_set(counter) } {
- true => Ok(()),
- false => Err(()),
- }
-}
-
-#[cfg(feature = "app-u2f")]
-#[cfg(feature = "testing")]
-pub fn u2f_counter_set(_counter: u32) -> Result<(), ()> {
- Ok(())
+ imp::u2f_counter_set(counter)
}
+/// Returns the detected secure-chip model.
pub fn model() -> Result<Model, ()> {
- let mut ver = core::mem::MaybeUninit::uninit();
- match unsafe { bitbox02_sys::securechip_model(ver.as_mut_ptr()) } {
- true => Ok(unsafe { ver.assume_init() }),
- false => Err(()),
- }
+ imp::model()
}
#[cfg(test)]
@@ -198,9 +169,22 @@ mod tests {
#[test]
fn test_kdf() {
- // Matches the deterministic HMAC result returned by test/hardware-fakes/src/fake_securechip.c.
+ // Matches the deterministic host/test fake securechip KDF.
let result = kdf(b"stub input").unwrap();
let expected = hex!("3d7caa0407f18f6b15a6202843c883f326d614996df67940af210d91aff5b9c8");
assert_eq!(result.as_slice(), expected.as_slice());
}
+
+ #[test]
+ fn test_init_new_password_invalid_password_stretch_algo() {
+ assert_eq!(
+ init_new_password(
+ "password",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0
+ ),
+ Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO,
+ )),
+ );
+ }
}
diff --git a/src/rust/bitbox02/src/securechip/imp.rs b/src/rust/bitbox02/src/securechip/imp.rs
new file mode 100644
index 0000000..bf836fa
--- /dev/null
+++ b/src/rust/bitbox02/src/securechip/imp.rs
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use super::*;
+use core::ffi::{c_char, c_int};
+use util::cell::SyncCell;
+
+#[derive(Copy, Clone)]
+enum Backend {
+ Atecc,
+ Optiga,
+}
+
+static BACKEND: SyncCell<Option<Backend>> = SyncCell::new(None);
+
+fn backend() -> Backend {
+ BACKEND.read().unwrap()
+}
+
+pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Result<(), ()> {
+ match unsafe { attestation_sign_ffi(challenge.as_ptr(), signature.as_mut_ptr()) } {
+ true => Ok(()),
+ false => Err(()),
+ }
+}
+
+unsafe fn attestation_sign_ffi(challenge: *const u8, signature_out: *mut u8) -> bool {
+ match backend() {
+ Backend::Atecc => unsafe { bitbox02_sys::atecc_attestation_sign(challenge, signature_out) },
+ Backend::Optiga => unsafe {
+ bitbox02_sys::optiga_attestation_sign(challenge, signature_out)
+ },
+ }
+}
+
+pub fn monotonic_increments_remaining() -> Result<u32, ()> {
+ let mut result = 0u32;
+ match unsafe { monotonic_increments_remaining_ffi(&mut result) } {
+ true => Ok(result),
+ false => Err(()),
+ }
+}
+
+unsafe fn monotonic_increments_remaining_ffi(remaining_out: *mut u32) -> bool {
+ match backend() {
+ Backend::Atecc => unsafe {
+ bitbox02_sys::atecc_monotonic_increments_remaining(remaining_out)
+ },
+ Backend::Optiga => unsafe {
+ bitbox02_sys::optiga_monotonic_increments_remaining(remaining_out)
+ },
+ }
+}
+
+pub fn reset_keys() -> Result<(), ()> {
+ match reset_keys_ffi() {
+ true => Ok(()),
+ false => Err(()),
+ }
+}
+
+fn reset_keys_ffi() -> bool {
+ match backend() {
+ Backend::Atecc => unsafe { bitbox02_sys::atecc_reset_keys() },
+ Backend::Optiga => unsafe { bitbox02_sys::optiga_reset_keys() },
+ }
+}
+
+pub fn init_new_password(
+ password: &str,
+ password_stretch_algo: PasswordStretchAlgo,
+) -> Result<Zeroizing<Vec<u8>>, Error> {
+ let password = util::strings::str_to_cstr_vec_zeroizing(password)
+ .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS))?;
+ let mut stretched = Zeroizing::new(vec![0u8; 32]);
+ let status = unsafe {
+ init_new_password_ffi(
+ password.as_ptr().cast(),
+ password_stretch_algo,
+ stretched.as_mut_ptr(),
+ )
+ };
+ if status == 0 {
+ Ok(stretched)
+ } else {
+ Err(Error::from_status(status))
+ }
+}
+
+unsafe fn init_new_password_ffi(
+ password: *const c_char,
+ password_stretch_algo: PasswordStretchAlgo,
+ stretched_out: *mut u8,
+) -> c_int {
+ match backend() {
+ Backend::Atecc => unsafe {
+ bitbox02_sys::atecc_init_new_password(password, password_stretch_algo, stretched_out)
+ },
+ Backend::Optiga => unsafe {
+ bitbox02_sys::optiga_init_new_password(password, password_stretch_algo, stretched_out)
+ },
+ }
+}
+
+pub fn stretch_password(
+ password: &str,
+ password_stretch_algo: PasswordStretchAlgo,
+) -> Result<Zeroizing<Vec<u8>>, Error> {
+ let password = util::strings::str_to_cstr_vec_zeroizing(password)
+ .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS))?;
+ let mut stretched = Zeroizing::new(vec![0u8; 32]);
+ let status = unsafe {
+ stretch_password_ffi(
+ password.as_ptr().cast(),
+ password_stretch_algo,
+ stretched.as_mut_ptr(),
+ )
+ };
+ if status == 0 {
+ Ok(stretched)
+ } else {
+ Err(Error::from_status(status))
+ }
+}
+
+unsafe fn stretch_password_ffi(
+ password: *const c_char,
+ password_stretch_algo: PasswordStretchAlgo,
+ stretched_out: *mut u8,
+) -> c_int {
+ match backend() {
+ Backend::Atecc => unsafe {
+ bitbox02_sys::atecc_stretch_password(password, password_stretch_algo, stretched_out)
+ },
+ Backend::Optiga => unsafe {
+ bitbox02_sys::optiga_stretch_password(password, password_stretch_algo, stretched_out)
+ },
+ }
+}
+
+/// Perform the secure chip KDF with the message in `msg` and return the zeroizing 32-byte
+/// result.
+pub fn kdf(msg: &[u8]) -> Result<Zeroizing<Vec<u8>>, Error> {
+ let mut result = Zeroizing::new(vec![0u8; 32]);
+ let status = unsafe { kdf_ffi(msg.as_ptr(), msg.len(), result.as_mut_ptr()) };
+ if status == 0 {
+ Ok(result)
+ } else {
+ Err(Error::from_status(status))
+ }
+}
+
+unsafe fn kdf_ffi(msg: *const u8, len: usize, kdf_out: *mut u8) -> c_int {
+ match backend() {
+ Backend::Atecc => unsafe { bitbox02_sys::atecc_kdf(msg, len, kdf_out) },
+ Backend::Optiga => unsafe { bitbox02_sys::optiga_kdf_external(msg, len, kdf_out) },
+ }
+}
+
+#[cfg(feature = "app-u2f")]
+pub fn u2f_counter_set(counter: u32) -> Result<(), ()> {
+ match u2f_counter_set_ffi(counter) {
+ true => Ok(()),
+ false => Err(()),
+ }
+}
+
+fn u2f_counter_set_ffi(counter: u32) -> bool {
+ match backend() {
+ Backend::Atecc => unsafe { bitbox02_sys::atecc_u2f_counter_set(counter) },
+ Backend::Optiga => unsafe { bitbox02_sys::optiga_u2f_counter_set(counter) },
+ }
+}
+
+pub fn model() -> Result<Model, ()> {
+ let mut model = core::mem::MaybeUninit::uninit();
+ match unsafe { model_ffi(model.as_mut_ptr()) } {
+ true => Ok(unsafe { model.assume_init() }),
+ false => Err(()),
+ }
+}
+
+unsafe fn model_ffi(model_out: *mut Model) -> bool {
+ match backend() {
+ Backend::Atecc => unsafe { bitbox02_sys::atecc_model(model_out) },
+ Backend::Optiga => unsafe { bitbox02_sys::optiga_model(model_out) },
+ }
+}
+
+unsafe fn gen_attestation_key_ffi(pubkey_out: *mut u8) -> bool {
+ match backend() {
+ Backend::Atecc => unsafe { bitbox02_sys::atecc_gen_attestation_key(pubkey_out) },
+ Backend::Optiga => unsafe { bitbox02_sys::optiga_gen_attestation_key(pubkey_out) },
+ }
+}
+
+unsafe fn random_ffi(rand_out: *mut u8) -> bool {
+ match backend() {
+ Backend::Atecc => unsafe { bitbox02_sys::atecc_random(rand_out) },
+ Backend::Optiga => unsafe { bitbox02_sys::optiga_random(rand_out) },
+ }
+}
+
+/// Discovers which secure chip is present and selects the matching backend for subsequent
+/// `rust_securechip_*` calls.
+///
+/// Returns `true` on success.
+#[unsafe(no_mangle)]
+pub extern "C" fn rust_securechip_init() -> bool {
+ BACKEND.write(Some(match crate::memory::get_securechip_type() {
+ Ok(crate::memory::SecurechipType::Optiga) => Backend::Optiga,
+ Ok(crate::memory::SecurechipType::Atecc) | Err(()) => Backend::Atecc,
+ }));
+ true
+}
+
+/// Initializes the backend-specific secure chip communication.
+///
+/// On the first successful call, the selected backend may also configure and lock the secure
+/// chip as needed.
+///
+/// Returns `0` on success. Negative values are [`SecureChipError`] codes. Positive values are
+/// backend-specific status codes from CryptoAuthLib or the Optiga library.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn rust_securechip_setup(
+ ifs: *const bitbox02_sys::securechip_interface_functions_t,
+) -> c_int {
+ match backend() {
+ Backend::Atecc => unsafe { bitbox02_sys::atecc_setup(ifs) },
+ Backend::Optiga => unsafe { bitbox02_sys::optiga_setup(ifs) },
+ }
+}
+
+/// Resets the secure-chip objects involved in password stretching.
+#[unsafe(no_mangle)]
+pub extern "C" fn rust_securechip_reset_keys() -> bool {
+ reset_keys_ffi()
+}
+
+/// Generates a new device attestation key and writes the public key to `pubkey_out`.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn rust_securechip_gen_attestation_key(pubkey_out: *mut u8) -> bool {
+ unsafe { gen_attestation_key_ffi(pubkey_out) }
+}
+
+/// Fills `rand_out` with 32 bytes of randomness from the secure chip.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn rust_securechip_random(rand_out: *mut u8) -> bool {
+ unsafe { random_ffi(rand_out) }
+}
+
+/// Sets the U2F counter to `counter`.
+///
+/// This is intended for initialization only.
+#[unsafe(no_mangle)]
+pub extern "C" fn rust_securechip_u2f_counter_set(counter: u32) -> bool {
+ u2f_counter_set_ffi(counter)
+}
+
+#[cfg(feature = "app-u2f")]
+/// Increments the U2F counter and writes the current value to `counter`.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn rust_securechip_u2f_counter_inc(counter: *mut u32) -> bool {
+ match backend() {
+ Backend::Atecc => unsafe { bitbox02_sys::atecc_u2f_counter_inc(counter) },
+ Backend::Optiga => unsafe { bitbox02_sys::optiga_u2f_counter_inc(counter) },
+ }
+}
diff --git a/src/rust/bitbox02/src/securechip/imp_fake.rs b/src/rust/bitbox02/src/securechip/imp_fake.rs
new file mode 100644
index 0000000..22b7533
--- /dev/null
+++ b/src/rust/bitbox02/src/securechip/imp_fake.rs
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use super::*;
+use hex_lit::hex;
+use hmac::{Hmac, Mac};
+use sha2::Sha256;
+
+const PASSWORD_STRETCH_KEY: &[u8] = b"unit-test";
+const KDF_KEY: [u8; 32] = hex!("d2e1e6b18b6c6b08433edbc1d168c1a0043774a4221877e79ed56684be5ac01b");
+
+#[cfg(feature = "app-u2f")]
+static U2F_COUNTER: util::cell::SyncCell<u32> = util::cell::SyncCell::new(0);
+
+type HmacSha256 = Hmac<Sha256>;
+
+fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
+ let mut mac = HmacSha256::new_from_slice(key).unwrap();
+ mac.update(data);
+ let result = mac.finalize().into_bytes();
+ let mut out = [0u8; 32];
+ out.copy_from_slice(&result);
+ out
+}
+
+pub fn attestation_sign(_challenge: &[u8; 32], _signature: &mut [u8; 64]) -> Result<(), ()> {
+ Err(())
+}
+
+pub fn monotonic_increments_remaining() -> Result<u32, ()> {
+ Ok(1)
+}
+
+pub fn reset_keys() -> Result<(), ()> {
+ Ok(())
+}
+
+pub fn init_new_password(
+ password: &str,
+ password_stretch_algo: PasswordStretchAlgo,
+) -> Result<Zeroizing<Vec<u8>>, Error> {
+ if password_stretch_algo != PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1 {
+ return Err(Error::from_status(
+ SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO as i32,
+ ));
+ }
+ Ok(Zeroizing::new(
+ hmac_sha256(PASSWORD_STRETCH_KEY, password.as_bytes()).to_vec(),
+ ))
+}
+
+pub fn stretch_password(
+ password: &str,
+ _password_stretch_algo: PasswordStretchAlgo,
+) -> Result<Zeroizing<Vec<u8>>, Error> {
+ Ok(Zeroizing::new(
+ hmac_sha256(PASSWORD_STRETCH_KEY, password.as_bytes()).to_vec(),
+ ))
+}
+
+/// Perform the secure chip KDF with the message in `msg` and return the zeroizing 32-byte
+/// result.
+pub fn kdf(msg: &[u8]) -> Result<Zeroizing<Vec<u8>>, Error> {
+ Ok(Zeroizing::new(hmac_sha256(&KDF_KEY, msg).to_vec()))
+}
+
+#[cfg(feature = "app-u2f")]
+pub fn u2f_counter_set(counter: u32) -> Result<(), ()> {
+ U2F_COUNTER.write(counter);
+ Ok(())
+}
+
+pub fn model() -> Result<Model, ()> {
+ Ok(Model::ATECC_ATECC608B)
+}
+
+#[cfg(feature = "app-u2f")]
+/// Increments the fake host-side U2F counter and writes the current value to `counter`.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn rust_securechip_u2f_counter_inc(counter: *mut u32) -> bool {
+ assert!(!counter.is_null());
+ let current = U2F_COUNTER.read();
+ U2F_COUNTER.write(current.wrapping_add(1));
+ unsafe { *counter = current };
+ true
+}
diff --git a/src/securechip/securechip.c b/src/securechip/securechip.c
deleted file mode 100644
index 893eb10..0000000
--- a/src/securechip/securechip.c
+++ /dev/null
@@ -1,167 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-#include "securechip.h"
-
-#include <atecc/atecc.h>
-#include <hardfault.h>
-#include <optiga/optiga.h>
-#include <rust/rust.h>
-
-typedef struct {
- int (*setup)(const securechip_interface_functions_t* fns);
- int (*kdf)(const uint8_t* msg, size_t msg_len, uint8_t* kdf_out);
- int (*init_new_password)(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out);
- int (*stretch_password)(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out);
- bool (*reset_keys)(void);
- bool (*gen_attestation_key)(uint8_t* pubkey_out);
- bool (*attestation_sign)(const uint8_t* challenge, uint8_t* signature_out);
- bool (*monotonic_increments_remaining)(uint32_t* remaining_out);
- bool (*random)(uint8_t* rand_out);
-#if APP_U2F == 1 || FACTORYSETUP == 1
- bool (*u2f_counter_set)(uint32_t counter);
-#endif
-#if APP_U2F == 1
- bool (*u2f_counter_inc)(uint32_t* counter);
-#endif
- bool (*model)(securechip_model_t* model_out);
-} securechip_crypt_interface_t;
-
-static securechip_crypt_interface_t _fns = {0};
-
-// Detect if we have atecc or optiga chip and set interface functions
-bool securechip_init(void)
-{
- switch (rust_memory_get_securechip_type()) {
- case RUST_MEMORY_SECURECHIP_TYPE_OPTIGA:
- _fns.setup = optiga_setup;
- _fns.kdf = optiga_kdf_external;
- _fns.init_new_password = optiga_init_new_password;
- _fns.stretch_password = optiga_stretch_password;
- _fns.reset_keys = optiga_reset_keys;
- _fns.gen_attestation_key = optiga_gen_attestation_key;
- _fns.attestation_sign = optiga_attestation_sign;
- _fns.monotonic_increments_remaining = optiga_monotonic_increments_remaining;
- _fns.random = optiga_random;
-#if APP_U2F == 1 || FACTORYSETUP == 1
- _fns.u2f_counter_set = optiga_u2f_counter_set;
-#endif
-#if APP_U2F == 1
- _fns.u2f_counter_inc = optiga_u2f_counter_inc;
-#endif
- _fns.model = optiga_model;
- break;
- case RUST_MEMORY_SECURECHIP_TYPE_ATECC:
- default:
- _fns.setup = atecc_setup;
- _fns.kdf = atecc_kdf;
- _fns.init_new_password = atecc_init_new_password;
- _fns.stretch_password = atecc_stretch_password;
- _fns.reset_keys = atecc_reset_keys;
- _fns.gen_attestation_key = atecc_gen_attestation_key;
- _fns.attestation_sign = atecc_attestation_sign;
- _fns.monotonic_increments_remaining = atecc_monotonic_increments_remaining;
- _fns.random = atecc_random;
-#if APP_U2F == 1 || FACTORYSETUP == 1
- _fns.u2f_counter_set = atecc_u2f_counter_set;
-#endif
-#if APP_U2F == 1
- _fns.u2f_counter_inc = atecc_u2f_counter_inc;
-#endif
- _fns.model = atecc_model;
- break;
- }
- return true;
-}
-
-#define ABORT_IF_NULL(fn) \
- do { \
- if (_fns.fn == 0) { \
- Abort("No " #fn " function"); \
- } \
- } while (0)
-
-int securechip_setup(const securechip_interface_functions_t* ifs)
-{
- ABORT_IF_NULL(setup);
- return _fns.setup(ifs);
-}
-
-int securechip_kdf(const uint8_t* msg, size_t msg_len, uint8_t* mac_out)
-{
- ABORT_IF_NULL(kdf);
- return _fns.kdf(msg, msg_len, mac_out);
-}
-
-int securechip_init_new_password(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out)
-{
- ABORT_IF_NULL(init_new_password);
- return _fns.init_new_password(password, password_stretch_algo, stretched_out);
-}
-
-int securechip_stretch_password(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out)
-{
- ABORT_IF_NULL(stretch_password);
- return _fns.stretch_password(password, password_stretch_algo, stretched_out);
-}
-
-bool securechip_reset_keys(void)
-{
- ABORT_IF_NULL(reset_keys);
- return _fns.reset_keys();
-}
-
-bool securechip_gen_attestation_key(uint8_t* pubkey_out)
-{
- ABORT_IF_NULL(gen_attestation_key);
- return _fns.gen_attestation_key(pubkey_out);
-}
-
-bool securechip_attestation_sign(const uint8_t* challenge, uint8_t* signature_out)
-{
- ABORT_IF_NULL(attestation_sign);
- return _fns.attestation_sign(challenge, signature_out);
-}
-
-bool securechip_monotonic_increments_remaining(uint32_t* remaining_out)
-{
- ABORT_IF_NULL(monotonic_increments_remaining);
- return _fns.monotonic_increments_remaining(remaining_out);
-}
-
-bool securechip_random(uint8_t* rand_out)
-{
- ABORT_IF_NULL(random);
- return _fns.random(rand_out);
-}
-
-#if APP_U2F == 1 || FACTORYSETUP == 1
-bool securechip_u2f_counter_set(uint32_t counter)
-{
- ABORT_IF_NULL(u2f_counter_set);
- return _fns.u2f_counter_set(counter);
-}
-#endif
-#if APP_U2F == 1
-bool securechip_u2f_counter_inc(uint32_t* counter)
-{
- ABORT_IF_NULL(u2f_counter_inc);
- return _fns.u2f_counter_inc(counter);
-}
-#endif
-bool securechip_model(securechip_model_t* model_out)
-{
- ABORT_IF_NULL(model);
- return _fns.model(model_out);
-}
diff --git a/src/securechip/securechip.h b/src/securechip/securechip.h
index 94ed188..924a155 100644
--- a/src/securechip/securechip.h
+++ b/src/securechip/securechip.h
@@ -64,112 +64,7 @@ typedef struct {
void (*const random_32_bytes)(uint8_t* buf);
} securechip_interface_functions_t;
-/**
- * Discovers what secure chip is used and configures the module to communicate with it.
- * @return True if success
- */
-USE_RESULT bool securechip_init(void);
-
-/**
- * Initializes the cryptoauthlib communication, by providing a custom i2c chip
- * communication interface/bridge to cryptoauthlib. On first call, the chip
- * is configured and locked.
- * @param[in] ifs Interface functions.
- * @return 0 on success. Values of `securechip_error_t` if negative. If positive, values of
- * `ATCA_STATUS` for ATECC, values of optiga_lib_return_codes.h for Optiga.
- */
-USE_RESULT int securechip_setup(const securechip_interface_functions_t* ifs);
-
-/**
- * Perform KDF using the key in kdf slot with the input msg.
- * This must not increment a monotonic counter.
- * @param[in] msg Use this msg as input
- * @param[in] len Must be <= 127.
- * @param[out] kdf_out Must have size 32. Result of the kdf will be stored here.
- * Cannot be the same as `msg`.
- * @return 0 on success. Values of `securechip_error_t` if negative. If positive, values of
- * `ATCA_STATUS` for ATECC, values of optiga_lib_return_codes.h for Optiga.
- */
-USE_RESULT int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out);
-
-/**
- * Prepare the securechip for a new password: re-initialize keys used in the derivation,
- * set up monotonic counters, etc.
- * @param[in] password The user password.
- * @param[in] password_stretch_algo the password stretching algorithm that should be used.
- * @param[out] stretched_out the stretched password. Same as calling `securechip_stretch_password()`
- * with the same stretching algo, but more efficient in terms of securechip operations.
- * @return For ATECC: values of `atecc_error_t` if negative, values of `ATCA_STATUS` if positive, 0
- * on success. For Optiga: values of `optiga_error_t` if negative, values of
- * optiga_lib_return_codes.h if positive, 0 on success.
- */
-USE_RESULT int securechip_init_new_password(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out);
-
-/**
- * Stretch password using secrets in the secure chip.
- * Calling this function increments the monotonic counter.
- * @param[in] password The user password.
- * @param[in] password_stretch_algo the password stretching algorithm that should be used.
- * @param[out] stretched_out the stretched password.
- * @return 0 on success. Values of `securechip_error_t` if negative. If positive, values of
- * `ATCA_STATUS` for ATECC, values of optiga_lib_return_codes.h for Optiga.
- */
-USE_RESULT int securechip_stretch_password(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out);
-
-/**
- * Reset the securechip objects involved in the password stretching.
- * @return true on success, false on failure.
- */
-USE_RESULT bool securechip_reset_keys(void);
-
-/**
- * Generates a new attestation device key and outputs the public key.
- * @param[out] pubkey_out
- */
-USE_RESULT bool securechip_gen_attestation_key(uint8_t* pubkey_out);
-
-/**
- * @param[in] msg 32 byte message to sign.
- * @param[out] signature_out must be 64 bytes. R/S P256 signature.
- */
-USE_RESULT bool securechip_attestation_sign(const uint8_t* challenge, uint8_t* signature_out);
-
-/**
- * Retrieves the number of remaining possible counter increments (max value - Counter).
- * The counter is increment when using `securechip_kdf()` (see its docstring).
- * @param[out] remaining_out current value of the monotonic counter.
- * @return false if there was a communication error with the SC.
- */
-USE_RESULT bool securechip_monotonic_increments_remaining(uint32_t* remaining_out);
-
-/**
- * @param[out] rand_out must be 32 bytes.
- */
-USE_RESULT bool securechip_random(uint8_t* rand_out);
-
-#if APP_U2F == 1 || FACTORYSETUP == 1
-/**
- * Set the u2f counter to `counter`. Should only be used for initialization.
- * @param[in] counter Value to set counter to
- * @return True if success
- */
-USE_RESULT bool securechip_u2f_counter_set(uint32_t counter);
-#endif
-
-#if APP_U2F == 1
-/**
- * Monotonically increase the U2F counter and return the current value
- * @param[out] counter Next counter value
- * @return True if success
- */
-USE_RESULT bool securechip_u2f_counter_inc(uint32_t* counter);
-#endif
+/* The common securechip ABI is implemented in Rust and declared in rust/rust.h. */
typedef enum {
ATECC_ATECC608A,
@@ -177,11 +72,4 @@ typedef enum {
OPTIGA_TRUST_M_V3,
} securechip_model_t;
-/**
- * Output the securechip model.
- * @param[out] model_out securechip model
- * @return True if success
- */
-USE_RESULT bool securechip_model(securechip_model_t* model_out);
-
#endif
diff --git a/src/u2f.c b/src/u2f.c
index f8672d9..1af6a76 100644
--- a/src/u2f.c
+++ b/src/u2f.c
@@ -628,7 +628,7 @@ static void _authenticate_continue(const USB_APDU* apdu, Packet* out_packet)
U2F_AUTHENTICATE_RESP* response = (U2F_AUTHENTICATE_RESP*)&buf;
uint32_t counter;
- if (!securechip_u2f_counter_inc(&counter)) {
+ if (!rust_securechip_u2f_counter_inc(&counter)) {
_error(U2F_SW_CONDITIONS_NOT_SATISFIED, out_packet);
return;
}
diff --git a/test/hardware-fakes/src/fake_securechip.c b/test/hardware-fakes/src/fake_securechip.c
deleted file mode 100644
index d25620e..0000000
--- a/test/hardware-fakes/src/fake_securechip.c
+++ /dev/null
@@ -1,77 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-#include <rust/rust.h>
-#include <securechip/securechip.h>
-#include <stdio.h>
-#include <string.h>
-
-static uint32_t _u2f_counter;
-
-// Mocked contents of the securechip kdf slot.
-static const uint8_t _kdfkey[32] =
- "\xd2\xe1\xe6\xb1\x8b\x6c\x6b\x08\x43\x3e\xdb\xc1\xd1\x68\xc1\xa0\x04\x37\x74\xa4\x22\x18\x77"
- "\xe7\x9e\xd5\x66\x84\xbe\x5a\xc0\x1b";
-
-int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out)
-{
- rust_hmac_sha256(_kdfkey, 32, msg, len, kdf_out);
- return 0;
-}
-
-int securechip_init_new_password(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out)
-{
- if (password_stretch_algo != SECURECHIP_PASSWORD_STRETCH_ALGO_V1) {
- // New passwords must use the latest algo.
- return SC_ERR_INVALID_PASSWORD_STRETCH_ALGO;
- }
- return securechip_stretch_password(password, password_stretch_algo, stretched_out);
-}
-int securechip_stretch_password(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out)
-{
- (void)password_stretch_algo;
- uint8_t key[9] = "unit-test";
- rust_hmac_sha256(key, sizeof(key), (const uint8_t*)password, strlen(password), stretched_out);
- return 0;
-}
-
-bool securechip_reset_keys(void)
-{
- return true;
-}
-
-bool securechip_u2f_counter_set(uint32_t counter)
-{
- _u2f_counter = counter;
- return true;
-}
-
-bool securechip_u2f_counter_inc(uint32_t* counter)
-{
- *counter = _u2f_counter++;
- return true;
-}
-
-bool securechip_attestation_sign(const uint8_t* msg, uint8_t* signature_out)
-{
- (void)msg;
- (void)signature_out;
- return false;
-}
-
-bool securechip_monotonic_increments_remaining(uint32_t* remaining_out)
-{
- *remaining_out = 1;
- return true;
-}
-
-bool securechip_model(securechip_model_t* model_out)
-{
- *model_out = ATECC_ATECC608B;
- return true;
-}
diff --git a/test/simulator-graphical-bb03/Cargo.lock b/test/simulator-graphical-bb03/Cargo.lock
index 62f2cec..bd91703 100644
--- a/test/simulator-graphical-bb03/Cargo.lock
+++ b/test/simulator-graphical-bb03/Cargo.lock
@@ -428,6 +428,9 @@ dependencies = [
"bitbox02-sys",
"futures-lite",
"grounded",
+ "hex_lit",
+ "hmac",
+ "sha2",
"util",
"zeroize",
]
diff --git a/test/simulator-graphical/Cargo.lock b/test/simulator-graphical/Cargo.lock
index 98c8d1f..86bee51 100644
--- a/test/simulator-graphical/Cargo.lock
+++ b/test/simulator-graphical/Cargo.lock
@@ -362,6 +362,9 @@ dependencies = [
"bitbox02-sys",
"futures-lite",
"grounded",
+ "hex_lit",
+ "hmac",
+ "sha2",
"util",
"zeroize",
]
Why this scored 34/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.