What changed, and why it matters
This commit rewrites how the BitBox02 hardware wallet fetches random numbers from its secure chip. Previously, the secure chip's random generator was called in a blocking (synchronous) way. The change makes it asynchronous, so the firmware can do other work while waiting. It also removes an older, simpler random interface and now mixes the secure-chip randomness with the microcontroller's own randomness before using it for sensitive key material. The commit itself is a defensive refactor; it does not appear to fix a known exploit, but it removes a synchronous call that could stall the device and strengthens how random values are combined when creating password-stretching secrets.
Treat as a hardening/refactoring change rather than an emergency security patch. Review the async state machine for race conditions or use-after-free around the new static `BUF` in `crypt_random`, verify that all callers correctly `.await` the RNG and handle errors, and confirm that the added MCU/factory entropy mixing does not introduce bias or reduce entropy. Run firmware tests covering seed creation, password setup, and device reset.
Security signals we found
Removal of synchronous secure-chip RNG C bindings (`optiga_random`, `optiga_ifs_random_32_bytes`)
Introduction of async `optiga_crypt_random` with a static buffer and explicit zeroization on error
Random material used for key generation now mixes MCU RNG, secure-chip RNG, and factory randomness via SHA-256
Password-stretching secret generation (`init_new_password`, `reset_keys`) now requires an explicit HAL RNG source
No vendor statement of security relevance or CVE in commit message or diff
Evidence from the diff
The patch converts secure-chip randomness generation from synchronous to async across the Rust firmware. It deletes the C functions optiga_ifs_random_32_bytes and optiga_random and the FFI wrappers, replacing them with an async optiga_crypt_random operation. The SecureChip::random trait method becomes async fn random. A new helper random_32_bytes_with_mixin in bitbox-core-utils XORs MCU RNG output with a supplied mixin (now the secure-chip random) and hashes the result with SHA-256, incorporating factory randomness. init_new_password and reset_keys now take a &mut impl Random so the secure-chip layer can mix MCU/factory entropy into the new HMAC, password-secret, and write-protected HMAC keys instead of relying solely on the secure chip’s RNG. Call sites in keystore, reset, and the C bridge are updated to .await the new async APIs.
Changed components
src/optiga/optiga.csrc/optiga/optiga.hsrc/rust/bitbox-core-utils/src/random.rssrc/rust/bitbox-hal/src/securechip.rssrc/rust/bitbox-securechip/src/optiga.rssrc/rust/bitbox-securechip/src/optiga/ops.rssrc/rust/bitbox02-rust/src/keystore.rssrc/rust/bitbox02-rust/src/reset.rssrc/rust/bitbox02/src/securechip/imp.rsInspect captured patch +233 / −100
diff --git a/src/optiga/optiga.c b/src/optiga/optiga.c
index 33e5919..f0055dd 100644
--- a/src/optiga/optiga.c
+++ b/src/optiga/optiga.c
@@ -1322,27 +1322,6 @@ optiga_crypt_t* optiga_crypt_instance(void)
return _crypt;
}
-bool optiga_ifs_random_32_bytes(uint8_t* rand_out)
-{
- if (_ifs == NULL || rand_out == NULL) {
- return false;
- }
- _ifs->random_32_bytes(rand_out);
- return true;
-}
-
-// rand_out must be 32 bytes
-int optiga_random(uint8_t* rand_out)
-{
- optiga_lib_status_t res =
- optiga_ops_crypt_random_sync(_crypt, OPTIGA_RNG_TYPE_TRNG, rand_out, 32);
- if (res != OPTIGA_CRYPT_SUCCESS) {
- util_log("optiga_random failed: %x", res);
- return res;
- }
- return 0;
-}
-
#if APP_U2F == 1 || FACTORYSETUP == 1
bool optiga_u2f_counter_set(uint32_t counter)
{
diff --git a/src/optiga/optiga.h b/src/optiga/optiga.h
index 0cbcf4e..18c346a 100644
--- a/src/optiga/optiga.h
+++ b/src/optiga/optiga.h
@@ -95,8 +95,6 @@ USE_RESULT bool optiga_gen_attestation_key(uint8_t* pubkey_out);
USE_RESULT bool optiga_attestation_sign(const uint8_t* challenge, uint8_t* signature_out);
USE_RESULT optiga_util_t* optiga_util_instance(void);
USE_RESULT optiga_crypt_t* optiga_crypt_instance(void);
-USE_RESULT bool optiga_ifs_random_32_bytes(uint8_t* rand_out);
-USE_RESULT int optiga_random(uint8_t* rand_out);
#if APP_U2F == 1 || FACTORYSETUP == 1
USE_RESULT bool optiga_u2f_counter_set(uint32_t counter);
#endif
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 6d0aa0a..c2955ec 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -126,11 +126,13 @@ version = "0.1.0"
name = "bitbox-core-utils"
version = "0.1.0"
dependencies = [
+ "async_test",
"bitbox-hal",
"bitbox-platform-host",
"digest",
"hex_lit",
"sha2",
+ "util",
"zeroize",
]
diff --git a/src/rust/bitbox-core-utils/Cargo.toml b/src/rust/bitbox-core-utils/Cargo.toml
index 26ab3f9..bbaa30e 100644
--- a/src/rust/bitbox-core-utils/Cargo.toml
+++ b/src/rust/bitbox-core-utils/Cargo.toml
@@ -14,5 +14,7 @@ sha2 = { workspace = true }
zeroize = { workspace = true }
[dev-dependencies]
+async_test = { path = "../async_test" }
bitbox-platform-host = { path = "../bitbox-platform-host" }
hex_lit = { workspace = true }
+util = { path = "../util", features = ["testing"] }
diff --git a/src/rust/bitbox-core-utils/src/random.rs b/src/rust/bitbox-core-utils/src/random.rs
index 1cdb5f3..2687b7b 100644
--- a/src/rust/bitbox-core-utils/src/random.rs
+++ b/src/rust/bitbox-core-utils/src/random.rs
@@ -6,16 +6,15 @@ use bitbox_hal::{Hal, Random, SecureChip, securechip};
use digest::FixedOutput;
use sha2::Digest;
-pub fn random_32_bytes(
+pub fn random_32_bytes_with_mixin(
hal_random: &mut impl Random,
- hal_securechip: &mut impl SecureChip,
-) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, securechip::Error> {
+ mixin: &[u8; 32],
+) -> Box<zeroize::Zeroizing<[u8; 32]>> {
let mut mixed = zeroize::Zeroizing::new([0u8; 32]);
hal_random.mcu_32_bytes(&mut mixed);
- let securechip_random = hal_securechip.random()?;
- for (byte, securechip_byte) in mixed.iter_mut().zip(securechip_random.iter()) {
- *byte ^= *securechip_byte;
+ for (byte, mixin_byte) in mixed.iter_mut().zip(mixin.iter()) {
+ *byte ^= *mixin_byte;
}
let factory_randomness = hal_random.factory_randomness();
@@ -27,16 +26,27 @@ pub fn random_32_bytes(
let mut hasher = sha2::Sha256::new();
hasher.update(mixed.as_slice());
FixedOutput::finalize_into(hasher, result.as_mut_slice().into());
- Ok(result)
+ result
+}
+
+pub async fn random_32_bytes(
+ hal_random: &mut impl Random,
+ hal_securechip: &mut impl SecureChip,
+) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, securechip::Error> {
+ let securechip_random = hal_securechip.random().await?;
+ Ok(random_32_bytes_with_mixin(
+ hal_random,
+ securechip_random.as_ref(),
+ ))
}
-pub fn random_32_bytes_from_hal(
+pub async fn random_32_bytes_from_hal(
hal: &mut impl Hal,
) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, securechip::Error> {
let bitbox_hal::HalSubsystems {
random, securechip, ..
} = hal.as_mut();
- random_32_bytes(random, securechip)
+ random_32_bytes(random, securechip).await
}
#[cfg(test)]
@@ -47,7 +57,23 @@ mod tests {
use hex_lit::hex;
#[test]
- fn test_random_32_bytes() {
+ fn test_random_32_bytes_with_mixin() {
+ let mut hal_random = TestingRandom::new();
+ let mcu_random = hex!("00112233445566778899aabbccddeefffedcba98765432100123456789abcdef");
+ let mixin = hex!("102030405060708090a0b0c0d0e0f0000f1e2d3c4b5a69788796a5b4c3d2e1f0");
+ let factory_randomness = TestingRandom::FACTORY_RANDOMNESS;
+
+ hal_random.mock_next(mcu_random);
+ assert_eq!(hal_random.factory_randomness(), &factory_randomness);
+
+ let result = random_32_bytes_with_mixin(&mut hal_random, &mixin);
+
+ let expected = hex!("843595519af3ac2a92cbe2be42a77d5297f64a1c98c1edbc27e1fc661f1d4ac8");
+ assert_eq!(result.as_slice(), &expected);
+ }
+
+ #[async_test::test]
+ async fn test_random_32_bytes() {
let mut hal_random = TestingRandom::new();
let mut hal_securechip = FakeSecureChip::new();
let mcu_random = hex!("00112233445566778899aabbccddeefffedcba98765432100123456789abcdef");
@@ -59,7 +85,9 @@ mod tests {
hal_securechip.mock_random(securechip_random);
assert_eq!(hal_random.factory_randomness(), &factory_randomness);
- let result = random_32_bytes(&mut hal_random, &mut hal_securechip).unwrap();
+ let result = random_32_bytes(&mut hal_random, &mut hal_securechip)
+ .await
+ .unwrap();
/* Reproduce expected with Python:
import hashlib
diff --git a/src/rust/bitbox-hal/src/securechip.rs b/src/rust/bitbox-hal/src/securechip.rs
index 40ec40e..75a1b60 100644
--- a/src/rust/bitbox-hal/src/securechip.rs
+++ b/src/rust/bitbox-hal/src/securechip.rs
@@ -50,7 +50,7 @@ pub enum SecureChipError {
#[allow(async_fn_in_trait)]
pub trait SecureChip {
/// Returns 32 bytes of randomness generated by the secure chip.
- fn random(&mut self) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error>;
+ async fn random(&mut self) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error>;
/// Prepares the secure chip for a new password and returns the stretched password.
///
@@ -58,9 +58,12 @@ pub trait SecureChip {
/// 32-byte value as [`stretch_password`] for the same `password` and
/// `password_stretch_algo`, but may require fewer secure-chip operations.
///
+ /// `random` is used to mix in MCU and factory randomness when generating new secrets.
+ ///
/// `memory` is used for persistent secrets needed during derivation, such as the salt root.
async fn init_new_password(
&mut self,
+ random: &mut impl super::random::Random,
memory: &mut impl super::memory::Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
@@ -99,7 +102,11 @@ pub trait SecureChip {
fn model(&mut self) -> Result<Model, ()>;
/// Resets the secure-chip objects involved in password stretching.
- async fn reset_keys(&mut self, memory: &mut impl super::memory::Memory) -> Result<(), ()>;
+ async fn reset_keys(
+ &mut self,
+ random: &mut impl super::random::Random,
+ memory: &mut impl super::memory::Memory,
+ ) -> Result<(), ()>;
#[cfg(feature = "app-u2f")]
/// Sets the U2F counter to `counter`.
diff --git a/src/rust/bitbox-platform-host/src/securechip.rs b/src/rust/bitbox-platform-host/src/securechip.rs
index f676601..4ed707d 100644
--- a/src/rust/bitbox-platform-host/src/securechip.rs
+++ b/src/rust/bitbox-platform-host/src/securechip.rs
@@ -72,7 +72,7 @@ impl FakeSecureChip {
}
impl bitbox_hal::SecureChip for FakeSecureChip {
- fn random(&mut self) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
+ async fn random(&mut self) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
Ok(Box::new(zeroize::Zeroizing::new(
self.mock_random_values.pop_front().unwrap_or([0u8; 32]),
)))
@@ -80,6 +80,7 @@ impl bitbox_hal::SecureChip for FakeSecureChip {
async fn init_new_password(
&mut self,
+ _random: &mut impl bitbox_hal::Random,
_memory: &mut impl bitbox_hal::Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
@@ -163,7 +164,11 @@ impl bitbox_hal::SecureChip for FakeSecureChip {
Ok(Model::Atecc608B)
}
- async fn reset_keys(&mut self, _memory: &mut impl bitbox_hal::Memory) -> Result<(), ()> {
+ async fn reset_keys(
+ &mut self,
+ _random: &mut impl bitbox_hal::Random,
+ _memory: &mut impl bitbox_hal::Memory,
+ ) -> Result<(), ()> {
if self.reset_keys_fail_once {
self.reset_keys_fail_once = false;
Err(())
@@ -186,13 +191,13 @@ mod tests {
use bitbox_hal::SecureChip;
use hex_lit::hex;
- #[test]
- fn test_mock_random() {
+ #[async_test::test]
+ async fn test_mock_random() {
let mut securechip = FakeSecureChip::new();
let expected = hex!("00112233445566778899aabbccddeefffedcba98765432100123456789abcdef");
securechip.mock_random(expected);
- let first = securechip.random().unwrap();
- let second = securechip.random().unwrap();
+ let first = securechip.random().await.unwrap();
+ let second = securechip.random().await.unwrap();
assert_eq!(first.as_slice(), &expected);
assert_eq!(second.as_slice(), &[0u8; 32]);
}
diff --git a/src/rust/bitbox-securechip-sys/build.rs b/src/rust/bitbox-securechip-sys/build.rs
index 317a570..aa6e067 100644
--- a/src/rust/bitbox-securechip-sys/build.rs
+++ b/src/rust/bitbox-securechip-sys/build.rs
@@ -41,13 +41,12 @@ const ALLOWLIST_FNS: &[&str] = &[
"optiga_crypt_hmac",
"optiga_crypt_hmac_verify",
"optiga_crypt_instance",
+ "optiga_crypt_random",
"optiga_crypt_symmetric_generate_key",
"optiga_crypt_symmetric_encrypt",
- "optiga_ifs_random_32_bytes",
"optiga_gen_attestation_key",
"optiga_ops_get_status",
"optiga_ops_set_status_busy",
- "optiga_random",
"optiga_setup",
"optiga_u2f_counter_inc",
"optiga_u2f_counter_set",
diff --git a/src/rust/bitbox-securechip/src/optiga.rs b/src/rust/bitbox-securechip/src/optiga.rs
index 095464e..e159b67 100644
--- a/src/rust/bitbox-securechip/src/optiga.rs
+++ b/src/rust/bitbox-securechip/src/optiga.rs
@@ -2,7 +2,7 @@
use crate::{Error, Model, PasswordStretchAlgo, SecureChipError};
use alloc::boxed::Box;
-use bitbox_hal::Memory;
+use bitbox_hal::{Memory, Random};
use util::sha2::{hmac_sha256, hmac_sha256_overwrite, sha256};
use zeroize::Zeroizing;
@@ -349,14 +349,10 @@ pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Resul
}
}
-pub fn random() -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
+pub async fn random() -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
let mut result = zeroed_secret::<32>();
- let status = unsafe { bitbox_securechip_sys::optiga_random(result.as_mut_ptr()) };
- if status == 0 {
- Ok(result)
- } else {
- Err(Error::from_status(status))
- }
+ ops::crypt_random(OPTIGA_RNG_TYPE_TRNG, &mut result).await?;
+ Ok(result)
}
pub async fn monotonic_increments_remaining() -> Result<u32, ()> {
@@ -371,7 +367,7 @@ pub async fn monotonic_increments_remaining() -> Result<u32, ()> {
Ok(MONOTONIC_COUNTER_MAX_USE - counter)
}
-pub async fn reset_keys(memory: &mut impl Memory) -> Result<(), ()> {
+pub async fn reset_keys(random: &mut impl Random, memory: &mut impl Memory) -> Result<(), ()> {
// This resets `OID_AES_SYMKEY` and the `OID_HMAC`/`OID_HMAC_WRITEPROTECTED` keys, as well as
// the `OID_PASSWORD_SECRET` and `OID_PASSWORD` keys. A password is needed because updating the
// `OID_PASSWORD` key requires auth using the `OID_PASSWORD_SECRET` key, but any password is
@@ -379,6 +375,7 @@ pub async fn reset_keys(memory: &mut impl Memory) -> Result<(), ()> {
//
// We reset using V1, the latest algorithm. It covers resetting everything from V0 as well.
init_new_password(
+ random,
memory,
"",
PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
@@ -389,6 +386,7 @@ pub async fn reset_keys(memory: &mut impl Memory) -> Result<(), ()> {
}
pub async fn init_new_password(
+ random: &mut impl Random,
memory: &mut impl Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
@@ -401,9 +399,9 @@ pub async fn init_new_password(
}
let mut stretched = zeroed_secret::<KDF_LEN>();
- let mut new_hmac_key = zeroed_secret::<KDF_LEN>();
- ops::ifs_random_32_bytes(&mut new_hmac_key)?;
// Set new HMAC key.
+ let securechip_random = self::random().await?;
+ let new_hmac_key = ops::random_32_bytes(random, &securechip_random)?;
ops::util_write_data(
OID_HMAC,
bitbox_securechip_sys::OPTIGA_UTIL_ERASE_AND_WRITE as u8,
@@ -415,8 +413,8 @@ pub async fn init_new_password(
ops::crypt_symmetric_generate_key(OPTIGA_SYMMETRIC_AES_256, OPTIGA_KEY_USAGE_ENCRYPTION)
.await?;
- let mut password_secret = zeroed_secret::<KDF_LEN>();
- ops::ifs_random_32_bytes(&mut password_secret)?;
+ let securechip_random = self::random().await?;
+ let password_secret = ops::random_32_bytes(random, &securechip_random)?;
ops::util_write_data(
OID_PASSWORD_SECRET,
bitbox_securechip_sys::OPTIGA_UTIL_ERASE_AND_WRITE as u8,
@@ -425,8 +423,8 @@ pub async fn init_new_password(
)
.await?;
- let mut new_hmac_writeprotected_key = zeroed_secret::<KDF_LEN>();
- ops::ifs_random_32_bytes(&mut new_hmac_writeprotected_key)?;
+ let securechip_random = self::random().await?;
+ let new_hmac_writeprotected_key = ops::random_32_bytes(random, &securechip_random)?;
let mut auth_password = zeroed_secret::<KDF_LEN>();
v1_get_auth_password(
@@ -494,6 +492,21 @@ mod tests {
// Fixed test vectors / keys (deterministic fakes).
const SALT_ROOT_FIXED: [u8; 32] = [0x42; 32];
+
+ struct TestRandom;
+
+ // Unused in these unit tests. The fake `ops::random_32_bytes()` implementation ignores the
+ // HAL RNG and returns fixed test vectors instead.
+ impl bitbox_hal::Random for TestRandom {
+ fn factory_randomness(&mut self) -> &'static [u8; 32] {
+ unreachable!("unused in optiga unit tests")
+ }
+
+ fn mcu_32_bytes(&mut self, _out: &mut [u8; 32]) {
+ unreachable!("unused in optiga unit tests")
+ }
+ }
+
fn setup_test() -> (std::sync::MutexGuard<'static, ()>, FakeMemory) {
let guard = ops::test_lock();
ops::test_reset();
@@ -718,8 +731,10 @@ mod tests {
// Attempts after init are special because the PASSWORD_COUNTER init/threshold are offset by 1.
async fn test_optiga_password_v1_stretch_exhaust_fails_after_init() {
let (_guard, mut memory) = setup_test();
+ let mut random = TestRandom;
let stretched = init_new_password(
+ &mut random,
&mut memory,
"pw",
PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
@@ -792,8 +807,10 @@ mod tests {
// that doing a correct attempt resets the counters.
async fn test_optiga_password_v1() {
let (_guard, mut memory) = setup_test();
+ let mut random = TestRandom;
let stretched = init_new_password(
+ &mut random,
&mut memory,
"pw",
PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
diff --git a/src/rust/bitbox-securechip/src/optiga/ops.rs b/src/rust/bitbox-securechip/src/optiga/ops.rs
index ae15301..c27fa70 100644
--- a/src/rust/bitbox-securechip/src/optiga/ops.rs
+++ b/src/rust/bitbox-securechip/src/optiga/ops.rs
@@ -1,12 +1,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{Error, SecureChipError};
+use alloc::boxed::Box;
use core::cell::UnsafeCell;
use core::future::poll_fn;
use core::task::{Poll, Waker};
use grounded::uninit::{GroundedArrayCell, GroundedCell};
use util::cell::SyncCell;
-use zeroize::Zeroize;
+use zeroize::{Zeroize, Zeroizing};
const ARBITRARY_DATA_OBJECT_TYPE_3_MAX_SIZE: usize =
bitbox_securechip_sys::ARBITRARY_DATA_OBJECT_TYPE_3_MAX_SIZE as usize;
@@ -523,10 +524,37 @@ pub(super) async fn crypt_clear_auto_state(secret: u16) -> Result<(), Error> {
.map_err(|status| Error::from_status(status as i32))
}
-pub(super) fn ifs_random_32_bytes(rand_out: &mut [u8; super::KDF_LEN]) -> Result<(), Error> {
- if unsafe { bitbox_securechip_sys::optiga_ifs_random_32_bytes(rand_out.as_mut_ptr()) } {
- Ok(())
- } else {
- Err(Error::SecureChip(SecureChipError::SC_ERR_IFS))
+pub(super) async fn crypt_random(
+ rng_type: bitbox_securechip_sys::optiga_rng_type_t,
+ out: &mut [u8; 32],
+) -> Result<(), Error> {
+ // Static because the Optiga library keeps a raw pointer to this buffer until the async
+ // callback completes, and the Rust future may be dropped before that happens.
+ static BUF: StaticBytes<32> = StaticBytes::const_init();
+
+ let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
+
+ BUF.clear();
+ let result = run_async_op(|| unsafe {
+ bitbox_securechip_sys::optiga_crypt_random(crypt, rng_type, BUF.as_mut_ptr(), 32)
+ })
+ .await
+ .map_err(|status| Error::from_status(status as i32));
+ if let Err(err) = result {
+ BUF.zeroize();
+ return Err(err);
}
+
+ BUF.copy_to_slice(out);
+ BUF.zeroize();
+ Ok(())
+}
+
+pub(super) fn random_32_bytes(
+ random: &mut impl bitbox_hal::Random,
+ mixin: &[u8; super::KDF_LEN],
+) -> Result<Box<Zeroizing<[u8; super::KDF_LEN]>>, Error> {
+ Ok(bitbox_core_utils::random::random_32_bytes_with_mixin(
+ random, mixin,
+ ))
}
diff --git a/src/rust/bitbox-securechip/src/optiga/ops_fake.rs b/src/rust/bitbox-securechip/src/optiga/ops_fake.rs
index 74e996c..6055609 100644
--- a/src/rust/bitbox-securechip/src/optiga/ops_fake.rs
+++ b/src/rust/bitbox-securechip/src/optiga/ops_fake.rs
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::Error;
+use alloc::boxed::Box;
use std::sync::{LazyLock, Mutex, MutexGuard};
+use zeroize::Zeroizing;
//------------------------------------------------------------------------------
// Fixed test vectors / keys (deterministic fakes).
@@ -211,18 +213,27 @@ pub(super) async fn crypt_clear_auto_state(secret: u16) -> Result<(), Error> {
crypt_clear_auto_state_sync(secret)
}
-pub(super) fn ifs_random_32_bytes(rand_out: &mut [u8; super::KDF_LEN]) -> Result<(), Error> {
+pub(super) async fn crypt_random(
+ _rng_type: bitbox_securechip_sys::optiga_rng_type_t,
+ out: &mut [u8; 32],
+) -> Result<(), Error> {
+ *out = [0u8; 32];
+ Ok(())
+}
+
+pub(super) fn random_32_bytes(
+ _random: &mut impl bitbox_hal::Random,
+ _mixin: &[u8; super::KDF_LEN],
+) -> Result<Box<Zeroizing<[u8; super::KDF_LEN]>>, Error> {
let mut state = lock_state();
- // There are only three calls to this at the moment, all in init_new_password.
let src = match state.random_ctr {
- 0 => &KDF_HMAC_KEY_FIXED,
- 1 => &PASSWORD_SECRET_FIXED,
- 2 => &KDF_HMAC_WRITEPROTECTED_KEY_FIXED,
+ 0 => KDF_HMAC_KEY_FIXED,
+ 1 => PASSWORD_SECRET_FIXED,
+ 2 => KDF_HMAC_WRITEPROTECTED_KEY_FIXED,
_ => unreachable!(),
};
- rand_out.copy_from_slice(src);
state.random_ctr = (state.random_ctr + 1) % 3;
- Ok(())
+ Ok(Box::new(Zeroizing::new(src)))
}
pub(super) fn util_write_data_sync(
diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs
index 4f1454d..6833d14 100644
--- a/src/rust/bitbox02-rust/src/keystore.rs
+++ b/src/rust/bitbox02-rust/src/keystore.rs
@@ -133,9 +133,13 @@ impl core::convert::From<securechip::Error> for Error {
}
}
-fn random_32_bytes(hal: &mut impl KeystoreHal) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
+async fn random_32_bytes(
+ hal: &mut impl KeystoreHal,
+) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
let (random, securechip) = hal.random_and_securechip();
- bitbox_core_utils::random::random_32_bytes(random, securechip).map_err(Into::into)
+ bitbox_core_utils::random::random_32_bytes(random, securechip)
+ .await
+ .map_err(Into::into)
}
#[derive(Copy, Clone)]
@@ -177,7 +181,7 @@ impl RetainedEncryptedBuffer {
data: &[u8],
purpose: &'static str,
) -> Result<Self, Error> {
- let rand: [u8; 32] = random_32_bytes(hal)?.as_slice().try_into().unwrap();
+ let rand: [u8; 32] = random_32_bytes(hal).await?.as_slice().try_into().unwrap();
let encryption_key = stretch_retained_seed_encryption_key(
hal,
&rand,
@@ -185,7 +189,7 @@ impl RetainedEncryptedBuffer {
&format!("{}_out", purpose),
)
.await?;
- let iv_rand = random_32_bytes(hal)?;
+ let iv_rand = random_32_bytes(hal).await?;
let iv: &[u8; 16] = iv_rand.first_chunk::<16>().unwrap();
let encrypted = bitbox_aes::encrypt_with_hmac(iv, &encryption_key, data);
Ok(RetainedEncryptedBuffer {
@@ -322,11 +326,16 @@ async fn encrypt_and_store_seed_internal(
let subsystems = hal.as_mut();
subsystems
.securechip
- .init_new_password(subsystems.memory, password, password_stretch_algo)
+ .init_new_password(
+ subsystems.random,
+ subsystems.memory,
+ password,
+ password_stretch_algo,
+ )
.await?
};
- let iv_rand = bitbox_core_utils::random::random_32_bytes_from_hal(hal)?;
+ let iv_rand = bitbox_core_utils::random::random_32_bytes_from_hal(hal).await?;
let iv: &[u8; 16] = iv_rand.first_chunk::<16>().unwrap();
let encrypted = bitbox_aes::encrypt_with_hmac(iv, secret.as_slice(), seed);
@@ -561,7 +570,7 @@ pub async fn create_and_store_seed(
return Err(Error::SeedSize);
}
- let mut seed_vec = bitbox_core_utils::random::random_32_bytes_from_hal(hal)?;
+ let mut seed_vec = bitbox_core_utils::random::random_32_bytes_from_hal(hal).await?;
let seed = &mut seed_vec[..seed_len];
// Mix in host entropy.
@@ -834,7 +843,9 @@ pub async fn secp256k1_schnorr_sign(
.map_err(|_| ())?;
}
- let aux_rand = bitbox_core_utils::random::random_32_bytes_from_hal(hal).map_err(|_| ())?;
+ let aux_rand = bitbox_core_utils::random::random_32_bytes_from_hal(hal)
+ .await
+ .map_err(|_| ())?;
let sig = SECP256K1.sign_schnorr_with_aux_rand(
&bitcoin::secp256k1::Message::from_digest(*msg),
&keypair,
diff --git a/src/rust/bitbox02-rust/src/reset.rs b/src/rust/bitbox02-rust/src/reset.rs
index 4b9be20..41eb804 100644
--- a/src/rust/bitbox02-rust/src/reset.rs
+++ b/src/rust/bitbox02-rust/src/reset.rs
@@ -25,7 +25,10 @@ pub(crate) async fn reset(hal: &mut impl crate::hal::Hal, status: bool) {
for _ in 0..5 {
let result = {
let subsystems = hal.as_mut();
- subsystems.securechip.reset_keys(subsystems.memory).await
+ subsystems
+ .securechip
+ .reset_keys(subsystems.random, subsystems.memory)
+ .await
};
if result.is_ok() {
reset_ok = true;
diff --git a/src/rust/bitbox02/src/hal/securechip.rs b/src/rust/bitbox02/src/hal/securechip.rs
index 300854c..20d9435 100644
--- a/src/rust/bitbox02/src/hal/securechip.rs
+++ b/src/rust/bitbox02/src/hal/securechip.rs
@@ -77,17 +77,19 @@ fn to_c_password_stretch_algo(algo: PasswordStretchAlgo) -> bitbox_securechip::P
}
impl SecureChip for BitBox02SecureChip {
- fn random(&mut self) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
- crate::securechip::random().map_err(to_hal_error)
+ async fn random(&mut self) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
+ crate::securechip::random().await.map_err(to_hal_error)
}
async fn init_new_password(
&mut self,
+ random: &mut impl bitbox_hal::Random,
memory: &mut impl bitbox_hal::Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
crate::securechip::init_new_password(
+ random,
memory,
password,
to_c_password_stretch_algo(password_stretch_algo),
@@ -131,8 +133,12 @@ impl SecureChip for BitBox02SecureChip {
crate::securechip::model().map(to_hal_model)
}
- async fn reset_keys(&mut self, memory: &mut impl bitbox_hal::Memory) -> Result<(), ()> {
- crate::securechip::reset_keys(memory).await
+ async fn reset_keys(
+ &mut self,
+ random: &mut impl bitbox_hal::Random,
+ memory: &mut impl bitbox_hal::Memory,
+ ) -> Result<(), ()> {
+ crate::securechip::reset_keys(random, memory).await
}
#[cfg(feature = "app-u2f")]
@@ -144,8 +150,21 @@ impl SecureChip for BitBox02SecureChip {
#[cfg(test)]
mod tests {
use super::*;
+ use bitbox_hal::Random;
use hex_lit::hex;
+ struct TestRandom;
+
+ impl Random for TestRandom {
+ fn factory_randomness(&mut self) -> &'static [u8; 32] {
+ &[0; 32]
+ }
+
+ fn mcu_32_bytes(&mut self, out: &mut [u8; 32]) {
+ *out = [0; 32];
+ }
+ }
+
#[test]
fn test_to_hal_model() {
assert_eq!(
@@ -274,10 +293,16 @@ mod tests {
#[async_test::test]
async fn test_init_new_password_invalid_password_stretch_algo() {
let mut securechip = BitBox02SecureChip;
+ let mut random = TestRandom;
let mut memory = crate::hal::memory::BitBox02Memory;
assert_eq!(
securechip
- .init_new_password(&mut memory, "password", PasswordStretchAlgo::V0)
+ .init_new_password(
+ &mut random,
+ &mut memory,
+ "password",
+ PasswordStretchAlgo::V0
+ )
.await,
Err(Error::SecureChip(
SecureChipError::InvalidPasswordStretchAlgo,
diff --git a/src/rust/bitbox02/src/securechip/imp.rs b/src/rust/bitbox02/src/securechip/imp.rs
index 6a33cbe..5fbe708 100644
--- a/src/rust/bitbox02/src/securechip/imp.rs
+++ b/src/rust/bitbox02/src/securechip/imp.rs
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
use alloc::boxed::Box;
-use bitbox_hal::Memory;
+use bitbox_hal::{Memory, Random};
use bitbox_securechip::{Error, Model, PasswordStretchAlgo, atecc, optiga};
use core::ffi::c_int;
use util::cell::SyncCell;
@@ -26,10 +26,10 @@ pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Resul
}
}
-pub fn random() -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
+pub async fn random() -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
match backend() {
Backend::Atecc => atecc::random(),
- Backend::Optiga => optiga::random(),
+ Backend::Optiga => optiga::random().await,
}
}
@@ -40,21 +40,24 @@ pub async fn monotonic_increments_remaining() -> Result<u32, ()> {
}
}
-pub async fn reset_keys(memory: &mut impl Memory) -> Result<(), ()> {
+pub async fn reset_keys(random: &mut impl Random, memory: &mut impl Memory) -> Result<(), ()> {
match backend() {
Backend::Atecc => atecc::reset_keys(),
- Backend::Optiga => optiga::reset_keys(memory).await,
+ Backend::Optiga => optiga::reset_keys(random, memory).await,
}
}
pub async fn init_new_password(
+ random: &mut impl Random,
memory: &mut impl Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
match backend() {
Backend::Atecc => atecc::init_new_password(memory, password, password_stretch_algo),
- Backend::Optiga => optiga::init_new_password(memory, password, password_stretch_algo).await,
+ Backend::Optiga => {
+ optiga::init_new_password(random, memory, password, password_stretch_algo).await
+ }
}
}
@@ -126,8 +129,9 @@ pub unsafe extern "C" fn rust_securechip_setup(
/// Resets the secure-chip objects involved in password stretching.
#[unsafe(no_mangle)]
pub extern "C" fn rust_securechip_reset_keys() -> bool {
+ let mut random = crate::hal::random::BitBox02Random;
let mut memory = crate::hal::memory::BitBox02Memory;
- util::bb02_async::block_on(reset_keys(&mut memory)).is_ok()
+ util::bb02_async::block_on(reset_keys(&mut random, &mut memory)).is_ok()
}
/// Generates a new device attestation key and writes the public key to `pubkey_out`.
@@ -142,9 +146,14 @@ pub unsafe extern "C" fn rust_securechip_gen_attestation_key(pubkey_out: *mut u8
/// 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 {
- match backend() {
- Backend::Atecc => unsafe { bitbox_securechip_sys::atecc_random(rand_out) == 0 },
- Backend::Optiga => unsafe { bitbox_securechip_sys::optiga_random(rand_out) == 0 },
+ match util::bb02_async::block_on(random()) {
+ Ok(random) => {
+ unsafe {
+ core::ptr::copy_nonoverlapping(random.as_ptr(), rand_out, 32);
+ }
+ true
+ }
+ Err(_) => false,
}
}
diff --git a/src/rust/bitbox02/src/securechip/imp_fake.rs b/src/rust/bitbox02/src/securechip/imp_fake.rs
index 07379db..9d693fa 100644
--- a/src/rust/bitbox02/src/securechip/imp_fake.rs
+++ b/src/rust/bitbox02/src/securechip/imp_fake.rs
@@ -28,7 +28,7 @@ pub fn attestation_sign(_challenge: &[u8; 32], _signature: &mut [u8; 64]) -> Res
Err(())
}
-pub fn random() -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
+pub async fn random() -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
Ok(Box::new(Zeroizing::new([0u8; 32])))
}
@@ -36,11 +36,15 @@ pub async fn monotonic_increments_remaining() -> Result<u32, ()> {
Ok(1)
}
-pub async fn reset_keys(_memory: &mut impl bitbox_hal::Memory) -> Result<(), ()> {
+pub async fn reset_keys(
+ _random: &mut impl bitbox_hal::Random,
+ _memory: &mut impl bitbox_hal::Memory,
+) -> Result<(), ()> {
Ok(())
}
pub async fn init_new_password(
+ _random: &mut impl bitbox_hal::Random,
_memory: &mut impl bitbox_hal::Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
diff --git a/src/rust/bitbox03/src/securechip.rs b/src/rust/bitbox03/src/securechip.rs
index bc633f2..80146b5 100644
--- a/src/rust/bitbox03/src/securechip.rs
+++ b/src/rust/bitbox03/src/securechip.rs
@@ -3,7 +3,7 @@ use bitbox_hal as hal;
pub struct BitBox03SecureChip;
impl hal::securechip::SecureChip for BitBox03SecureChip {
- fn random(
+ async fn random(
&mut self,
) -> Result<alloc::boxed::Box<zeroize::Zeroizing<[u8; 32]>>, bitbox_hal::securechip::Error>
{
@@ -12,6 +12,7 @@ impl hal::securechip::SecureChip for BitBox03SecureChip {
async fn init_new_password(
&mut self,
+ _random: &mut impl bitbox_hal::Random,
_memory: &mut impl bitbox_hal::Memory,
_password: &str,
_password_stretch_algo: bitbox_hal::memory::PasswordStretchAlgo,
@@ -54,7 +55,11 @@ impl hal::securechip::SecureChip for BitBox03SecureChip {
todo!()
}
- async fn reset_keys(&mut self, _memory: &mut impl bitbox_hal::Memory) -> Result<(), ()> {
+ async fn reset_keys(
+ &mut self,
+ _random: &mut impl bitbox_hal::Random,
+ _memory: &mut impl bitbox_hal::Memory,
+ ) -> Result<(), ()> {
todo!()
}
Why this scored 33/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.