add bitbox-securechip-sys bindings crate add bitbox-securechip crate
What changed, and why it matters
This commit is a code reorganization: it moves the Rust bindings and wrappers for the BitBox02's secure chip (ATECC/Optiga) out of the general bitbox02-sys crate into two new dedicated crates, bitbox-securechip-sys and bitbox-securechip. It does not change the underlying C secure-chip implementation or add new user-facing behavior. The change is architectural cleanup, not a security fix or vulnerability.
No security action required. Treat as normal code-quality refactoring. If reviewing further, verify that the new bindgen flags (especially -fshort-enums for thumbv7em-none-eabi) produce ABI-compatible layouts with the previous bitbox02-sys bindings and that the allowlists match the old ones exactly.
Security signals we found
Refactoring only: no change to secure-chip cryptographic logic or trust boundaries
New FFI bindings are auto-generated with the same allowlisted functions/types as before
Safe wrappers preserve existing error mapping and zeroization behavior
MAX_UNLOCK_ATTEMPTS constant moved from Rust header include to local C define to reduce coupling
Evidence from the diff
The commit introduces bitbox-securechip-sys (raw bindgen FFI bindings) and bitbox-securechip (safe/idiomatic wrappers) and updates bitbox02 to depend on them. It removes securechip-related allowlist entries from bitbox02-sys/build.rs and wrapper.h, and refactors bitbox02/src/securechip to re-export the new crate’s functionality. The only functional C-side tweak is in src/optiga/optiga.h, replacing an include of rust/rust.h for MAX_UNLOCK_ATTEMPTS with a local #define kept in sync with keystore.rs. No bug fixes, privilege changes, or cryptographic changes are visible in the diff.
Changed components
src/rust/bitbox-securechip-syssrc/rust/bitbox-securechipsrc/rust/bitbox02-syssrc/rust/bitbox02/src/securechipsrc/rust/bitbox02/src/hal/securechip.rssrc/optiga/optiga.hInspect captured patch +695 / −412
diff --git a/src/optiga/optiga.h b/src/optiga/optiga.h
index 1dca574..939535c 100644
--- a/src/optiga/optiga.h
+++ b/src/optiga/optiga.h
@@ -12,7 +12,10 @@
#include <stddef.h>
#include <stdint.h>
-#include <rust/rust.h> // for MAX_UNLOCK_ATTEMPTS
+// Keep in sync with MAX_UNLOCK_ATTEMPTS in keystore.rs.
+#ifndef MAX_UNLOCK_ATTEMPTS
+ #define MAX_UNLOCK_ATTEMPTS 10
+#endif
// The Data Object IDs we use.
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index d926ca1..13b15ec 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -195,6 +195,19 @@ dependencies = [
"hex_lit",
]
+[[package]]
+name = "bitbox-securechip"
+version = "0.1.0"
+dependencies = [
+ "bitbox-securechip-sys",
+ "util",
+ "zeroize",
+]
+
+[[package]]
+name = "bitbox-securechip-sys"
+version = "0.1.0"
+
[[package]]
name = "bitbox-u2fhid"
version = "0.1.0"
@@ -218,6 +231,8 @@ dependencies = [
"bitbox-bytequeue",
"bitbox-framed-serial-link",
"bitbox-hal",
+ "bitbox-securechip",
+ "bitbox-securechip-sys",
"bitbox-usb-report-queue",
"bitbox02-noise",
"bitbox02-sys",
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index 8d1f609..ad538c0 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -18,6 +18,8 @@ members = [
"bitbox02",
"bitbox-secp256k1",
"bitbox02-sys",
+ "bitbox-securechip",
+ "bitbox-securechip-sys",
"bitbox-lvgl-sys",
"erc20_params",
"streaming-silent-payments",
diff --git a/src/rust/bitbox-hal/src/securechip.rs b/src/rust/bitbox-hal/src/securechip.rs
index ed64b1c..a44fab7 100644
--- a/src/rust/bitbox-hal/src/securechip.rs
+++ b/src/rust/bitbox-hal/src/securechip.rs
@@ -48,25 +48,54 @@ pub enum SecureChipError {
}
pub trait SecureChip {
+ /// 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.
fn init_new_password(
&mut self,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<zeroize::Zeroizing<Vec<u8>>, Error>;
+
+ /// 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.
fn stretch_password(
&mut self,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<zeroize::Zeroizing<Vec<u8>>, Error>;
+
+ /// 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.
fn kdf(&mut self, msg: &[u8]) -> Result<zeroize::Zeroizing<Vec<u8>>, Error>;
+
+ /// Signs a 32-byte attestation challenge and writes the raw 64-byte P-256 signature to
+ /// `signature`.
fn attestation_sign(
&mut self,
challenge: &[u8; 32],
signature: &mut [u8; 64],
) -> Result<(), ()>;
+
+ /// Returns the remaining number of secure-chip monotonic counter increments.
fn monotonic_increments_remaining(&mut self) -> Result<u32, ()>;
+
+ /// Returns the detected secure-chip model.
fn model(&mut self) -> Result<Model, ()>;
+
+ /// Resets the secure-chip objects involved in password stretching.
fn reset_keys(&mut self) -> Result<(), ()>;
+
#[cfg(feature = "app-u2f")]
+ /// Sets the U2F counter to `counter`.
+ ///
+ /// This is intended for initialization only.
fn u2f_counter_set(&mut self, counter: u32) -> Result<(), ()>;
}
diff --git a/src/rust/bitbox-securechip-sys/Cargo.toml b/src/rust/bitbox-securechip-sys/Cargo.toml
new file mode 100644
index 0000000..727fc68
--- /dev/null
+++ b/src/rust/bitbox-securechip-sys/Cargo.toml
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: Apache-2.0
+
+[package]
+name = "bitbox-securechip-sys"
+version = "0.1.0"
+authors = ["Shift Crypto AG <support@bitbox.swiss>"]
+edition = "2024"
+description = "Rust bindings for securechip C code BitBox firmware"
+license = "Apache-2.0"
diff --git a/src/rust/bitbox-securechip-sys/build.rs b/src/rust/bitbox-securechip-sys/build.rs
new file mode 100644
index 0000000..5b444f5
--- /dev/null
+++ b/src/rust/bitbox-securechip-sys/build.rs
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::env;
+use std::io::ErrorKind;
+use std::path::PathBuf;
+use std::process::{Command, Output};
+
+const ALLOWLIST_TYPES: &[&str] = &[
+ "securechip_error_t",
+ "securechip_interface_functions_t",
+ "securechip_model_t",
+ "securechip_password_stretch_algo_t",
+];
+
+const ALLOWLIST_FNS: &[&str] = &[
+ "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",
+ "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",
+];
+
+const RUSTIFIED_ENUMS: &[&str] = &[
+ "securechip_password_stretch_algo_t",
+ "securechip_error_t",
+ "securechip_model_t",
+];
+
+type BuildResult<T> = Result<T, String>;
+
+pub fn main() -> BuildResult<()> {
+ ensure_command_exists("bindgen")?;
+
+ let manifest_dir =
+ PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
+ let repo_root = manifest_dir.join("../../..");
+ let src_dir = repo_root.join("src");
+ let wrapper = manifest_dir.join("wrapper.h");
+ if !wrapper.is_file() {
+ return Err("wrapper.h not found".into());
+ }
+
+ emit_rerun_if_changed(&wrapper);
+ emit_rerun_if_changed(src_dir.join("atecc"));
+ emit_rerun_if_changed(src_dir.join("optiga"));
+ emit_rerun_if_changed(src_dir.join("platform"));
+ emit_rerun_if_changed(src_dir.join("securechip"));
+ emit_rerun_if_changed(src_dir.join("compiler_util.h"));
+
+ let target = env::var("TARGET").expect("TARGET not set");
+ let cross_compiling = target == "thumbv7em-none-eabi";
+
+ let mut extra_flags = if cross_compiling {
+ vec![
+ // Generate bindings for the firmware target ABI, not the host ABI.
+ "--target=thumbv7em-none-eabi",
+ // The firmware C code is compiled with arm-none-eabi-gcc, which uses
+ // -fshort-enums by default. Bindgen must match those enum sizes.
+ "-fshort-enums",
+ ]
+ } else {
+ vec![]
+ };
+
+ if let Ok(rustflags) = std::env::var("CARGO_ENCODED_RUSTFLAGS") {
+ for flag in rustflags.split('\x1f') {
+ if flag == "-Dwarnings" {
+ extra_flags.push("-Werror");
+ }
+ }
+ }
+ let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
+ let out_path = out_dir.join("bindings.rs");
+ let out_path = out_path.into_os_string().into_string().unwrap();
+
+ let mut definitions = vec![
+ // Expose the U2F counter declarations guarded by APP_U2F in atecc.h/optiga.h.
+ "-DAPP_U2F=1",
+ ];
+ definitions.extend(&extra_flags);
+
+ run_command(
+ Command::new("bindgen")
+ .args(["--output", &out_path])
+ .arg("--use-core")
+ .arg("--with-derive-default")
+ .args(
+ ALLOWLIST_FNS
+ .iter()
+ .flat_map(|item| ["--allowlist-function", item]),
+ )
+ .args(
+ ALLOWLIST_TYPES
+ .iter()
+ .flat_map(|item| ["--allowlist-type", item]),
+ )
+ .args(
+ RUSTIFIED_ENUMS
+ .iter()
+ .flat_map(|item| ["--rustified-enum", item]),
+ )
+ .arg(&wrapper)
+ .arg("--")
+ .args(&definitions)
+ .arg(format!("-I{}", src_dir.display())),
+ "run bindgen",
+ )?;
+
+ Ok(())
+}
+
+fn emit_rerun_if_changed(path: impl AsRef<std::path::Path>) {
+ println!("cargo::rerun-if-changed={}", path.as_ref().display());
+}
+
+fn ensure_command_exists(command: &str) -> BuildResult<()> {
+ match Command::new(command).arg("--version").output() {
+ Ok(_) => Ok(()),
+ Err(err) if err.kind() == ErrorKind::NotFound => {
+ Err(format!("`{command}` was not found! Check your PATH!"))
+ }
+ Err(err) => Err(format!("failed to run `{command} --version`: {err}")),
+ }
+}
+
+fn run_command(command: &mut Command, context: &str) -> BuildResult<Output> {
+ let output = command
+ .output()
+ .map_err(|err| format!("failed to {context}: {err}"))?;
+ if !output.status.success() {
+ return Err(format!(
+ "{context} failed\nstdout:\n{}\n\nstderr:\n{}",
+ String::from_utf8_lossy(&output.stdout),
+ String::from_utf8_lossy(&output.stderr)
+ ));
+ }
+ Ok(output)
+}
diff --git a/src/rust/bitbox-securechip-sys/src/lib.rs b/src/rust/bitbox-securechip-sys/src/lib.rs
new file mode 100644
index 0000000..0661b9d
--- /dev/null
+++ b/src/rust/bitbox-securechip-sys/src/lib.rs
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#![no_std]
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+// Can be removed once https://github.com/rust-lang/rust-bindgen/issues/1651 is resolved.
+#![allow(deref_nullptr)]
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/src/rust/bitbox-securechip-sys/wrapper.h b/src/rust/bitbox-securechip-sys/wrapper.h
new file mode 100644
index 0000000..9730fc7
--- /dev/null
+++ b/src/rust/bitbox-securechip-sys/wrapper.h
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <atecc/atecc.h>
+#include <optiga/optiga.h>
diff --git a/src/rust/bitbox-securechip/Cargo.toml b/src/rust/bitbox-securechip/Cargo.toml
new file mode 100644
index 0000000..1acc9d8
--- /dev/null
+++ b/src/rust/bitbox-securechip/Cargo.toml
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: Apache-2.0
+
+[package]
+name = "bitbox-securechip"
+version = "0.1.0"
+authors = ["Shift Crypto AG <support@bitbox.swiss>"]
+edition = "2024"
+description = "Safe Rust bindings for securechip code in BitBox firmware"
+license = "Apache-2.0"
+
+[dependencies]
+bitbox-securechip-sys = { path = "../bitbox-securechip-sys" }
+util = { path = "../util" }
+zeroize = { workspace = true }
+
+[features]
+app-u2f = []
diff --git a/src/rust/bitbox-securechip/src/atecc.rs b/src/rust/bitbox-securechip/src/atecc.rs
new file mode 100644
index 0000000..5283267
--- /dev/null
+++ b/src/rust/bitbox-securechip/src/atecc.rs
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{Error, Model, PasswordStretchAlgo, SecureChipError};
+use alloc::{vec, vec::Vec};
+use zeroize::Zeroizing;
+
+pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Result<(), ()> {
+ match unsafe {
+ bitbox_securechip_sys::atecc_attestation_sign(challenge.as_ptr(), signature.as_mut_ptr())
+ } {
+ true => Ok(()),
+ false => Err(()),
+ }
+}
+
+pub fn monotonic_increments_remaining() -> Result<u32, ()> {
+ let mut result = 0u32;
+ match unsafe { bitbox_securechip_sys::atecc_monotonic_increments_remaining(&mut result) } {
+ true => Ok(result),
+ false => Err(()),
+ }
+}
+
+pub fn reset_keys() -> Result<(), ()> {
+ match unsafe { bitbox_securechip_sys::atecc_reset_keys() } {
+ true => Ok(()),
+ false => Err(()),
+ }
+}
+
+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 {
+ bitbox_securechip_sys::atecc_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))
+ }
+}
+
+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 {
+ bitbox_securechip_sys::atecc_stretch_password(
+ password.as_ptr().cast(),
+ password_stretch_algo,
+ stretched.as_mut_ptr(),
+ )
+ };
+ if status == 0 {
+ Ok(stretched)
+ } else {
+ Err(Error::from_status(status))
+ }
+}
+
+pub fn kdf(msg: &[u8]) -> Result<Zeroizing<Vec<u8>>, Error> {
+ let mut result = Zeroizing::new(vec![0u8; 32]);
+ let status =
+ unsafe { bitbox_securechip_sys::atecc_kdf(msg.as_ptr(), msg.len(), result.as_mut_ptr()) };
+ if status == 0 {
+ Ok(result)
+ } else {
+ Err(Error::from_status(status))
+ }
+}
+
+#[cfg(feature = "app-u2f")]
+pub fn u2f_counter_set(counter: u32) -> Result<(), ()> {
+ match unsafe { bitbox_securechip_sys::atecc_u2f_counter_set(counter) } {
+ true => Ok(()),
+ false => Err(()),
+ }
+}
+
+pub fn model() -> Result<Model, ()> {
+ let mut model = core::mem::MaybeUninit::uninit();
+ match unsafe { bitbox_securechip_sys::atecc_model(model.as_mut_ptr()) } {
+ true => Ok(unsafe { model.assume_init() }),
+ false => Err(()),
+ }
+}
diff --git a/src/rust/bitbox-securechip/src/lib.rs b/src/rust/bitbox-securechip/src/lib.rs
new file mode 100644
index 0000000..f1c9f86
--- /dev/null
+++ b/src/rust/bitbox-securechip/src/lib.rs
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#![no_std]
+
+extern crate alloc;
+
+use bitbox_securechip_sys as ffi;
+
+pub mod atecc;
+pub mod optiga;
+
+pub use ffi::securechip_error_t as SecureChipError;
+pub use ffi::securechip_model_t as Model;
+pub use ffi::securechip_password_stretch_algo_t as PasswordStretchAlgo;
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum Error {
+ SecureChip(SecureChipError),
+ Status(i32),
+}
+
+// Keep in sync with securechip.h's securechip_error_t.
+const SECURECHIP_ERRORS: [SecureChipError; 17] = [
+ // Errors common to any securechip implementation
+ SecureChipError::SC_ERR_IFS,
+ SecureChipError::SC_ERR_INVALID_ARGS,
+ SecureChipError::SC_ERR_CONFIG_MISMATCH,
+ SecureChipError::SC_ERR_SALT,
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO,
+ SecureChipError::SC_ERR_MEMORY,
+ // Errors specific to the ATECC
+ SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG,
+ SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_DATA,
+ SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_IO,
+ SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_AUTH,
+ SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_ENC,
+ SecureChipError::SC_ATECC_ERR_RESET_KEYS,
+ // Errors specific to the Optiga
+ SecureChipError::SC_OPTIGA_ERR_CREATE,
+ SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_METADATA,
+ SecureChipError::SC_OPTIGA_ERR_PAL,
+ SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_LEN,
+];
+
+fn securechip_error_from_status(status: i32) -> Option<SecureChipError> {
+ SECURECHIP_ERRORS
+ .iter()
+ .copied()
+ .find(|err| *err as i32 == status)
+}
+
+impl Error {
+ fn from_status(status: i32) -> Self {
+ if status < 0 {
+ match securechip_error_from_status(status) {
+ Some(err) => Error::SecureChip(err),
+ None => Error::Status(status),
+ }
+ } else {
+ Error::Status(status)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_error_from_status() {
+ let cases = [
+ SecureChipError::SC_ERR_IFS,
+ SecureChipError::SC_ERR_INVALID_ARGS,
+ SecureChipError::SC_ERR_CONFIG_MISMATCH,
+ SecureChipError::SC_ERR_SALT,
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO,
+ SecureChipError::SC_ERR_MEMORY,
+ SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG,
+ SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_DATA,
+ SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_IO,
+ SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_AUTH,
+ SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_ENC,
+ SecureChipError::SC_ATECC_ERR_RESET_KEYS,
+ SecureChipError::SC_OPTIGA_ERR_CREATE,
+ SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_METADATA,
+ SecureChipError::SC_OPTIGA_ERR_PAL,
+ SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_LEN,
+ ];
+
+ for error in cases {
+ assert_eq!(Error::from_status(error as i32), Error::SecureChip(error),);
+ }
+
+ assert_eq!(Error::from_status(7), Error::Status(7));
+ assert_eq!(Error::from_status(-9999), Error::Status(-9999));
+ }
+}
diff --git a/src/rust/bitbox-securechip/src/optiga.rs b/src/rust/bitbox-securechip/src/optiga.rs
new file mode 100644
index 0000000..be64e2e
--- /dev/null
+++ b/src/rust/bitbox-securechip/src/optiga.rs
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{Error, Model, PasswordStretchAlgo, SecureChipError};
+use alloc::{vec, vec::Vec};
+use zeroize::Zeroizing;
+
+pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Result<(), ()> {
+ match unsafe {
+ bitbox_securechip_sys::optiga_attestation_sign(challenge.as_ptr(), signature.as_mut_ptr())
+ } {
+ true => Ok(()),
+ false => Err(()),
+ }
+}
+
+pub fn monotonic_increments_remaining() -> Result<u32, ()> {
+ let mut result = 0u32;
+ match unsafe { bitbox_securechip_sys::optiga_monotonic_increments_remaining(&mut result) } {
+ true => Ok(result),
+ false => Err(()),
+ }
+}
+
+pub fn reset_keys() -> Result<(), ()> {
+ match unsafe { bitbox_securechip_sys::optiga_reset_keys() } {
+ true => Ok(()),
+ false => Err(()),
+ }
+}
+
+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 {
+ bitbox_securechip_sys::optiga_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))
+ }
+}
+
+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 {
+ bitbox_securechip_sys::optiga_stretch_password(
+ password.as_ptr().cast(),
+ password_stretch_algo,
+ stretched.as_mut_ptr(),
+ )
+ };
+ if status == 0 {
+ Ok(stretched)
+ } else {
+ Err(Error::from_status(status))
+ }
+}
+
+pub fn kdf(msg: &[u8]) -> Result<Zeroizing<Vec<u8>>, Error> {
+ let mut result = Zeroizing::new(vec![0u8; 32]);
+ let status = unsafe {
+ bitbox_securechip_sys::optiga_kdf_external(msg.as_ptr(), msg.len(), result.as_mut_ptr())
+ };
+ if status == 0 {
+ Ok(result)
+ } else {
+ Err(Error::from_status(status))
+ }
+}
+
+#[cfg(feature = "app-u2f")]
+pub fn u2f_counter_set(counter: u32) -> Result<(), ()> {
+ match unsafe { bitbox_securechip_sys::optiga_u2f_counter_set(counter) } {
+ true => Ok(()),
+ false => Err(()),
+ }
+}
+
+pub fn model() -> Result<Model, ()> {
+ let mut model = core::mem::MaybeUninit::uninit();
+ match unsafe { bitbox_securechip_sys::optiga_model(model.as_mut_ptr()) } {
+ true => Ok(unsafe { model.assume_init() }),
+ false => Err(()),
+ }
+}
diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs
index 3af8091..c70143c 100644
--- a/src/rust/bitbox02-sys/build.rs
+++ b/src/rust/bitbox02-sys/build.rs
@@ -44,10 +44,6 @@ const ALLOWLIST_TYPES: &[&str] = &[
"event_types",
"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",
@@ -69,18 +65,6 @@ 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",
@@ -141,18 +125,6 @@ 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",
@@ -224,11 +196,8 @@ const RUSTIFIED_ENUMS: &[&str] = &[
"memory_optiga_config_version_t",
"memory_password_stretch_algo_t",
"memory_result_t",
- "securechip_password_stretch_algo_t",
"multisig_script_type_t",
"output_type_t",
- "securechip_error_t",
- "securechip_model_t",
"simple_type_t",
"trinary_choice_t",
];
diff --git a/src/rust/bitbox02-sys/wrapper.h b/src/rust/bitbox02-sys/wrapper.h
index 22519a5..455ba87 100644
--- a/src/rust/bitbox02-sys/wrapper.h
+++ b/src/rust/bitbox02-sys/wrapper.h
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
-#include <atecc/atecc.h>
#include <da14531/da14531.h>
#include <da14531/da14531_handler.h>
#include <da14531/da14531_protocol.h>
@@ -12,7 +11,6 @@
#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>
diff --git a/src/rust/bitbox02/Cargo.toml b/src/rust/bitbox02/Cargo.toml
index 370a7fc..8c5d77c 100644
--- a/src/rust/bitbox02/Cargo.toml
+++ b/src/rust/bitbox02/Cargo.toml
@@ -10,6 +10,8 @@ license = "Apache-2.0"
[dependencies]
bitbox02-sys = {path="../bitbox02-sys"}
+bitbox-securechip = { path = "../bitbox-securechip" }
+bitbox-securechip-sys = { path = "../bitbox-securechip-sys" }
bitbox02-noise = { path = "../bitbox02-noise" }
bitbox-hal = { path = "../bitbox-hal" }
bitbox-bytequeue = { path = "../bitbox-bytequeue" }
@@ -41,4 +43,4 @@ simulator-graphical = []
app-ethereum = []
app-bitcoin = []
app-litecoin = []
-app-u2f = ["bitbox-hal/app-u2f"]
+app-u2f = ["bitbox-hal/app-u2f", "bitbox-securechip/app-u2f"]
diff --git a/src/rust/bitbox02/src/hal/securechip.rs b/src/rust/bitbox02/src/hal/securechip.rs
index ef681d6..d1360fc 100644
--- a/src/rust/bitbox02/src/hal/securechip.rs
+++ b/src/rust/bitbox02/src/hal/securechip.rs
@@ -8,70 +8,70 @@ use bitbox_hal::securechip::{Error, Model, SecureChipError};
pub struct BitBox02SecureChip;
-fn to_hal_model(model: crate::securechip::Model) -> Model {
+fn to_hal_model(model: bitbox_securechip::Model) -> Model {
match model {
- crate::securechip::Model::ATECC_ATECC608A => Model::Atecc608A,
- crate::securechip::Model::ATECC_ATECC608B => Model::Atecc608B,
- crate::securechip::Model::OPTIGA_TRUST_M_V3 => Model::OptigaTrustM3,
+ bitbox_securechip::Model::ATECC_ATECC608A => Model::Atecc608A,
+ bitbox_securechip::Model::ATECC_ATECC608B => Model::Atecc608B,
+ bitbox_securechip::Model::OPTIGA_TRUST_M_V3 => Model::OptigaTrustM3,
}
}
-fn to_hal_error(error: crate::securechip::Error) -> Error {
+fn to_hal_error(error: bitbox_securechip::Error) -> Error {
match error {
- crate::securechip::Error::SecureChip(sc_err) => Error::SecureChip(match sc_err {
- crate::securechip::SecureChipError::SC_ERR_IFS => SecureChipError::Ifs,
- crate::securechip::SecureChipError::SC_ERR_INVALID_ARGS => SecureChipError::InvalidArgs,
- crate::securechip::SecureChipError::SC_ERR_CONFIG_MISMATCH => {
+ bitbox_securechip::Error::SecureChip(sc_err) => Error::SecureChip(match sc_err {
+ bitbox_securechip::SecureChipError::SC_ERR_IFS => SecureChipError::Ifs,
+ bitbox_securechip::SecureChipError::SC_ERR_INVALID_ARGS => SecureChipError::InvalidArgs,
+ bitbox_securechip::SecureChipError::SC_ERR_CONFIG_MISMATCH => {
SecureChipError::ConfigMismatch
}
- crate::securechip::SecureChipError::SC_ERR_SALT => SecureChipError::Salt,
- crate::securechip::SecureChipError::SC_ERR_INCORRECT_PASSWORD => {
+ bitbox_securechip::SecureChipError::SC_ERR_SALT => SecureChipError::Salt,
+ bitbox_securechip::SecureChipError::SC_ERR_INCORRECT_PASSWORD => {
SecureChipError::IncorrectPassword
}
- crate::securechip::SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO => {
+ bitbox_securechip::SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO => {
SecureChipError::InvalidPasswordStretchAlgo
}
- crate::securechip::SecureChipError::SC_ERR_MEMORY => SecureChipError::Memory,
- crate::securechip::SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG => {
+ bitbox_securechip::SecureChipError::SC_ERR_MEMORY => SecureChipError::Memory,
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG => {
SecureChipError::AteccZoneUnlockedConfig
}
- crate::securechip::SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_DATA => {
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_DATA => {
SecureChipError::AteccZoneUnlockedData
}
- crate::securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_IO => {
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_IO => {
SecureChipError::AteccSlotUnlockedIo
}
- crate::securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_AUTH => {
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_AUTH => {
SecureChipError::AteccSlotUnlockedAuth
}
- crate::securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_ENC => {
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_ENC => {
SecureChipError::AteccSlotUnlockedEnc
}
- crate::securechip::SecureChipError::SC_ATECC_ERR_RESET_KEYS => {
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_RESET_KEYS => {
SecureChipError::AteccResetKeys
}
- crate::securechip::SecureChipError::SC_OPTIGA_ERR_CREATE => {
+ bitbox_securechip::SecureChipError::SC_OPTIGA_ERR_CREATE => {
SecureChipError::OptigaCreate
}
- crate::securechip::SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_METADATA => {
+ bitbox_securechip::SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_METADATA => {
SecureChipError::OptigaUnexpectedMetadata
}
- crate::securechip::SecureChipError::SC_OPTIGA_ERR_PAL => SecureChipError::OptigaPal,
- crate::securechip::SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_LEN => {
+ bitbox_securechip::SecureChipError::SC_OPTIGA_ERR_PAL => SecureChipError::OptigaPal,
+ bitbox_securechip::SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_LEN => {
SecureChipError::OptigaUnexpectedLen
}
}),
- crate::securechip::Error::Status(status) => Error::Status(status),
+ bitbox_securechip::Error::Status(status) => Error::Status(status),
}
}
-fn to_c_password_stretch_algo(algo: PasswordStretchAlgo) -> crate::securechip::PasswordStretchAlgo {
+fn to_c_password_stretch_algo(algo: PasswordStretchAlgo) -> bitbox_securechip::PasswordStretchAlgo {
match algo {
PasswordStretchAlgo::V0 => {
- crate::securechip::PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0
+ bitbox_securechip::PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0
}
PasswordStretchAlgo::V1 => {
- crate::securechip::PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1
+ bitbox_securechip::PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1
}
}
}
@@ -134,19 +134,20 @@ impl SecureChip for BitBox02SecureChip {
#[cfg(test)]
mod tests {
use super::*;
+ use hex_lit::hex;
#[test]
fn test_to_hal_model() {
assert_eq!(
- to_hal_model(crate::securechip::Model::ATECC_ATECC608A),
+ to_hal_model(bitbox_securechip::Model::ATECC_ATECC608A),
Model::Atecc608A,
);
assert_eq!(
- to_hal_model(crate::securechip::Model::ATECC_ATECC608B),
+ to_hal_model(bitbox_securechip::Model::ATECC_ATECC608B),
Model::Atecc608B,
);
assert_eq!(
- to_hal_model(crate::securechip::Model::OPTIGA_TRUST_M_V3),
+ to_hal_model(bitbox_securechip::Model::OPTIGA_TRUST_M_V3),
Model::OptigaTrustM3,
);
}
@@ -155,77 +156,77 @@ mod tests {
fn test_to_hal_error_securechip() {
let cases = [
(
- crate::securechip::SecureChipError::SC_ERR_IFS,
+ bitbox_securechip::SecureChipError::SC_ERR_IFS,
SecureChipError::Ifs,
),
(
- crate::securechip::SecureChipError::SC_ERR_INVALID_ARGS,
+ bitbox_securechip::SecureChipError::SC_ERR_INVALID_ARGS,
SecureChipError::InvalidArgs,
),
(
- crate::securechip::SecureChipError::SC_ERR_CONFIG_MISMATCH,
+ bitbox_securechip::SecureChipError::SC_ERR_CONFIG_MISMATCH,
SecureChipError::ConfigMismatch,
),
(
- crate::securechip::SecureChipError::SC_ERR_SALT,
+ bitbox_securechip::SecureChipError::SC_ERR_SALT,
SecureChipError::Salt,
),
(
- crate::securechip::SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ bitbox_securechip::SecureChipError::SC_ERR_INCORRECT_PASSWORD,
SecureChipError::IncorrectPassword,
),
(
- crate::securechip::SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO,
+ bitbox_securechip::SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO,
SecureChipError::InvalidPasswordStretchAlgo,
),
(
- crate::securechip::SecureChipError::SC_ERR_MEMORY,
+ bitbox_securechip::SecureChipError::SC_ERR_MEMORY,
SecureChipError::Memory,
),
(
- crate::securechip::SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG,
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG,
SecureChipError::AteccZoneUnlockedConfig,
),
(
- crate::securechip::SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_DATA,
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_DATA,
SecureChipError::AteccZoneUnlockedData,
),
(
- crate::securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_IO,
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_IO,
SecureChipError::AteccSlotUnlockedIo,
),
(
- crate::securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_AUTH,
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_AUTH,
SecureChipError::AteccSlotUnlockedAuth,
),
(
- crate::securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_ENC,
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_ENC,
SecureChipError::AteccSlotUnlockedEnc,
),
(
- crate::securechip::SecureChipError::SC_ATECC_ERR_RESET_KEYS,
+ bitbox_securechip::SecureChipError::SC_ATECC_ERR_RESET_KEYS,
SecureChipError::AteccResetKeys,
),
(
- crate::securechip::SecureChipError::SC_OPTIGA_ERR_CREATE,
+ bitbox_securechip::SecureChipError::SC_OPTIGA_ERR_CREATE,
SecureChipError::OptigaCreate,
),
(
- crate::securechip::SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_METADATA,
+ bitbox_securechip::SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_METADATA,
SecureChipError::OptigaUnexpectedMetadata,
),
(
- crate::securechip::SecureChipError::SC_OPTIGA_ERR_PAL,
+ bitbox_securechip::SecureChipError::SC_OPTIGA_ERR_PAL,
SecureChipError::OptigaPal,
),
(
- crate::securechip::SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_LEN,
+ bitbox_securechip::SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_LEN,
SecureChipError::OptigaUnexpectedLen,
),
];
for (input, expected) in cases {
assert_eq!(
- to_hal_error(crate::securechip::Error::SecureChip(input)),
+ to_hal_error(bitbox_securechip::Error::SecureChip(input)),
Error::SecureChip(expected),
);
}
@@ -234,7 +235,7 @@ mod tests {
#[test]
fn test_to_hal_error_status() {
assert_eq!(
- to_hal_error(crate::securechip::Error::Status(7)),
+ to_hal_error(bitbox_securechip::Error::Status(7)),
Error::Status(7)
);
}
@@ -243,11 +244,30 @@ mod tests {
fn test_to_c_password_stretch_algo() {
assert_eq!(
to_c_password_stretch_algo(PasswordStretchAlgo::V0),
- crate::securechip::PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0,
+ bitbox_securechip::PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0,
);
assert_eq!(
to_c_password_stretch_algo(PasswordStretchAlgo::V1),
- crate::securechip::PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
+ bitbox_securechip::PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
+ );
+ }
+
+ #[test]
+ fn test_kdf() {
+ let mut securechip = BitBox02SecureChip;
+ let result = securechip.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() {
+ let mut securechip = BitBox02SecureChip;
+ assert_eq!(
+ securechip.init_new_password("password", PasswordStretchAlgo::V0),
+ Err(Error::SecureChip(
+ SecureChipError::InvalidPasswordStretchAlgo,
+ )),
);
}
}
diff --git a/src/rust/bitbox02/src/securechip.rs b/src/rust/bitbox02/src/securechip.rs
index 388d793..56a0657 100644
--- a/src/rust/bitbox02/src/securechip.rs
+++ b/src/rust/bitbox02/src/securechip.rs
@@ -1,64 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
-extern crate alloc;
-
-use alloc::vec::Vec;
-use zeroize::Zeroizing;
-
-pub use bitbox02_sys::securechip_error_t as SecureChipError;
-pub use bitbox02_sys::securechip_model_t as Model;
-pub use bitbox02_sys::securechip_password_stretch_algo_t as PasswordStretchAlgo;
-
-#[derive(Debug, PartialEq, Eq)]
-pub enum Error {
- SecureChip(SecureChipError),
- Status(i32),
-}
-
-// Keep in sync with securechip.h's securechip_error_t.
-const SECURECHIP_ERRORS: [SecureChipError; 17] = [
- // Errors common to any securechip implementation
- SecureChipError::SC_ERR_IFS,
- SecureChipError::SC_ERR_INVALID_ARGS,
- SecureChipError::SC_ERR_CONFIG_MISMATCH,
- SecureChipError::SC_ERR_SALT,
- SecureChipError::SC_ERR_INCORRECT_PASSWORD,
- SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO,
- SecureChipError::SC_ERR_MEMORY,
- // Errors specific to the ATECC
- SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG,
- SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_DATA,
- SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_IO,
- SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_AUTH,
- SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_ENC,
- SecureChipError::SC_ATECC_ERR_RESET_KEYS,
- // Errors specific to the Optiga
- SecureChipError::SC_OPTIGA_ERR_CREATE,
- SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_METADATA,
- SecureChipError::SC_OPTIGA_ERR_PAL,
- SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_LEN,
-];
-
-fn securechip_error_from_status(status: i32) -> Option<SecureChipError> {
- SECURECHIP_ERRORS
- .iter()
- .copied()
- .find(|err| *err as i32 == status)
-}
-
-impl Error {
- fn from_status(status: i32) -> Self {
- if status < 0 {
- match securechip_error_from_status(status) {
- Some(err) => Error::SecureChip(err),
- None => Error::Status(status),
- }
- } else {
- Error::Status(status)
- }
- }
-}
-
#[cfg_attr(
any(
test,
@@ -70,121 +11,4 @@ impl Error {
)]
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<(), ()> {
- imp::attestation_sign(challenge, signature)
-}
-
-/// Returns the remaining number of secure-chip monotonic counter increments.
-pub fn monotonic_increments_remaining() -> Result<u32, ()> {
- imp::monotonic_increments_remaining()
-}
-
-/// Resets the secure-chip objects involved in password stretching.
-pub fn reset_keys() -> Result<(), ()> {
- 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> {
- 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> {
- imp::stretch_password(password, password_stretch_algo)
-}
-
-/// 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> {
- imp::kdf(msg)
-}
-
-#[cfg(feature = "app-u2f")]
-/// Sets the U2F counter to `counter`.
-///
-/// This is intended for initialization only.
-pub fn u2f_counter_set(counter: u32) -> Result<(), ()> {
- imp::u2f_counter_set(counter)
-}
-
-/// Returns the detected secure-chip model.
-pub fn model() -> Result<Model, ()> {
- imp::model()
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- use hex_lit::hex;
-
- #[test]
- fn test_error_from_status() {
- let cases = [
- SecureChipError::SC_ERR_IFS,
- SecureChipError::SC_ERR_INVALID_ARGS,
- SecureChipError::SC_ERR_CONFIG_MISMATCH,
- SecureChipError::SC_ERR_SALT,
- SecureChipError::SC_ERR_INCORRECT_PASSWORD,
- SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO,
- SecureChipError::SC_ERR_MEMORY,
- SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG,
- SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_DATA,
- SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_IO,
- SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_AUTH,
- SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_ENC,
- SecureChipError::SC_ATECC_ERR_RESET_KEYS,
- SecureChipError::SC_OPTIGA_ERR_CREATE,
- SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_METADATA,
- SecureChipError::SC_OPTIGA_ERR_PAL,
- SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_LEN,
- ];
-
- for error in cases {
- assert_eq!(Error::from_status(error as i32), Error::SecureChip(error),);
- }
-
- assert_eq!(Error::from_status(7), Error::Status(7));
- assert_eq!(Error::from_status(-9999), Error::Status(-9999));
- }
-
- #[test]
- fn test_kdf() {
- // 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,
- )),
- );
- }
-}
+pub(crate) use imp::*;
diff --git a/src/rust/bitbox02/src/securechip/imp.rs b/src/rust/bitbox02/src/securechip/imp.rs
index bf836fa..0260088 100644
--- a/src/rust/bitbox02/src/securechip/imp.rs
+++ b/src/rust/bitbox02/src/securechip/imp.rs
@@ -1,8 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
-use super::*;
-use core::ffi::{c_char, c_int};
+use alloc::vec::Vec;
+use bitbox_securechip::{Error, Model, PasswordStretchAlgo, atecc, optiga};
+use core::ffi::c_int;
use util::cell::SyncCell;
+use zeroize::Zeroizing;
#[derive(Copy, Clone)]
enum Backend {
@@ -17,51 +19,23 @@ fn backend() -> Backend {
}
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)
- },
+ Backend::Atecc => atecc::attestation_sign(challenge, signature),
+ Backend::Optiga => optiga::attestation_sign(challenge, signature),
}
}
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)
- },
+ Backend::Atecc => atecc::monotonic_increments_remaining(),
+ Backend::Optiga => optiga::monotonic_increments_remaining(),
}
}
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() },
+ Backend::Atecc => atecc::reset_keys(),
+ Backend::Optiga => optiga::reset_keys(),
}
}
@@ -69,35 +43,9 @@ 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)
- },
+ Backend::Atecc => atecc::init_new_password(password, password_stretch_algo),
+ Backend::Optiga => optiga::init_new_password(password, password_stretch_algo),
}
}
@@ -105,98 +53,33 @@ 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)
- },
+ Backend::Atecc => atecc::stretch_password(password, password_stretch_algo),
+ Backend::Optiga => optiga::stretch_password(password, password_stretch_algo),
}
}
/// 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) },
+ Backend::Atecc => atecc::kdf(msg),
+ Backend::Optiga => optiga::kdf(msg),
}
}
#[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) },
+ Backend::Atecc => atecc::u2f_counter_set(counter),
+ Backend::Optiga => 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) },
+ Backend::Atecc => atecc::model(),
+ Backend::Optiga => optiga::model(),
}
}
@@ -222,30 +105,40 @@ pub extern "C" fn rust_securechip_init() -> bool {
/// 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,
+ ifs: *const bitbox_securechip_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) },
+ Backend::Atecc => unsafe { bitbox_securechip_sys::atecc_setup(ifs) },
+ Backend::Optiga => unsafe { bitbox_securechip_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()
+ match backend() {
+ Backend::Atecc => atecc::reset_keys(),
+ Backend::Optiga => optiga::reset_keys(),
+ }
+ .is_ok()
}
/// 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) }
+ match backend() {
+ Backend::Atecc => unsafe { bitbox_securechip_sys::atecc_gen_attestation_key(pubkey_out) },
+ Backend::Optiga => unsafe { bitbox_securechip_sys::optiga_gen_attestation_key(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) }
+ match backend() {
+ Backend::Atecc => unsafe { bitbox_securechip_sys::atecc_random(rand_out) },
+ Backend::Optiga => unsafe { bitbox_securechip_sys::optiga_random(rand_out) },
+ }
}
/// Sets the U2F counter to `counter`.
@@ -253,7 +146,10 @@ pub unsafe extern "C" fn rust_securechip_random(rand_out: *mut u8) -> bool {
/// 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)
+ match backend() {
+ Backend::Atecc => unsafe { bitbox_securechip_sys::atecc_u2f_counter_set(counter) },
+ Backend::Optiga => unsafe { bitbox_securechip_sys::optiga_u2f_counter_set(counter) },
+ }
}
#[cfg(feature = "app-u2f")]
@@ -261,7 +157,7 @@ pub extern "C" fn rust_securechip_u2f_counter_set(counter: u32) -> bool {
#[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) },
+ Backend::Atecc => unsafe { bitbox_securechip_sys::atecc_u2f_counter_inc(counter) },
+ Backend::Optiga => unsafe { bitbox_securechip_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
index 22b7533..73a14e7 100644
--- a/src/rust/bitbox02/src/securechip/imp_fake.rs
+++ b/src/rust/bitbox02/src/securechip/imp_fake.rs
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
-use super::*;
+use alloc::vec::Vec;
+use bitbox_securechip::{Error, Model, PasswordStretchAlgo, SecureChipError};
use hex_lit::hex;
use hmac::{Hmac, Mac};
use sha2::Sha256;
+use zeroize::Zeroizing;
const PASSWORD_STRETCH_KEY: &[u8] = b"unit-test";
const KDF_KEY: [u8; 32] = hex!("d2e1e6b18b6c6b08433edbc1d168c1a0043774a4221877e79ed56684be5ac01b");
@@ -39,8 +41,8 @@ pub fn init_new_password(
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,
+ return Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO,
));
}
Ok(Zeroizing::new(
diff --git a/test/simulator-graphical-bb03/Cargo.lock b/test/simulator-graphical-bb03/Cargo.lock
index bd91703..803b641 100644
--- a/test/simulator-graphical-bb03/Cargo.lock
+++ b/test/simulator-graphical-bb03/Cargo.lock
@@ -401,6 +401,19 @@ dependencies = [
"cc",
]
+[[package]]
+name = "bitbox-securechip"
+version = "0.1.0"
+dependencies = [
+ "bitbox-securechip-sys",
+ "util",
+ "zeroize",
+]
+
+[[package]]
+name = "bitbox-securechip-sys"
+version = "0.1.0"
+
[[package]]
name = "bitbox-u2fhid"
version = "0.1.0"
@@ -423,6 +436,8 @@ dependencies = [
"bitbox-bytequeue",
"bitbox-framed-serial-link",
"bitbox-hal",
+ "bitbox-securechip",
+ "bitbox-securechip-sys",
"bitbox-usb-report-queue",
"bitbox02-noise",
"bitbox02-sys",
diff --git a/test/simulator-graphical/Cargo.lock b/test/simulator-graphical/Cargo.lock
index 00e4840..9c3f983 100644
--- a/test/simulator-graphical/Cargo.lock
+++ b/test/simulator-graphical/Cargo.lock
@@ -345,6 +345,19 @@ dependencies = [
"cc",
]
+[[package]]
+name = "bitbox-securechip"
+version = "0.1.0"
+dependencies = [
+ "bitbox-securechip-sys",
+ "util",
+ "zeroize",
+]
+
+[[package]]
+name = "bitbox-securechip-sys"
+version = "0.1.0"
+
[[package]]
name = "bitbox-u2fhid"
version = "0.1.0"
@@ -367,6 +380,8 @@ dependencies = [
"bitbox-bytequeue",
"bitbox-framed-serial-link",
"bitbox-hal",
+ "bitbox-securechip",
+ "bitbox-securechip-sys",
"bitbox-usb-report-queue",
"bitbox02-noise",
"bitbox02-sys",
Why this scored 12/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.