memory: add password stretch algo memory flag
What changed, and why it matters
This commit adds a new memory flag that records which password-stretching algorithm was used to protect the wallet seed. It does not change the actual stretching math yet; it only stores the choice so future firmware can support a newer algorithm while still recognizing older backups. There is no immediate security fix, but it is infrastructure for a future security improvement.
No urgent action required. Treat as normal firmware maintenance. Monitor follow-up commits that actually implement V1 stretching and flip the Optiga default, since this commit only lays groundwork.
Security signals we found
New persisted security-parameter field (password stretch algorithm identifier)
Defensive validation added: ATECC returns SC_ERR_INVALID_PASSWORD_STRETCH_ALGO for unsupported algorithms
Future algorithm agility scaffolding for password-based seed encryption
No actual cryptographic change to stretching routine in this commit
Evidence from the diff
The patch introduces memory_password_stretch_algo_t (V0 for legacy ATECC/Optiga stretching, V1 reserved for a future Optiga algorithm) and persists one byte of it in chunk_1 of the device flash. All call sites that set or get the encrypted seed now carry the algorithm identifier, and the secure chip abstraction passes it through to atecc_ and optiga_ functions. ATECC currently rejects any algorithm other than V0; Optiga accepts V0/V1 but still uses the same V0 implementation. A TODO in keystore.rs notes that Optiga will eventually default to V1 once implemented. The change is backward-compatible: V0 is encoded as 0xFF (the previous reserved byte value), so existing seeded devices read as V0.
Changed components
src/memory/memory.c / memory.hsrc/securechip/securechip.c / securechip.hsrc/atecc/atecc.c / atecc.hsrc/optiga/optiga.c / optiga.hsrc/rust/bitbox02/src/memory.rs / securechip.rssrc/rust/bitbox02-rust/src/hal.rs / keystore.rstest/hardware-fakes/src/fake_securechip.ctest/unit-test/test_optiga.cInspect captured patch +303 / −79
diff --git a/src/atecc/atecc.c b/src/atecc/atecc.c
index 48d841a..6942d47 100644
--- a/src/atecc/atecc.c
+++ b/src/atecc/atecc.c
@@ -4,6 +4,7 @@
#include "hardfault.h"
#include "securechip/securechip.h"
#include <i2c_ecc.h>
+#include <memory/memory.h>
#include <rust/rust.h>
#include <salt.h>
#include <util.h>
@@ -572,17 +573,29 @@ int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out)
return _atecc_kdf(ATECC_SLOT_KDF, msg, len, kdf_out);
}
-int atecc_init_new_password(const char* password)
+int atecc_init_new_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo)
{
(void)password;
+ if (password_stretch_algo != MEMORY_PASSWORD_STRETCH_ALGO_V0) {
+ return SC_ERR_INVALID_PASSWORD_STRETCH_ALGO;
+ }
if (!atecc_reset_keys()) {
return SC_ATECC_ERR_RESET_KEYS;
}
return 0;
}
-int atecc_stretch_password(const char* password, uint8_t* stretched_out)
+int atecc_stretch_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out)
{
+ if (password_stretch_algo != MEMORY_PASSWORD_STRETCH_ALGO_V0) {
+ return SC_ERR_INVALID_PASSWORD_STRETCH_ALGO;
+ }
+
uint8_t password_salted_hashed[32] = {0};
UTIL_CLEANUP_32(password_salted_hashed);
if (!salt_hash_data(
diff --git a/src/atecc/atecc.h b/src/atecc/atecc.h
index 563e9f6..8b4e0f8 100644
--- a/src/atecc/atecc.h
+++ b/src/atecc/atecc.h
@@ -15,8 +15,13 @@
USE_RESULT int atecc_setup(const securechip_interface_functions_t* ifs);
USE_RESULT int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out);
-USE_RESULT int atecc_init_new_password(const char* password);
-USE_RESULT int atecc_stretch_password(const char* password, uint8_t* stretched_out);
+USE_RESULT int atecc_init_new_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo);
+USE_RESULT int atecc_stretch_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out);
USE_RESULT bool atecc_reset_keys(void);
USE_RESULT bool atecc_gen_attestation_key(uint8_t* pubkey_out);
USE_RESULT bool atecc_attestation_sign(const uint8_t* challenge, uint8_t* signature_out);
diff --git a/src/memory/memory.c b/src/memory/memory.c
index 83609a2..5cbd852 100644
--- a/src/memory/memory.c
+++ b/src/memory/memory.c
@@ -75,7 +75,8 @@ typedef union {
struct __attribute__((__packed__)) {
uint8_t bitmask; // inverse bitmask, BITMASK_* bits
uint8_t failed_unlock_attempts; // starts at 0xFF (0 failed attempts), counting downwards
- uint8_t reserved[2];
+ uint8_t password_stretch_algo; // see `memory_password_stretch_algo_t`.
+ uint8_t reserved[1];
uint8_t noise_static_private_key[32]; // CURVE25519
uint8_t noise_remote_static_pubkeys[5][NOISE_PUBKEY_SIZE]; // 5 pubkey slots
uint8_t salt_root[32];
@@ -485,7 +486,10 @@ bool memory_reset_failed_unlock_attempts(void)
return _write_chunk(CHUNK_1, chunk.bytes);
}
-bool memory_set_encrypted_seed_and_hmac(const uint8_t* encrypted_seed_and_hmac, uint8_t len)
+bool memory_set_encrypted_seed_and_hmac(
+ const uint8_t* encrypted_seed_and_hmac,
+ uint8_t len,
+ memory_password_stretch_algo_t password_stretch_algo)
{
chunk_1_t chunk = {0};
CLEANUP_CHUNK(chunk);
@@ -494,6 +498,18 @@ bool memory_set_encrypted_seed_and_hmac(const uint8_t* encrypted_seed_and_hmac,
}
_read_chunk(CHUNK_1, chunk_bytes);
chunk.fields.encrypted_seed_and_hmac_len = len;
+
+ switch (password_stretch_algo) {
+ case MEMORY_PASSWORD_STRETCH_ALGO_V0:
+ chunk.fields.password_stretch_algo = 0xFF;
+ break;
+ case MEMORY_PASSWORD_STRETCH_ALGO_V1:
+ chunk.fields.password_stretch_algo = 0x00;
+ break;
+ default:
+ return false;
+ }
+
memset(
chunk.fields.encrypted_seed_and_hmac, 0xFF, sizeof(chunk.fields.encrypted_seed_and_hmac));
memcpy(chunk.fields.encrypted_seed_and_hmac, encrypted_seed_and_hmac, len);
@@ -504,7 +520,10 @@ bool memory_set_encrypted_seed_and_hmac(const uint8_t* encrypted_seed_and_hmac,
return _write_chunk(CHUNK_1, chunk.bytes);
}
-bool memory_get_encrypted_seed_and_hmac(uint8_t* encrypted_seed_and_hmac_out, uint8_t* len_out)
+bool memory_get_encrypted_seed_and_hmac(
+ uint8_t* encrypted_seed_and_hmac_out,
+ uint8_t* len_out,
+ memory_password_stretch_algo_t* password_stretch_algo_out)
{
if (!memory_is_seeded()) {
return false;
@@ -517,6 +536,18 @@ bool memory_get_encrypted_seed_and_hmac(uint8_t* encrypted_seed_and_hmac_out, ui
chunk.fields.encrypted_seed_and_hmac,
sizeof(chunk.fields.encrypted_seed_and_hmac));
*len_out = chunk.fields.encrypted_seed_and_hmac_len;
+
+ switch (chunk.fields.password_stretch_algo) {
+ case 0xFF:
+ *password_stretch_algo_out = MEMORY_PASSWORD_STRETCH_ALGO_V0;
+ break;
+ case 0x00:
+ *password_stretch_algo_out = MEMORY_PASSWORD_STRETCH_ALGO_V1;
+ break;
+ default:
+ return false;
+ }
+
return true;
}
diff --git a/src/memory/memory.h b/src/memory/memory.h
index 192f416..42e3f21 100644
--- a/src/memory/memory.h
+++ b/src/memory/memory.h
@@ -17,6 +17,14 @@
// How many multisig configurations (accounts) can be registered.
#define MEMORY_MULTISIG_NUM_ENTRIES 25
+typedef enum {
+ // Legacy/initial value for BitBox02 and BitBox02 Nova using the initial stretch algo in
+ // ATECC/Optiga.
+ MEMORY_PASSWORD_STRETCH_ALGO_V0,
+ // Currently used only by Optiga.
+ MEMORY_PASSWORD_STRETCH_ALGO_V1,
+} memory_password_stretch_algo_t;
+
typedef struct {
void (*const random_32_bytes)(uint8_t* buf_out);
} memory_interface_functions_t;
@@ -124,18 +132,22 @@ USE_RESULT bool memory_reset_failed_unlock_attempts(void);
USE_RESULT bool memory_set_encrypted_seed_and_hmac(
const uint8_t* encrypted_seed_and_hmac,
- uint8_t len);
+ uint8_t len,
+ memory_password_stretch_algo_t password_stretch_algo);
/**
* Retrieves the encrypted seed and hmac.
" param[out] encrypted_seed_and_hmac_out must have size 96.
" param[out] len_out will contain the length of the encrypted seed.
+ " param[out] password_stretch_algo_out will contain the identifier of the password stretching
+ * algorithm that was used in the encryption.
* memory_is_seeded() must return true prior to calling this
* function, otherwise the result is undefined.
*/
USE_RESULT bool memory_get_encrypted_seed_and_hmac(
uint8_t* encrypted_seed_and_hmac_out,
- uint8_t* len_out);
+ uint8_t* len_out,
+ memory_password_stretch_algo_t* password_stretch_algo_out);
void memory_get_io_protection_key(uint8_t* key_out);
void memory_get_authorization_key(uint8_t* key_out);
diff --git a/src/optiga/optiga.c b/src/optiga/optiga.c
index e6e44ef..a7e7e04 100644
--- a/src/optiga/optiga.c
+++ b/src/optiga/optiga.c
@@ -10,6 +10,7 @@
#include <hardfault.h>
#include <memory/bitbox02_smarteeprom.h>
+#include <memory/memory.h>
#include <optiga_crypt.h>
#include <optiga_util.h>
#include <rust/rust.h>
@@ -980,8 +981,12 @@ cleanup: {
}
}
-int optiga_init_new_password(const char* password)
+int optiga_init_new_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo)
{
+ (void)password_stretch_algo;
+
// Set new hmac key.
uint8_t new_hmac_key[32] = {0};
_ifs->random_32_bytes(new_hmac_key);
@@ -1042,7 +1047,9 @@ bool optiga_reset_keys(void)
// 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 fine for the purpose of resetting
// the keys.
- return optiga_init_new_password("") == 0;
+
+ // We reset using V1, the latest algorithm. It covers resetting everything from V0 as well.
+ return optiga_init_new_password("", MEMORY_PASSWORD_STRETCH_ALGO_V1) == 0;
}
static int _optiga_verify_password(const char* password, uint8_t* password_secret_out)
@@ -1334,8 +1341,13 @@ static int _kdf_internal(const uint8_t* msg, size_t len, uint8_t* kdf_out)
return 0;
}
-int optiga_stretch_password(const char* password, uint8_t* stretched_out)
+int optiga_stretch_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out)
{
+ (void)password_stretch_algo;
+
uint8_t password_salted_hashed[32] = {0};
UTIL_CLEANUP_32(password_salted_hashed);
if (!salt_hash_data(
diff --git a/src/optiga/optiga.h b/src/optiga/optiga.h
index 9c5f84a..635592d 100644
--- a/src/optiga/optiga.h
+++ b/src/optiga/optiga.h
@@ -9,6 +9,7 @@
#include "compiler_util.h"
#include "securechip/securechip.h"
#include <memory/bitbox02_smarteeprom.h>
+#include <memory/memory.h>
#include <platform/platform_config.h>
#include <stdbool.h>
#include <stddef.h>
@@ -59,8 +60,13 @@
USE_RESULT int optiga_setup(const securechip_interface_functions_t* ifs);
USE_RESULT int optiga_kdf_external(const uint8_t* msg, size_t len, uint8_t* mac_out);
-USE_RESULT int optiga_init_new_password(const char* password);
-USE_RESULT int optiga_stretch_password(const char* password, uint8_t* stretched_out);
+USE_RESULT int optiga_init_new_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo);
+USE_RESULT int optiga_stretch_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out);
USE_RESULT bool optiga_reset_keys(void);
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);
diff --git a/src/rust/bitbox02-rust/src/hal.rs b/src/rust/bitbox02-rust/src/hal.rs
index b140a30..1694506 100644
--- a/src/rust/bitbox02-rust/src/hal.rs
+++ b/src/rust/bitbox02-rust/src/hal.rs
@@ -27,10 +27,15 @@ pub trait Random {
}
pub trait SecureChip {
- fn init_new_password(&mut self, password: &str) -> Result<(), bitbox02::securechip::Error>;
+ fn init_new_password(
+ &mut self,
+ password: &str,
+ password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
+ ) -> Result<(), bitbox02::securechip::Error>;
fn stretch_password(
&mut self,
password: &str,
+ password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error>;
fn kdf(
&mut self,
@@ -60,8 +65,14 @@ pub trait Memory {
fn is_seeded(&mut self) -> bool;
fn is_initialized(&mut self) -> bool;
fn set_initialized(&mut self) -> Result<(), ()>;
- fn get_encrypted_seed_and_hmac(&mut self) -> Result<alloc::vec::Vec<u8>, ()>;
- fn set_encrypted_seed_and_hmac(&mut self, data: &[u8]) -> Result<(), ()>;
+ fn get_encrypted_seed_and_hmac(
+ &mut self,
+ ) -> Result<(alloc::vec::Vec<u8>, bitbox02::memory::PasswordStretchAlgo), ()>;
+ fn set_encrypted_seed_and_hmac(
+ &mut self,
+ data: &[u8],
+ password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
+ ) -> Result<(), ()>;
fn reset_hww(&mut self) -> Result<(), ()>;
fn get_unlock_attempts(&mut self) -> u8;
fn increment_unlock_attempts(&mut self);
@@ -140,15 +151,20 @@ impl Random for BitBox02Random {
pub struct BitBox02SecureChip;
impl SecureChip for BitBox02SecureChip {
- fn init_new_password(&mut self, password: &str) -> Result<(), bitbox02::securechip::Error> {
- bitbox02::securechip::init_new_password(password)
+ fn init_new_password(
+ &mut self,
+ password: &str,
+ password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
+ ) -> Result<(), bitbox02::securechip::Error> {
+ bitbox02::securechip::init_new_password(password, password_stretch_algo)
}
fn stretch_password(
&mut self,
password: &str,
+ password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error> {
- bitbox02::securechip::stretch_password(password)
+ bitbox02::securechip::stretch_password(password, password_stretch_algo)
}
fn kdf(
@@ -231,12 +247,18 @@ impl Memory for BitBox02Memory {
bitbox02::memory::set_initialized()
}
- fn get_encrypted_seed_and_hmac(&mut self) -> Result<alloc::vec::Vec<u8>, ()> {
+ fn get_encrypted_seed_and_hmac(
+ &mut self,
+ ) -> Result<(alloc::vec::Vec<u8>, bitbox02::memory::PasswordStretchAlgo), ()> {
bitbox02::memory::get_encrypted_seed_and_hmac()
}
- fn set_encrypted_seed_and_hmac(&mut self, data: &[u8]) -> Result<(), ()> {
- bitbox02::memory::set_encrypted_seed_and_hmac(data)
+ fn set_encrypted_seed_and_hmac(
+ &mut self,
+ data: &[u8],
+ password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
+ ) -> Result<(), ()> {
+ bitbox02::memory::set_encrypted_seed_and_hmac(data, password_stretch_algo)
}
fn reset_hww(&mut self) -> Result<(), ()> {
@@ -433,7 +455,7 @@ pub mod testing {
is_seeded: bool,
mnemonic_passphrase_enabled: bool,
seed_birthdate: u32,
- encrypted_seed_and_hmac: Option<Vec<u8>>,
+ encrypted_seed_and_hmac: Option<(Vec<u8>, bitbox02::memory::PasswordStretchAlgo)>,
device_name: Option<String>,
unlock_attempts: u8,
salt_root: [u8; 32],
@@ -488,6 +510,7 @@ pub mod testing {
fn init_new_password(
&mut self,
_password: &str,
+ _password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
) -> Result<(), bitbox02::securechip::Error> {
self.event_counter += 1;
Ok(())
@@ -496,6 +519,7 @@ pub mod testing {
fn stretch_password(
&mut self,
password: &str,
+ _password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error> {
self.event_counter += 5;
@@ -664,16 +688,22 @@ pub mod testing {
Ok(())
}
- fn get_encrypted_seed_and_hmac(&mut self) -> Result<alloc::vec::Vec<u8>, ()> {
+ fn get_encrypted_seed_and_hmac(
+ &mut self,
+ ) -> Result<(alloc::vec::Vec<u8>, bitbox02::memory::PasswordStretchAlgo), ()> {
self.encrypted_seed_and_hmac.clone().ok_or(())
}
- fn set_encrypted_seed_and_hmac(&mut self, data: &[u8]) -> Result<(), ()> {
+ fn set_encrypted_seed_and_hmac(
+ &mut self,
+ data: &[u8],
+ password_stretch_algo: bitbox02::memory::PasswordStretchAlgo,
+ ) -> Result<(), ()> {
// 96 is the max space allocated in BitBox02's memory for this.
if data.len() > 96 {
return Err(());
}
- self.encrypted_seed_and_hmac = Some(data.to_vec());
+ self.encrypted_seed_and_hmac = Some((data.to_vec(), password_stretch_algo));
self.is_seeded = true;
Ok(())
}
diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs
index 6bebde8..e52816e 100644
--- a/src/rust/bitbox02-rust/src/keystore.rs
+++ b/src/rust/bitbox02-rust/src/keystore.rs
@@ -157,15 +157,19 @@ fn verify_seed(
hal: &mut impl crate::hal::Hal,
encryption_key: &[u8],
expected_seed: &[u8],
+ expected_password_stretch_also: bitbox02::memory::PasswordStretchAlgo,
) -> bool {
if encryption_key.len() != 32 {
return false;
}
- let cipher = match hal.memory().get_encrypted_seed_and_hmac() {
+ let (cipher, password_stretch_algo) = match hal.memory().get_encrypted_seed_and_hmac() {
Ok(cipher) => cipher,
Err(_) => return false,
};
+ if password_stretch_algo != expected_password_stretch_also {
+ return false;
+ }
let decrypted = match bitbox_aes::decrypt_with_hmac(encryption_key, &cipher) {
Ok(decrypted) => decrypted,
Err(_) => return false,
@@ -202,6 +206,25 @@ fn retain_bip39_seed(hal: &mut impl crate::hal::Hal, bip39_seed: &[u8]) -> Resul
Ok(())
}
+/// Returns the stretching algo that will be used when setting new passwords.
+fn default_password_stretch_algo(
+ hal: &mut impl crate::hal::Hal,
+) -> Result<bitbox02::memory::PasswordStretchAlgo, Error> {
+ match hal
+ .memory()
+ .get_securechip_type()
+ .map_err(|_| Error::Memory)?
+ {
+ bitbox02::memory::SecurechipType::Atecc => {
+ Ok(bitbox02::memory::PasswordStretchAlgo::MEMORY_PASSWORD_STRETCH_ALGO_V0)
+ }
+ bitbox02::memory::SecurechipType::Optiga => {
+ // TODO: flip to V1 once implemented
+ Ok(bitbox02::memory::PasswordStretchAlgo::MEMORY_PASSWORD_STRETCH_ALGO_V0)
+ }
+ }
+}
+
/// Internal helper to encrypt a seed with a password and store it on flash
fn encrypt_and_store_seed_internal(
hal: &mut impl crate::hal::Hal,
@@ -218,9 +241,14 @@ fn encrypt_and_store_seed_internal(
bitbox02::usb_processing::timeout_reset(LONG_TIMEOUT);
- hal.securechip().init_new_password(password)?;
+ let password_stretch_algo = default_password_stretch_algo(hal)?;
- let secret = hal.securechip().stretch_password(password)?;
+ hal.securechip()
+ .init_new_password(password, password_stretch_algo)?;
+
+ let secret = hal
+ .securechip()
+ .stretch_password(password, password_stretch_algo)?;
let iv_rand = hal.random().random_32_bytes();
let iv: &[u8; 16] = iv_rand.first_chunk::<16>().unwrap();
@@ -231,10 +259,10 @@ fn encrypt_and_store_seed_internal(
}
hal.memory()
- .set_encrypted_seed_and_hmac(&encrypted)
+ .set_encrypted_seed_and_hmac(&encrypted, password_stretch_algo)
.map_err(|_| Error::Memory)?;
- if !verify_seed(hal, &secret, seed) {
+ if !verify_seed(hal, &secret, seed, password_stretch_algo) {
hal.memory().reset_hww().map_err(|_| Error::Memory)?;
return Err(Error::Memory);
}
@@ -296,7 +324,7 @@ fn get_and_decrypt_seed(
hal: &mut impl crate::hal::Hal,
password: &str,
) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
- let encrypted = hal
+ let (encrypted, password_stretch_algo) = hal
.memory()
.get_encrypted_seed_and_hmac()
.map_err(|_| Error::Memory)?;
@@ -304,7 +332,9 @@ fn get_and_decrypt_seed(
// wrong, so it already returns an error here. The ATECC stretches the password without checking
// if the password is correct, and we determine if it is correct in the seed decryption
// step below.
- let secret = hal.securechip().stretch_password(password)?;
+ let secret = hal
+ .securechip()
+ .stretch_password(password, password_stretch_algo)?;
let seed = match bitbox_aes::decrypt_with_hmac(&secret, &encrypted) {
Ok(seed) => seed,
Err(()) => return Err(Error::IncorrectPassword),
@@ -846,8 +876,12 @@ mod tests {
);
// Check the seed has been stored encrypted with the expected encryption key.
// Decrypt and check seed.
- let cipher = hal.memory.get_encrypted_seed_and_hmac().unwrap();
+ let (cipher, password_stretch_algo) = hal.memory.get_encrypted_seed_and_hmac().unwrap();
+ assert_eq!(
+ password_stretch_algo,
+ bitbox02::memory::PasswordStretchAlgo::MEMORY_PASSWORD_STRETCH_ALGO_V0
+ );
// Same as Python:
// import hmac, hashlib; hmac.digest(b"unit-test", b"password", hashlib.sha256).hex()
// See also: mock_securechip.c
diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs
index b5b8f81..941916e 100644
--- a/src/rust/bitbox02-sys/build.rs
+++ b/src/rust/bitbox02-sys/build.rs
@@ -189,6 +189,7 @@ const ALLOWLIST_FNS: &[&str] = &[
const RUSTIFIED_ENUMS: &[&str] = &[
"event_types",
"keystore_secp256k1_pubkey_format",
+ "memory_password_stretch_algo_t",
"memory_result_t",
"multisig_script_type_t",
"output_type_t",
diff --git a/src/rust/bitbox02/src/memory.rs b/src/rust/bitbox02/src/memory.rs
index af5696e..ab40266 100644
--- a/src/rust/bitbox02/src/memory.rs
+++ b/src/rust/bitbox02/src/memory.rs
@@ -12,6 +12,7 @@ pub const MULTISIG_NAME_MAX_LEN: usize = bitbox02_sys::MEMORY_MULTISIG_NAME_MAX_
pub use bitbox02_sys::memory_ble_metadata_t as BleMetadata;
+pub use bitbox02_sys::memory_password_stretch_algo_t as PasswordStretchAlgo;
pub use bitbox02_sys::memory_result_t as MemoryError;
#[derive(Debug)]
@@ -83,13 +84,20 @@ pub fn get_attestation_pubkey_and_certificate(
}
}
-pub fn get_encrypted_seed_and_hmac() -> Result<alloc::vec::Vec<u8>, ()> {
+pub fn get_encrypted_seed_and_hmac() -> Result<(alloc::vec::Vec<u8>, PasswordStretchAlgo), ()> {
let mut out = vec![0u8; 96];
let mut len = 0u8;
- match unsafe { bitbox02_sys::memory_get_encrypted_seed_and_hmac(out.as_mut_ptr(), &mut len) } {
+ let mut password_stretch_algo = PasswordStretchAlgo::MEMORY_PASSWORD_STRETCH_ALGO_V0;
+ match unsafe {
+ bitbox02_sys::memory_get_encrypted_seed_and_hmac(
+ out.as_mut_ptr(),
+ &mut len,
+ &mut password_stretch_algo,
+ )
+ } {
true => {
out.truncate(len as _);
- Ok(out)
+ Ok((out, password_stretch_algo))
}
false => Err(()),
}
@@ -136,12 +144,19 @@ pub fn get_seed_birthdate() -> u32 {
}
}
-pub fn set_encrypted_seed_and_hmac(data: &[u8]) -> Result<(), ()> {
+pub fn set_encrypted_seed_and_hmac(
+ data: &[u8],
+ password_stretch_algo: PasswordStretchAlgo,
+) -> Result<(), ()> {
if data.len() > u8::MAX as usize {
return Err(());
}
match unsafe {
- bitbox02_sys::memory_set_encrypted_seed_and_hmac(data.as_ptr(), data.len() as u8)
+ bitbox02_sys::memory_set_encrypted_seed_and_hmac(
+ data.as_ptr(),
+ data.len() as u8,
+ password_stretch_algo,
+ )
} {
true => Ok(()),
false => Err(()),
@@ -431,7 +446,11 @@ mod tests {
assert!(set_initialized().is_err());
let seed_data: Vec<u8> = (0..96).map(|i| i as u8).collect();
- set_encrypted_seed_and_hmac(&seed_data).unwrap();
+ set_encrypted_seed_and_hmac(
+ &seed_data,
+ PasswordStretchAlgo::MEMORY_PASSWORD_STRETCH_ALGO_V0,
+ )
+ .unwrap();
assert!(is_seeded());
assert!(!is_initialized());
@@ -661,19 +680,27 @@ mod tests {
#[test]
fn test_encrypted_seed_and_hmac_roundtrip() {
- mock_memory();
-
- assert!(!is_seeded());
- let seed_data: Vec<u8> = (0..96).map(|i| i as u8).collect();
- set_encrypted_seed_and_hmac(&seed_data).unwrap();
- assert!(is_seeded());
-
- let stored = get_encrypted_seed_and_hmac().unwrap();
- assert_eq!(stored, seed_data);
-
- let oversized = vec![0u8; 97];
- assert!(set_encrypted_seed_and_hmac(&oversized).is_err());
- assert_eq!(get_encrypted_seed_and_hmac().unwrap(), stored);
+ for algo in [
+ PasswordStretchAlgo::MEMORY_PASSWORD_STRETCH_ALGO_V0,
+ PasswordStretchAlgo::MEMORY_PASSWORD_STRETCH_ALGO_V1,
+ ] {
+ mock_memory();
+
+ assert!(!is_seeded());
+ let seed_data: Vec<u8> = (0..96).map(|i| i as u8).collect();
+ set_encrypted_seed_and_hmac(&seed_data, algo).unwrap();
+ assert!(is_seeded());
+
+ let (stored, stored_algo) = get_encrypted_seed_and_hmac().unwrap();
+ assert_eq!(stored, seed_data);
+ assert_eq!(stored_algo, algo);
+
+ let oversized = vec![0u8; 97];
+ assert!(set_encrypted_seed_and_hmac(&oversized, algo).is_err());
+ let (stored, stored_algo) = get_encrypted_seed_and_hmac().unwrap();
+ assert_eq!(stored, seed_data);
+ assert_eq!(stored_algo, algo);
+ }
}
#[test]
diff --git a/src/rust/bitbox02/src/securechip.rs b/src/rust/bitbox02/src/securechip.rs
index 89557ae..f44815a 100644
--- a/src/rust/bitbox02/src/securechip.rs
+++ b/src/rust/bitbox02/src/securechip.rs
@@ -8,6 +8,8 @@ use zeroize::Zeroizing;
pub use bitbox02_sys::securechip_error_t as SecureChipError;
pub use bitbox02_sys::securechip_model_t as Model;
+use crate::memory::PasswordStretchAlgo;
+
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
SecureChip(SecureChipError),
@@ -15,13 +17,14 @@ pub enum Error {
}
// Keep in sync with securechip.h's securechip_error_t.
-const SECURECHIP_ERRORS: [SecureChipError; 15] = [
+const SECURECHIP_ERRORS: [SecureChipError; 16] = [
// 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,
// Errors specific to the ATECC
SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG,
SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_DATA,
@@ -80,10 +83,15 @@ pub fn reset_keys() -> Result<(), ()> {
}
}
-pub fn init_new_password(password: &str) -> Result<(), Error> {
+pub fn init_new_password(
+ password: &str,
+ password_stretch_algo: PasswordStretchAlgo,
+) -> Result<(), Error> {
let password = crate::util::str_to_cstr_vec_zeroizing(password)
.map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS))?;
- let status = unsafe { bitbox02_sys::securechip_init_new_password(password.as_ptr().cast()) };
+ let status = unsafe {
+ bitbox02_sys::securechip_init_new_password(password.as_ptr().cast(), password_stretch_algo)
+ };
if status == 0 {
Ok(())
} else {
@@ -91,12 +99,19 @@ pub fn init_new_password(password: &str) -> Result<(), Error> {
}
}
-pub fn stretch_password(password: &str) -> Result<Zeroizing<Vec<u8>>, Error> {
+pub fn stretch_password(
+ password: &str,
+ password_stretch_algo: PasswordStretchAlgo,
+) -> Result<Zeroizing<Vec<u8>>, Error> {
let password = crate::util::str_to_cstr_vec_zeroizing(password)
.map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS))?;
let mut stretched = Zeroizing::new(vec![0u8; 32]);
let status = unsafe {
- bitbox02_sys::securechip_stretch_password(password.as_ptr().cast(), stretched.as_mut_ptr())
+ bitbox02_sys::securechip_stretch_password(
+ password.as_ptr().cast(),
+ password_stretch_algo,
+ stretched.as_mut_ptr(),
+ )
};
if status == 0 {
Ok(stretched)
diff --git a/src/securechip/securechip.c b/src/securechip/securechip.c
index 5a9a0e3..abfd094 100644
--- a/src/securechip/securechip.c
+++ b/src/securechip/securechip.c
@@ -4,14 +4,20 @@
#include <atecc/atecc.h>
#include <hardfault.h>
+#include <memory/memory.h>
#include <memory/memory_shared.h>
#include <optiga/optiga.h>
typedef struct {
int (*setup)(const securechip_interface_functions_t* fns);
int (*kdf)(const uint8_t* msg, size_t msg_len, uint8_t* kdf_out);
- int (*init_new_password)(const char* password);
- int (*stretch_password)(const char* password, uint8_t* stretched_out);
+ int (*init_new_password)(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo);
+ int (*stretch_password)(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out);
bool (*reset_keys)(void);
bool (*gen_attestation_key)(uint8_t* pubkey_out);
bool (*attestation_sign)(const uint8_t* challenge, uint8_t* signature_out);
@@ -92,16 +98,21 @@ int securechip_kdf(const uint8_t* msg, size_t msg_len, uint8_t* mac_out)
return _fns.kdf(msg, msg_len, mac_out);
}
-int securechip_init_new_password(const char* password)
+int securechip_init_new_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo)
{
ABORT_IF_NULL(init_new_password);
- return _fns.init_new_password(password);
+ return _fns.init_new_password(password, password_stretch_algo);
}
-int securechip_stretch_password(const char* password, uint8_t* stretched_out)
+int securechip_stretch_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out)
{
ABORT_IF_NULL(stretch_password);
- return _fns.stretch_password(password, stretched_out);
+ return _fns.stretch_password(password, password_stretch_algo, stretched_out);
}
bool securechip_reset_keys(void)
diff --git a/src/securechip/securechip.h b/src/securechip/securechip.h
index d5c7691..0c599fc 100644
--- a/src/securechip/securechip.h
+++ b/src/securechip/securechip.h
@@ -4,6 +4,7 @@
#define _SECURECHIP_H_
#include "compiler_util.h"
+#include <memory/memory.h>
#include <platform/platform_config.h>
#include <stdbool.h>
#include <stddef.h>
@@ -20,6 +21,8 @@ typedef enum {
// securechip is consistent and the caller does not need to distinguish between the chips at the
// callsite.
SC_ERR_INCORRECT_PASSWORD = -6,
+ // The password stretch algo is not supported
+ SC_ERR_INVALID_PASSWORD_STRETCH_ALGO = -7,
// Errors specific to the ATECC
SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG = -100,
@@ -85,23 +88,30 @@ USE_RESULT int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out);
* Prepare the securechip for a new password: re-initialize keys used in the derivation,
* set up monotonic counters, etc.
* @param[in] password The user password.
+ * @param[in] password_stretch_algo the password stretching algorithm that should be used.
+ * @param[out] stretched_out the stretched password. Same as calling `securechip_stretch_password()`
+ * with the same stretching algo, but more efficient in terms of securechip operations.
* @return For ATECC: values of `atecc_error_t` if negative, values of `ATCA_STATUS` if positive, 0
* on success. For Optiga: values of `optiga_error_t` if negative, values of
* optiga_lib_return_codes.h if positive, 0 on success.
*/
-USE_RESULT int securechip_init_new_password(const char* password);
+USE_RESULT int securechip_init_new_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo);
/**
* Stretch password using secrets in the secure chip.
* Calling this function increments the monotonic counter.
- * @param[in] msg Use this msg as input
- * @param[in] len Must be <= 127.
- * @param[out] kdf_out Must have size 32. Result of the kdf will be stored here.
- * Cannot be the same as `msg`.
+ * @param[in] password The user password.
+ * @param[in] password_stretch_algo the password stretching algorithm that should be used.
+ * @param[out] stretched_out the stretched password.
* @return 0 on success. Values of `securechip_error_t` if negative. If positive, values of
* `ATCA_STATUS` for ATECC, values of optiga_lib_return_codes.h for Optiga.
*/
-USE_RESULT int securechip_stretch_password(const char* password, uint8_t* stretched_out);
+USE_RESULT int securechip_stretch_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out);
/**
* Reset the securechip objects involved in the password stretching.
diff --git a/test/hardware-fakes/src/fake_securechip.c b/test/hardware-fakes/src/fake_securechip.c
index 222c93f..edda6e1 100644
--- a/test/hardware-fakes/src/fake_securechip.c
+++ b/test/hardware-fakes/src/fake_securechip.c
@@ -19,13 +19,20 @@ int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out)
return 0;
}
-int securechip_init_new_password(const char* password)
+int securechip_init_new_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo)
{
(void)password;
+ (void)password_stretch_algo;
return 0;
}
-int securechip_stretch_password(const char* password, uint8_t* stretched_out)
+int securechip_stretch_password(
+ const char* password,
+ memory_password_stretch_algo_t password_stretch_algo,
+ uint8_t* stretched_out)
{
+ (void)password_stretch_algo;
uint8_t key[9] = "unit-test";
rust_hmac_sha256(key, sizeof(key), (const uint8_t*)password, strlen(password), stretched_out);
return 0;
diff --git a/test/unit-test/test_optiga.c b/test/unit-test/test_optiga.c
index 6932cb2..0e97bb8 100644
--- a/test/unit-test/test_optiga.c
+++ b/test/unit-test/test_optiga.c
@@ -5,6 +5,7 @@
#include <stddef.h>
#include <cmocka.h>
+#include <memory/memory.h>
#include <optiga/optiga.h>
#include <optiga/optiga_ops.h>
@@ -594,7 +595,8 @@ static void test_optiga_stretch_password_success(void** state)
memcpy(_oid_counter_password_buf, counter_reset_buf, sizeof(_oid_counter_password_buf));
uint8_t stretched_out[32] = {0};
- assert_int_equal(optiga_stretch_password("pw", stretched_out), 0);
+ assert_int_equal(
+ optiga_stretch_password("pw", MEMORY_PASSWORD_STRETCH_ALGO_V0, stretched_out), 0);
assert_memory_equal(stretched_out, _expected_stretched_out, sizeof(_expected_stretched_out));
// Successful password verification resets the small monotonic counter/threshold.
assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 0);
@@ -615,27 +617,35 @@ static void test_optiga_stretch_password_attempt_counter(void** state)
uint8_t stretched_out[32] = {0};
- assert_int_equal(optiga_stretch_password("wrong", stretched_out), SC_ERR_INCORRECT_PASSWORD);
+ assert_int_equal(
+ optiga_stretch_password("wrong", MEMORY_PASSWORD_STRETCH_ALGO_V0, stretched_out),
+ SC_ERR_INCORRECT_PASSWORD);
assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 1);
assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE);
- assert_int_equal(optiga_stretch_password("wrong", stretched_out), SC_ERR_INCORRECT_PASSWORD);
+ assert_int_equal(
+ optiga_stretch_password("wrong", MEMORY_PASSWORD_STRETCH_ALGO_V0, stretched_out),
+ SC_ERR_INCORRECT_PASSWORD);
assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 2);
assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE);
- assert_int_equal(optiga_stretch_password("pw", stretched_out), 0);
+ assert_int_equal(
+ optiga_stretch_password("pw", MEMORY_PASSWORD_STRETCH_ALGO_V0, stretched_out), 0);
assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 0);
assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE);
for (int i = 0; i < SMALL_MONOTONIC_COUNTER_MAX_USE; i++) {
assert_int_equal(
- optiga_stretch_password("wrong", stretched_out), SC_ERR_INCORRECT_PASSWORD);
+ optiga_stretch_password("wrong", MEMORY_PASSWORD_STRETCH_ALGO_V0, stretched_out),
+ SC_ERR_INCORRECT_PASSWORD);
}
assert_int_equal(
optiga_common_get_uint32(&_oid_counter_password_buf[0]), SMALL_MONOTONIC_COUNTER_MAX_USE);
// After exhausting all allowed attempts, a correct password fails as well.
- assert_int_equal(optiga_stretch_password("pw", stretched_out), SC_ERR_INCORRECT_PASSWORD);
+ assert_int_equal(
+ optiga_stretch_password("pw", MEMORY_PASSWORD_STRETCH_ALGO_V0, stretched_out),
+ SC_ERR_INCORRECT_PASSWORD);
assert_int_equal(
optiga_common_get_uint32(&_oid_counter_password_buf[0]), SMALL_MONOTONIC_COUNTER_MAX_USE);
}
Why this scored 28/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.