securechip: change output type of password stretch to [u8; 32]
What changed, and why it matters
This commit is a straightforward code cleanup: it changes the return type of password-stretching functions from a variable-length byte list to a fixed 32-byte array, because the result is always exactly 32 bytes. The change makes the code clearer and lets the compiler enforce the size, but it does not fix any security bug or change behavior.
No security action required; treat as normal refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors secure-chip password-stretching APIs across the Rust codebase from Zeroizing<Vec<u8>> to Box<Zeroizing<[u8; 32]>>. All call sites are updated to use .as_slice() / .as_ref() where needed, and an explicit length check in verify_seed is removed because the type system now guarantees 32 bytes. The underlying C FFI calls, HMAC/SHA-256 logic, and zeroization behavior remain unchanged. No vulnerability is addressed.
Changed components
securechip HAL traitATECC backendOptiga backendBitBox02 securechip wrapperBitBox03 securechip stubhost simulator fake securechipkeystore seed encryption/decryptionInspect captured patch +48 / −48
diff --git a/src/rust/bitbox-hal/src/securechip.rs b/src/rust/bitbox-hal/src/securechip.rs
index e5ed864..94ab69c 100644
--- a/src/rust/bitbox-hal/src/securechip.rs
+++ b/src/rust/bitbox-hal/src/securechip.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
-use alloc::{boxed::Box, vec::Vec};
+use alloc::boxed::Box;
use super::memory::PasswordStretchAlgo;
@@ -61,7 +61,7 @@ pub trait SecureChip {
&mut self,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
- ) -> Result<zeroize::Zeroizing<Vec<u8>>, Error>;
+ ) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error>;
/// Stretches `password` using secrets stored in the secure chip.
///
@@ -71,7 +71,7 @@ pub trait SecureChip {
&mut self,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
- ) -> Result<zeroize::Zeroizing<Vec<u8>>, Error>;
+ ) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error>;
/// Runs the secure-chip KDF with `msg` and returns the zeroizing 32-byte result.
///
diff --git a/src/rust/bitbox-platform-host/src/securechip.rs b/src/rust/bitbox-platform-host/src/securechip.rs
index aa6321f..fe9ded6 100644
--- a/src/rust/bitbox-platform-host/src/securechip.rs
+++ b/src/rust/bitbox-platform-host/src/securechip.rs
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
+use alloc::boxed::Box;
use alloc::collections::VecDeque;
-use alloc::{boxed::Box, vec::Vec};
#[cfg(all(feature = "simulator-graphical", not(feature = "testing")))]
use bitbox_hal::Timer;
@@ -82,7 +82,7 @@ impl bitbox_hal::SecureChip for FakeSecureChip {
&mut self,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
- ) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
+ ) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
self.event_counter += 3;
let key: &'static [u8] = match password_stretch_algo {
@@ -93,16 +93,16 @@ impl bitbox_hal::SecureChip for FakeSecureChip {
let mut engine = HmacEngine::<sha256::Hash>::new(key);
engine.input(password.as_bytes());
let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
- Ok(zeroize::Zeroizing::new(
- hmac_result.to_byte_array().to_vec(),
- ))
+ Ok(Box::new(zeroize::Zeroizing::new(
+ hmac_result.to_byte_array(),
+ )))
}
fn stretch_password(
&mut self,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
- ) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
+ ) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
self.event_counter += match password_stretch_algo {
PasswordStretchAlgo::V0 => 5,
PasswordStretchAlgo::V1 => 4,
@@ -117,9 +117,9 @@ impl bitbox_hal::SecureChip for FakeSecureChip {
let mut engine = HmacEngine::<sha256::Hash>::new(key);
engine.input(password.as_bytes());
let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
- Ok(zeroize::Zeroizing::new(
- hmac_result.to_byte_array().to_vec(),
- ))
+ Ok(Box::new(zeroize::Zeroizing::new(
+ hmac_result.to_byte_array(),
+ )))
}
async fn kdf(&mut self, msg: &[u8; 32]) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
diff --git a/src/rust/bitbox-securechip/src/atecc.rs b/src/rust/bitbox-securechip/src/atecc.rs
index fa6d9e3..1eadea9 100644
--- a/src/rust/bitbox-securechip/src/atecc.rs
+++ b/src/rust/bitbox-securechip/src/atecc.rs
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{Error, Model, PasswordStretchAlgo, SecureChipError};
-use alloc::{boxed::Box, vec, vec::Vec};
+use alloc::boxed::Box;
use zeroize::Zeroizing;
pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Result<(), ()> {
@@ -40,10 +40,10 @@ pub fn reset_keys() -> Result<(), ()> {
pub fn init_new_password(
password: &str,
password_stretch_algo: PasswordStretchAlgo,
-) -> Result<Zeroizing<Vec<u8>>, Error> {
+) -> Result<Box<Zeroizing<[u8; 32]>>, 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 mut stretched = Box::new(Zeroizing::new([0u8; 32]));
let status = unsafe {
bitbox_securechip_sys::atecc_init_new_password(
password.as_ptr().cast(),
@@ -61,10 +61,10 @@ pub fn init_new_password(
pub fn stretch_password(
password: &str,
password_stretch_algo: PasswordStretchAlgo,
-) -> Result<Zeroizing<Vec<u8>>, Error> {
+) -> Result<Box<Zeroizing<[u8; 32]>>, 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 mut stretched = Box::new(Zeroizing::new([0u8; 32]));
let status = unsafe {
bitbox_securechip_sys::atecc_stretch_password(
password.as_ptr().cast(),
diff --git a/src/rust/bitbox-securechip/src/optiga.rs b/src/rust/bitbox-securechip/src/optiga.rs
index 8d658b8..3dde9d3 100644
--- a/src/rust/bitbox-securechip/src/optiga.rs
+++ b/src/rust/bitbox-securechip/src/optiga.rs
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{Error, Model, PasswordStretchAlgo, SecureChipError};
-use alloc::{boxed::Box, vec, vec::Vec};
+use alloc::boxed::Box;
use zeroize::Zeroizing;
mod ops;
@@ -54,10 +54,10 @@ pub fn reset_keys() -> Result<(), ()> {
pub fn init_new_password(
password: &str,
password_stretch_algo: PasswordStretchAlgo,
-) -> Result<Zeroizing<Vec<u8>>, Error> {
+) -> Result<Box<Zeroizing<[u8; 32]>>, 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 mut stretched = Box::new(Zeroizing::new([0u8; 32]));
let status = unsafe {
bitbox_securechip_sys::optiga_init_new_password(
password.as_ptr().cast(),
@@ -75,10 +75,10 @@ pub fn init_new_password(
pub fn stretch_password(
password: &str,
password_stretch_algo: PasswordStretchAlgo,
-) -> Result<Zeroizing<Vec<u8>>, Error> {
+) -> Result<Box<Zeroizing<[u8; 32]>>, 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 mut stretched = Box::new(Zeroizing::new([0u8; 32]));
let status = unsafe {
bitbox_securechip_sys::optiga_stretch_password(
password.as_ptr().cast(),
diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs
index 9b1a5dd..2f3c06e 100644
--- a/src/rust/bitbox02-rust/src/keystore.rs
+++ b/src/rust/bitbox02-rust/src/keystore.rs
@@ -237,14 +237,10 @@ pub fn is_locked() -> bool {
fn verify_seed(
hal: &mut impl crate::hal::Hal,
- encryption_key: &[u8],
+ encryption_key: &[u8; 32],
expected_seed: &[u8],
expected_password_stretch_also: memory::PasswordStretchAlgo,
) -> bool {
- if encryption_key.len() != 32 {
- return false;
- }
-
let (cipher, password_stretch_algo) = match hal.memory().get_encrypted_seed_and_hmac() {
Ok(cipher) => cipher,
Err(_) => return false,
@@ -328,7 +324,7 @@ async fn encrypt_and_store_seed_internal(
let iv_rand = bitbox_core_utils::random::random_32_bytes_from_hal(hal)?;
let iv: &[u8; 16] = iv_rand.first_chunk::<16>().unwrap();
- let encrypted = bitbox_aes::encrypt_with_hmac(iv, &secret, seed);
+ let encrypted = bitbox_aes::encrypt_with_hmac(iv, secret.as_slice(), seed);
if encrypted.len() > u8::MAX as usize {
panic!("encrypted seed length overflow");
@@ -338,7 +334,7 @@ async fn encrypt_and_store_seed_internal(
.set_encrypted_seed_and_hmac(&encrypted, password_stretch_algo)
.map_err(|_| Error::Memory)?;
- if !verify_seed(hal, &secret, seed, password_stretch_algo) {
+ if !verify_seed(hal, secret.as_ref(), seed, password_stretch_algo) {
hal.memory().reset_hww().map_err(|_| Error::Memory)?;
return Err(Error::Memory);
}
@@ -433,7 +429,7 @@ fn get_and_decrypt_seed(
let secret = hal
.securechip()
.stretch_password(password, password_stretch_algo)?;
- let seed = match bitbox_aes::decrypt_with_hmac(&secret, &encrypted) {
+ let seed = match bitbox_aes::decrypt_with_hmac(secret.as_slice(), &encrypted) {
Ok(seed) => seed,
Err(()) => return Err(Error::IncorrectPassword),
};
@@ -1565,7 +1561,7 @@ mod tests {
.unwrap();
let iv: &[u8; 16] = &[0xaau8; 16];
- bitbox_aes::encrypt_with_hmac(iv, &secret, &seed)
+ bitbox_aes::encrypt_with_hmac(iv, secret.as_slice(), &seed)
};
mock_hal
diff --git a/src/rust/bitbox02/src/hal/securechip.rs b/src/rust/bitbox02/src/hal/securechip.rs
index b5e7dc6..272c556 100644
--- a/src/rust/bitbox02/src/hal/securechip.rs
+++ b/src/rust/bitbox02/src/hal/securechip.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
-use alloc::{boxed::Box, vec::Vec};
+use alloc::boxed::Box;
use bitbox_hal::SecureChip;
use bitbox_hal::memory::PasswordStretchAlgo;
@@ -85,7 +85,7 @@ impl SecureChip for BitBox02SecureChip {
&mut self,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
- ) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
+ ) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
crate::securechip::init_new_password(
password,
to_c_password_stretch_algo(password_stretch_algo),
@@ -97,7 +97,7 @@ impl SecureChip for BitBox02SecureChip {
&mut self,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
- ) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
+ ) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
crate::securechip::stretch_password(
password,
to_c_password_stretch_algo(password_stretch_algo),
diff --git a/src/rust/bitbox02/src/securechip/imp.rs b/src/rust/bitbox02/src/securechip/imp.rs
index 608d998..f8fe8d8 100644
--- a/src/rust/bitbox02/src/securechip/imp.rs
+++ b/src/rust/bitbox02/src/securechip/imp.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
-use alloc::{boxed::Box, vec::Vec};
+use alloc::boxed::Box;
use bitbox_securechip::{Error, Model, PasswordStretchAlgo, atecc, optiga};
use core::ffi::c_int;
use util::cell::SyncCell;
@@ -49,7 +49,7 @@ pub fn reset_keys() -> Result<(), ()> {
pub fn init_new_password(
password: &str,
password_stretch_algo: PasswordStretchAlgo,
-) -> Result<Zeroizing<Vec<u8>>, Error> {
+) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
match backend() {
Backend::Atecc => atecc::init_new_password(password, password_stretch_algo),
Backend::Optiga => optiga::init_new_password(password, password_stretch_algo),
@@ -59,7 +59,7 @@ pub fn init_new_password(
pub fn stretch_password(
password: &str,
password_stretch_algo: PasswordStretchAlgo,
-) -> Result<Zeroizing<Vec<u8>>, Error> {
+) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
match backend() {
Backend::Atecc => atecc::stretch_password(password, password_stretch_algo),
Backend::Optiga => optiga::stretch_password(password, password_stretch_algo),
diff --git a/src/rust/bitbox02/src/securechip/imp_fake.rs b/src/rust/bitbox02/src/securechip/imp_fake.rs
index e072877..4c57372 100644
--- a/src/rust/bitbox02/src/securechip/imp_fake.rs
+++ b/src/rust/bitbox02/src/securechip/imp_fake.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
-use alloc::{boxed::Box, vec::Vec};
+use alloc::boxed::Box;
use bitbox_securechip::{Error, Model, PasswordStretchAlgo, SecureChipError};
use hex_lit::hex;
use hmac::{Hmac, Mac};
@@ -43,24 +43,26 @@ pub fn reset_keys() -> Result<(), ()> {
pub fn init_new_password(
password: &str,
password_stretch_algo: PasswordStretchAlgo,
-) -> Result<Zeroizing<Vec<u8>>, Error> {
+) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
if password_stretch_algo != PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1 {
return Err(Error::SecureChip(
SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO,
));
}
- Ok(Zeroizing::new(
- hmac_sha256(PASSWORD_STRETCH_KEY, password.as_bytes()).to_vec(),
- ))
+ Ok(Box::new(Zeroizing::new(hmac_sha256(
+ PASSWORD_STRETCH_KEY,
+ password.as_bytes(),
+ ))))
}
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(),
- ))
+) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
+ Ok(Box::new(Zeroizing::new(hmac_sha256(
+ PASSWORD_STRETCH_KEY,
+ password.as_bytes(),
+ ))))
}
/// Perform the secure chip KDF with the message in `msg` and return the zeroizing 32-byte
diff --git a/src/rust/bitbox03/src/securechip.rs b/src/rust/bitbox03/src/securechip.rs
index a432eff..a4d66ca 100644
--- a/src/rust/bitbox03/src/securechip.rs
+++ b/src/rust/bitbox03/src/securechip.rs
@@ -14,7 +14,8 @@ impl hal::securechip::SecureChip for BitBox03SecureChip {
&mut self,
_password: &str,
_password_stretch_algo: bitbox_hal::memory::PasswordStretchAlgo,
- ) -> Result<zeroize::Zeroizing<alloc::vec::Vec<u8>>, bitbox_hal::securechip::Error> {
+ ) -> Result<alloc::boxed::Box<zeroize::Zeroizing<[u8; 32]>>, bitbox_hal::securechip::Error>
+ {
todo!()
}
@@ -22,7 +23,8 @@ impl hal::securechip::SecureChip for BitBox03SecureChip {
&mut self,
_password: &str,
_password_stretch_algo: bitbox_hal::memory::PasswordStretchAlgo,
- ) -> Result<zeroize::Zeroizing<alloc::vec::Vec<u8>>, bitbox_hal::securechip::Error> {
+ ) -> Result<alloc::boxed::Box<zeroize::Zeroizing<[u8; 32]>>, bitbox_hal::securechip::Error>
+ {
todo!()
}
Why this scored 15/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.