Skip to content

Commit 6aad2a2

Browse files
committed
Merge branch 'port-salt'
2 parents ab86eb7 + cfa4c4d commit 6aad2a2

File tree

10 files changed

+169
-163
lines changed

10 files changed

+169
-163
lines changed

src/rust/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/rust/bitbox02-rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub mod hal;
3636
pub mod hash;
3737
pub mod hww;
3838
pub mod keystore;
39+
pub mod salt;
3940
pub mod secp256k1;
4041
#[cfg(feature = "app-u2f")]
4142
mod u2f;

src/rust/bitbox02-rust/src/salt.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2025 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use alloc::vec::Vec;
16+
use core::ffi::c_char;
17+
18+
use bitbox02::memory;
19+
use sha2::Digest;
20+
use util::bytes::{Bytes, BytesMut};
21+
use zeroize::Zeroizing;
22+
23+
/// Creates `SHA256(salt_root || purpose || data)`, where `salt_root` is a persisted value that
24+
/// remains unchanged until the device is reset. The `purpose` string namespaces individual uses of
25+
/// the salt, and the provided `data` slice is hashed alongside it.
26+
///
27+
/// Returns `Err(())` if the salt root cannot be retrieved from persistent storage.
28+
pub fn hash_data(data: &[u8], purpose: &str) -> Result<Zeroizing<Vec<u8>>, ()> {
29+
let salt_root = memory::get_salt_root()?;
30+
31+
let mut hasher = sha2::Sha256::new();
32+
hasher.update(salt_root.as_slice());
33+
hasher.update(purpose.as_bytes());
34+
hasher.update(data);
35+
36+
Ok(Zeroizing::new(hasher.finalize().to_vec()))
37+
}
38+
39+
/// # Safety
40+
///
41+
/// `purpose` must be a valid, null-terminated UTF-8 string pointer.
42+
#[unsafe(no_mangle)]
43+
pub unsafe extern "C" fn rust_salt_hash_data(
44+
data: Bytes,
45+
purpose: *const c_char,
46+
mut hash_out: BytesMut,
47+
) -> bool {
48+
let purpose_str = match unsafe { bitbox02::util::str_from_null_terminated_ptr(purpose) } {
49+
Ok(purpose) => purpose,
50+
Err(()) => return false,
51+
};
52+
match hash_data(data.as_ref(), purpose_str) {
53+
Ok(hash) => {
54+
hash_out.as_mut()[..32].copy_from_slice(&hash);
55+
true
56+
}
57+
Err(()) => false,
58+
}
59+
}
60+
61+
#[cfg(test)]
62+
mod tests {
63+
use super::*;
64+
use bitbox02::testing::mock_memory;
65+
use core::convert::TryInto;
66+
use core::ptr;
67+
use hex_lit::hex;
68+
69+
const MOCK_SALT_ROOT: [u8; 32] =
70+
hex!("0000000000000000111111111111111122222222222222223333333333333333");
71+
72+
#[test]
73+
fn test_hash_data() {
74+
mock_memory();
75+
memory::set_salt_root(&MOCK_SALT_ROOT).unwrap();
76+
77+
let data = hex!("001122334455667788");
78+
let expected = hex!("62db8dcd47ddf8e81809c377ed96643855d3052bb73237100ca81f0f5a7611e6");
79+
80+
let hash = hash_data(&data, "test purpose").unwrap();
81+
assert_eq!(hash.as_slice(), &expected);
82+
}
83+
84+
#[test]
85+
fn test_hash_data_empty_inputs() {
86+
mock_memory();
87+
memory::set_salt_root(&MOCK_SALT_ROOT).unwrap();
88+
89+
let expected = hex!("2dbb05dd73d94edba6946611aaca367f76c809e96f20499ad674e596050f9833");
90+
91+
let hash = hash_data(&[], "").unwrap();
92+
assert_eq!(hash.as_slice(), &expected);
93+
}
94+
95+
#[test]
96+
fn test_rust_salt_hash_data() {
97+
mock_memory();
98+
memory::set_salt_root(&MOCK_SALT_ROOT).unwrap();
99+
100+
let data = hex!("001122334455667788");
101+
let expected = hex!("62db8dcd47ddf8e81809c377ed96643855d3052bb73237100ca81f0f5a7611e6");
102+
103+
let mut hash_out = [0u8; 32];
104+
let purpose = c"test purpose";
105+
assert!(unsafe {
106+
rust_salt_hash_data(
107+
util::bytes::rust_util_bytes(data.as_ptr(), data.len()),
108+
purpose.as_ptr(),
109+
util::bytes::rust_util_bytes_mut(hash_out.as_mut_ptr(), hash_out.len()),
110+
)
111+
});
112+
assert_eq!(hash_out, expected);
113+
}
114+
115+
#[test]
116+
fn test_rust_salt_hash_data_empty_inputs() {
117+
mock_memory();
118+
memory::set_salt_root(&MOCK_SALT_ROOT).unwrap();
119+
120+
let expected = hex!("2dbb05dd73d94edba6946611aaca367f76c809e96f20499ad674e596050f9833");
121+
let mut hash_out = [0u8; 32];
122+
assert!(unsafe {
123+
rust_salt_hash_data(
124+
util::bytes::rust_util_bytes(ptr::null(), 0),
125+
c"".as_ptr(),
126+
util::bytes::rust_util_bytes_mut(hash_out.as_mut_ptr(), hash_out.len()),
127+
)
128+
});
129+
assert_eq!(hash_out, expected);
130+
}
131+
}

src/rust/bitbox02-sys/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ const ALLOWLIST_FNS: &[&str] = &[
9999
"memory_is_initialized",
100100
"memory_is_mnemonic_passphrase_enabled",
101101
"memory_is_seeded",
102+
"memory_get_salt_root",
102103
"memory_multisig_get_by_hash",
103104
"memory_multisig_set_by_hash",
104105
"memory_set_device_name",

src/rust/bitbox02/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ hex = { workspace = true }
3333
hex = { workspace = true }
3434
bitbox-aes = { path = "../bitbox-aes" }
3535
bitbox02-rust = { path = "../bitbox02-rust" }
36+
hex_lit = { workspace = true }
3637

3738
[features]
3839
# Only to be enabled in unit tests and simulators

src/rust/bitbox02/src/memory.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
extern crate alloc;
1717
use alloc::string::String;
18+
use alloc::vec::Vec;
1819

1920
// deduct one for the null terminator.
2021
pub const DEVICE_NAME_MAX_LEN: usize = bitbox02_sys::MEMORY_DEVICE_NAME_MAX_LEN as usize - 1;
@@ -232,6 +233,15 @@ pub fn ble_enable(enable: bool) -> Result<(), ()> {
232233
if res { Ok(()) } else { Err(()) }
233234
}
234235

236+
pub fn get_salt_root() -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
237+
let mut salt_root = zeroize::Zeroizing::new(vec![0u8; 32]);
238+
if unsafe { bitbox02_sys::memory_get_salt_root(salt_root.as_mut_ptr()) } {
239+
Ok(salt_root)
240+
} else {
241+
Err(())
242+
}
243+
}
244+
235245
#[cfg(feature = "testing")]
236246
pub fn set_salt_root(salt_root: &[u8; 32]) -> Result<(), ()> {
237247
match unsafe { bitbox02_sys::memory_set_salt_root(salt_root.as_ptr()) } {
@@ -244,9 +254,29 @@ pub fn set_salt_root(salt_root: &[u8; 32]) -> Result<(), ()> {
244254
mod tests {
245255
use super::*;
246256

257+
use hex_lit::hex;
258+
247259
#[test]
248260
fn test_get_attestation_bootloader_hash() {
249-
let expected: [u8; 32] = *b"\x71\x3d\xf0\xd5\x8c\x71\x7d\x40\x31\x78\x7c\xdc\x8f\xa3\x5b\x90\x25\x82\xbe\x6a\xb6\xa2\x2e\x09\xde\x44\x77\xd3\x0e\x22\x30\xfc";
261+
let expected: [u8; 32] =
262+
hex!("713df0d58c717d4031787cdc8fa35b902582be6ab6a22e09de4477d30e2230fc");
250263
assert_eq!(get_attestation_bootloader_hash(), expected);
251264
}
265+
266+
#[test]
267+
fn test_get_salt_root_roundtrip() {
268+
let original = get_salt_root().unwrap();
269+
270+
let expected = hex!("00112233445566778899aabbccddeefffeeddccbbaa998877665544332211000");
271+
272+
set_salt_root(expected.as_slice().try_into().unwrap()).unwrap();
273+
let salt_root = get_salt_root().unwrap();
274+
assert_eq!(salt_root.as_slice(), &expected);
275+
276+
let erased = [0xffu8; 32];
277+
set_salt_root(&erased).unwrap();
278+
assert!(get_salt_root().is_err());
279+
280+
set_salt_root(original.as_slice().try_into().unwrap()).unwrap();
281+
}
252282
}

src/salt.c

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,9 @@
2121

2222
bool salt_hash_data(const uint8_t* data, size_t data_len, const char* purpose, uint8_t* hash_out)
2323
{
24-
if (data_len > 0 && data == NULL) {
24+
if ((data_len > 0 && data == NULL) || purpose == NULL || hash_out == NULL) {
2525
return false;
2626
}
27-
if (!purpose || !hash_out) {
28-
return false;
29-
}
30-
31-
uint8_t salt_root[32];
32-
UTIL_CLEANUP_32(salt_root);
33-
if (!memory_get_salt_root(salt_root)) {
34-
return false;
35-
}
36-
37-
void* ctx = rust_sha256_new();
38-
rust_sha256_update(ctx, salt_root, sizeof(salt_root));
39-
rust_sha256_update(ctx, purpose, strlen(purpose));
40-
if (data != NULL) {
41-
rust_sha256_update(ctx, data, data_len);
42-
}
43-
rust_sha256_finish(&ctx, hash_out);
44-
return true;
27+
return rust_salt_hash_data(
28+
rust_util_bytes(data, data_len), purpose, rust_util_bytes_mut(hash_out, 32));
4529
}

test/unit-test/CMakeLists.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-parameter -Wno-missing-prototype
3434
add_library(mocks STATIC EXCLUDE_FROM_ALL
3535
framework/src/mock_gestures.c
3636
framework/src/mock_screen_stack.c
37-
framework/src/mock_memory.c
3837
framework/src/mock_qtouch.c
3938
)
4039
target_link_libraries(mocks PUBLIC c-unit-tests_rust_c ${CMOCKA_LDFLAGS})
@@ -66,8 +65,6 @@ set(TEST_LIST
6665
"-Wl,--wrap=memory_read_chunk_fake,--wrap=memory_write_chunk_fake,--wrap=rust_noise_generate_static_private_key,--wrap=memory_read_shared_bootdata_fake,--wrap=memory_write_to_address_fake,--wrap=random_32_bytes_mcu"
6766
memory_functional
6867
""
69-
salt
70-
"-Wl,--wrap=memory_get_salt_root"
7168
cipher
7269
"-Wl,--wrap=cipher_fake_iv"
7370
util

test/unit-test/framework/src/mock_memory.c

Lines changed: 0 additions & 76 deletions
This file was deleted.

0 commit comments

Comments
 (0)