Skip to content

Commit 028c10f

Browse files
committed
Merge branch 'port-stretch'
2 parents 6aad2a2 + ed7c021 commit 028c10f

File tree

6 files changed

+234
-37
lines changed

6 files changed

+234
-37
lines changed

src/keystore.c

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -63,27 +63,6 @@ static bool _validate_seed_length(size_t seed_len)
6363
return seed_len == 16 || seed_len == 24 || seed_len == 32;
6464
}
6565

66-
USE_RESULT static keystore_error_t _stretch_retained_seed_encryption_key(
67-
const uint8_t* encryption_key,
68-
const char* purpose_in,
69-
const char* purpose_out,
70-
uint8_t* out)
71-
{
72-
uint8_t salted_hashed[32] = {0};
73-
UTIL_CLEANUP_32(salted_hashed);
74-
if (!salt_hash_data(encryption_key, 32, purpose_in, salted_hashed)) {
75-
return KEYSTORE_ERR_SALT;
76-
}
77-
if (securechip_kdf(salted_hashed, 32, out)) {
78-
return KEYSTORE_ERR_SECURECHIP;
79-
}
80-
if (!salt_hash_data(encryption_key, 32, purpose_out, salted_hashed)) {
81-
return KEYSTORE_ERR_SALT;
82-
}
83-
rust_hmac_sha256(salted_hashed, sizeof(salted_hashed), out, 32, out);
84-
return KEYSTORE_OK;
85-
}
86-
8766
bool keystore_copy_seed(uint8_t* seed_out, size_t* length_out)
8867
{
8968
if (!_is_unlocked_device) {
@@ -92,11 +71,15 @@ bool keystore_copy_seed(uint8_t* seed_out, size_t* length_out)
9271

9372
uint8_t retained_seed_encryption_key[32] = {0};
9473
UTIL_CLEANUP_32(retained_seed_encryption_key);
95-
if (_stretch_retained_seed_encryption_key(
96-
_unstretched_retained_seed_encryption_key,
74+
if (!rust_keystore_stretch_retained_seed_encryption_key(
75+
rust_util_bytes(
76+
_unstretched_retained_seed_encryption_key,
77+
sizeof(_unstretched_retained_seed_encryption_key)),
9778
"keystore_retained_seed_access_in",
9879
"keystore_retained_seed_access_out",
99-
retained_seed_encryption_key) != KEYSTORE_OK) {
80+
rust_util_bytes_mut(
81+
retained_seed_encryption_key,
82+
sizeof(retained_seed_encryption_key)))) {
10083
return false;
10184
}
10285
size_t len = _retained_seed_encrypted_len - 48;
@@ -122,11 +105,15 @@ bool keystore_copy_bip39_seed(uint8_t* bip39_seed_out)
122105

123106
uint8_t retained_bip39_seed_encryption_key[32] = {0};
124107
UTIL_CLEANUP_32(retained_bip39_seed_encryption_key);
125-
if (_stretch_retained_seed_encryption_key(
126-
_unstretched_retained_bip39_seed_encryption_key,
108+
if (!rust_keystore_stretch_retained_seed_encryption_key(
109+
rust_util_bytes(
110+
_unstretched_retained_bip39_seed_encryption_key,
111+
sizeof(_unstretched_retained_bip39_seed_encryption_key)),
127112
"keystore_retained_bip39_seed_access_in",
128113
"keystore_retained_bip39_seed_access_out",
129-
retained_bip39_seed_encryption_key) != KEYSTORE_OK) {
114+
rust_util_bytes_mut(
115+
retained_bip39_seed_encryption_key,
116+
sizeof(retained_bip39_seed_encryption_key)))) {
130117
return false;
131118
}
132119
size_t len = _retained_bip39_seed_encrypted_len - 48;
@@ -266,13 +253,15 @@ USE_RESULT static keystore_error_t _retain_seed(const uint8_t* seed, size_t seed
266253
#endif
267254
uint8_t retained_seed_encryption_key[32] = {0};
268255
UTIL_CLEANUP_32(retained_seed_encryption_key);
269-
keystore_error_t result = _stretch_retained_seed_encryption_key(
270-
_unstretched_retained_seed_encryption_key,
256+
bool stretched = rust_keystore_stretch_retained_seed_encryption_key(
257+
rust_util_bytes(
258+
_unstretched_retained_seed_encryption_key,
259+
sizeof(_unstretched_retained_seed_encryption_key)),
271260
"keystore_retained_seed_access_in",
272261
"keystore_retained_seed_access_out",
273-
retained_seed_encryption_key);
274-
if (result != KEYSTORE_OK) {
275-
return result;
262+
rust_util_bytes_mut(retained_seed_encryption_key, sizeof(retained_seed_encryption_key)));
263+
if (!stretched) {
264+
return KEYSTORE_ERR_STRETCH_RETAINED_SEED_KEY;
276265
}
277266
size_t len = seed_len + 64;
278267
if (!cipher_aes_hmac_encrypt(
@@ -299,11 +288,15 @@ USE_RESULT static bool _retain_bip39_seed(const uint8_t* bip39_seed)
299288
#endif
300289
uint8_t retained_bip39_seed_encryption_key[32] = {0};
301290
UTIL_CLEANUP_32(retained_bip39_seed_encryption_key);
302-
if (_stretch_retained_seed_encryption_key(
303-
_unstretched_retained_bip39_seed_encryption_key,
291+
if (!rust_keystore_stretch_retained_seed_encryption_key(
292+
rust_util_bytes(
293+
_unstretched_retained_bip39_seed_encryption_key,
294+
sizeof(_unstretched_retained_bip39_seed_encryption_key)),
304295
"keystore_retained_bip39_seed_access_in",
305296
"keystore_retained_bip39_seed_access_out",
306-
retained_bip39_seed_encryption_key) != KEYSTORE_OK) {
297+
rust_util_bytes_mut(
298+
retained_bip39_seed_encryption_key,
299+
sizeof(retained_bip39_seed_encryption_key)))) {
307300
return false;
308301
}
309302
size_t len = sizeof(_retained_bip39_seed_encrypted);

src/keystore.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ typedef enum {
4141
KEYSTORE_ERR_HASH,
4242
KEYSTORE_ERR_ENCRYPT,
4343
KEYSTORE_ERR_DECRYPT,
44+
KEYSTORE_ERR_STRETCH_RETAINED_SEED_KEY,
4445
} keystore_error_t;
4546

4647
/**

src/rust/bitbox02-rust/src/keystore.rs

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ use alloc::string::String;
1919
use alloc::vec::Vec;
2020

2121
use crate::bip32;
22-
use bitbox02::keystore;
22+
use bitbox02::{keystore, securechip};
2323

2424
use util::bip32::HARDENED;
2525

2626
use crate::secp256k1::SECP256K1;
2727

28-
use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha512};
28+
use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha256, sha512};
2929

3030
/// Returns the keystore's seed encoded as a BIP-39 mnemonic.
3131
pub fn get_bip39_mnemonic() -> Result<zeroize::Zeroizing<String>, ()> {
@@ -131,6 +131,62 @@ pub fn root_fingerprint() -> Result<Vec<u8>, ()> {
131131
keystore::root_fingerprint()
132132
}
133133

134+
/// Stretches the given encryption_key using the securechip. The resulting key is used to encrypt
135+
/// the retained seed or bip39 seed.
136+
pub fn stretch_retained_seed_encryption_key(
137+
encryption_key: &[u8; 32],
138+
purpose_in: &str,
139+
purpose_out: &str,
140+
) -> Result<zeroize::Zeroizing<Vec<u8>>, keystore::Error> {
141+
let salted_in =
142+
crate::salt::hash_data(encryption_key, purpose_in).map_err(|_| keystore::Error::Salt)?;
143+
144+
let kdf = securechip::kdf(salted_in.as_slice()).map_err(|err| match err {
145+
securechip::Error::SecureChip(sc_err) => keystore::Error::SecureChip(sc_err as i32),
146+
securechip::Error::Status(status) => keystore::Error::SecureChip(status),
147+
})?;
148+
149+
let salted_out =
150+
crate::salt::hash_data(encryption_key, purpose_out).map_err(|_| keystore::Error::Salt)?;
151+
152+
let mut engine = HmacEngine::<sha256::Hash>::new(salted_out.as_slice());
153+
engine.input(kdf.as_slice());
154+
let stretched = Hmac::<sha256::Hash>::from_engine(engine).to_byte_array();
155+
156+
Ok(zeroize::Zeroizing::new(stretched.to_vec()))
157+
}
158+
159+
/// # Safety
160+
///
161+
/// `encryption_key` must refer to a 32-byte buffer and `out` must have space for 32 bytes.
162+
/// `purpose_in` and `purpose_out` must be null-terminated C strings.
163+
#[unsafe(no_mangle)]
164+
pub unsafe extern "C" fn rust_keystore_stretch_retained_seed_encryption_key(
165+
encryption_key: util::bytes::Bytes,
166+
purpose_in: *const core::ffi::c_char,
167+
purpose_out: *const core::ffi::c_char,
168+
mut out: util::bytes::BytesMut,
169+
) -> bool {
170+
let encryption_key: [u8; 32] = match encryption_key.as_ref().try_into() {
171+
Ok(key) => key,
172+
Err(_) => return false,
173+
};
174+
let purpose_in = unsafe { bitbox02::util::str_from_null_terminated_ptr(purpose_in) };
175+
let purpose_out = unsafe { bitbox02::util::str_from_null_terminated_ptr(purpose_out) };
176+
let (purpose_in, purpose_out) = match (purpose_in, purpose_out) {
177+
(Ok(purpose_in), Ok(purpose_out)) => (purpose_in, purpose_out),
178+
_ => return false,
179+
};
180+
181+
match stretch_retained_seed_encryption_key(&encryption_key, purpose_in, purpose_out) {
182+
Ok(stretched) => {
183+
out.as_mut().copy_from_slice(stretched.as_slice());
184+
true
185+
}
186+
Err(_) => false,
187+
}
188+
}
189+
134190
fn bip85_entropy(keypath: &[u32]) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
135191
let priv_key = secp256k1_get_private_key_twice(keypath)?;
136192

@@ -455,6 +511,68 @@ mod tests {
455511
assert_eq!(root_fingerprint(), Err(()));
456512
}
457513

514+
#[test]
515+
fn test_stretch_retained_seed_encryption_key_success() {
516+
mock_memory();
517+
let salt_root = hex!("0000000000000000111111111111111122222222222222223333333333333333");
518+
bitbox02::memory::set_salt_root(&salt_root).unwrap();
519+
520+
let encryption_key =
521+
hex!("00112233445566778899aabbccddeeff112233445566778899aabbccddeeff00");
522+
523+
let stretched = stretch_retained_seed_encryption_key(
524+
&encryption_key,
525+
"keystore_retained_seed_access_in",
526+
"keystore_retained_seed_access_out",
527+
)
528+
.unwrap();
529+
530+
let expected = hex!("b6b20683810aee16b5603ae95d14eaae5ae2c8d9df9b66e1b67c698e627bb208");
531+
assert_eq!(stretched.as_slice(), expected.as_slice());
532+
}
533+
534+
#[test]
535+
fn test_rust_keystore_stretch_retained_seed_encryption_key_success() {
536+
mock_memory();
537+
let salt_root =
538+
hex::decode("0000000000000000111111111111111122222222222222223333333333333333")
539+
.unwrap();
540+
bitbox02::memory::set_salt_root(salt_root.as_slice().try_into().unwrap()).unwrap();
541+
542+
let encryption_key_vec =
543+
hex::decode("00112233445566778899aabbccddeeff112233445566778899aabbccddeeff00")
544+
.unwrap();
545+
546+
let mut out = [0u8; 32];
547+
let purpose_in = c"keystore_retained_seed_access_in";
548+
let purpose_out = c"keystore_retained_seed_access_out";
549+
550+
let success = unsafe {
551+
rust_keystore_stretch_retained_seed_encryption_key(
552+
util::bytes::rust_util_bytes(encryption_key_vec.as_ptr(), encryption_key_vec.len()),
553+
purpose_in.as_ptr(),
554+
purpose_out.as_ptr(),
555+
util::bytes::rust_util_bytes_mut(out.as_mut_ptr(), out.len()),
556+
)
557+
};
558+
assert!(success);
559+
let expected =
560+
hex::decode("b6b20683810aee16b5603ae95d14eaae5ae2c8d9df9b66e1b67c698e627bb208")
561+
.unwrap();
562+
assert_eq!(out, expected.as_slice());
563+
}
564+
565+
#[test]
566+
fn test_stretch_retained_seed_encryption_key_salt_error() {
567+
mock_memory();
568+
bitbox02::memory::set_salt_root(&[0xffu8; 32]).unwrap();
569+
570+
let encryption_key = [0u8; 32];
571+
let result =
572+
stretch_retained_seed_encryption_key(&encryption_key, "purpose_in", "purpose_out");
573+
assert!(matches!(result, Err(keystore::Error::Salt)));
574+
}
575+
458576
#[test]
459577
fn test_bip85_bip39() {
460578
keystore::lock();

src/rust/bitbox02-sys/build.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const ALLOWLIST_TYPES: &[&str] = &[
5050
"component_t",
5151
"confirm_params_t",
5252
"trinary_input_string_params_t",
53+
"securechip_error_t",
5354
];
5455

5556
const ALLOWLIST_FNS: &[&str] = &[
@@ -142,6 +143,7 @@ const ALLOWLIST_FNS: &[&str] = &[
142143
"sdcard_create",
143144
"secp256k1_ecdsa_anti_exfil_host_commit",
144145
"securechip_attestation_sign",
146+
"securechip_kdf",
145147
"securechip_model",
146148
"securechip_monotonic_increments_remaining",
147149
"securechip_u2f_counter_set",
@@ -167,6 +169,7 @@ const RUSTIFIED_ENUMS: &[&str] = &[
167169
"memory_result_t",
168170
"multisig_script_type_t",
169171
"output_type_t",
172+
"securechip_error_t",
170173
"securechip_model_t",
171174
"simple_type_t",
172175
"trinary_choice_t",

src/rust/bitbox02/src/securechip.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,62 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
extern crate alloc;
16+
17+
use alloc::vec::Vec;
18+
use zeroize::Zeroizing;
19+
20+
pub use bitbox02_sys::securechip_error_t as SecureChipError;
1521
pub use bitbox02_sys::securechip_model_t as Model;
1622

23+
#[derive(Debug, PartialEq, Eq)]
24+
pub enum Error {
25+
SecureChip(SecureChipError),
26+
Status(i32),
27+
}
28+
29+
// Keep in sync with securechip.h's securechip_error_t.
30+
const SECURECHIP_ERRORS: [SecureChipError; 15] = [
31+
// Errors common to any securechip implementation
32+
SecureChipError::SC_ERR_IFS,
33+
SecureChipError::SC_ERR_INVALID_ARGS,
34+
SecureChipError::SC_ERR_CONFIG_MISMATCH,
35+
SecureChipError::SC_ERR_SALT,
36+
SecureChipError::SC_ERR_INCORRECT_PASSWORD,
37+
// Errors specific to the ATECC
38+
SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG,
39+
SecureChipError::SC_ATECC_ERR_ZONE_UNLOCKED_DATA,
40+
SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_IO,
41+
SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_AUTH,
42+
SecureChipError::SC_ATECC_ERR_SLOT_UNLOCKED_ENC,
43+
SecureChipError::SC_ATECC_ERR_RESET_KEYS,
44+
// Errors specific to the Optiga
45+
SecureChipError::SC_OPTIGA_ERR_CREATE,
46+
SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_METADATA,
47+
SecureChipError::SC_OPTIGA_ERR_PAL,
48+
SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_LEN,
49+
];
50+
51+
fn securechip_error_from_status(status: i32) -> Option<SecureChipError> {
52+
SECURECHIP_ERRORS
53+
.iter()
54+
.copied()
55+
.find(|err| *err as i32 == status)
56+
}
57+
58+
impl Error {
59+
fn from_status(status: i32) -> Self {
60+
if status < 0 {
61+
match securechip_error_from_status(status) {
62+
Some(err) => Error::SecureChip(err),
63+
None => Error::Status(status),
64+
}
65+
} else {
66+
Error::Status(status)
67+
}
68+
}
69+
}
70+
1771
pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Result<(), ()> {
1872
match unsafe {
1973
bitbox02_sys::securechip_attestation_sign(challenge.as_ptr(), signature.as_mut_ptr())
@@ -31,6 +85,18 @@ pub fn monotonic_increments_remaining() -> Result<u32, ()> {
3185
}
3286
}
3387

88+
/// Perform the secure chip KDF with the message in `msg` and return the zeroizing 32-byte result.
89+
pub fn kdf(msg: &[u8]) -> Result<Zeroizing<Vec<u8>>, Error> {
90+
let mut result = Zeroizing::new(vec![0u8; 32]);
91+
let status =
92+
unsafe { bitbox02_sys::securechip_kdf(msg.as_ptr(), msg.len(), result.as_mut_ptr()) };
93+
if status == 0 {
94+
Ok(result)
95+
} else {
96+
Err(Error::from_status(status))
97+
}
98+
}
99+
34100
#[cfg(feature = "app-u2f")]
35101
#[cfg(not(feature = "testing"))]
36102
pub fn u2f_counter_set(counter: u32) -> Result<(), ()> {
@@ -63,3 +129,18 @@ pub fn model() -> Result<Model, ()> {
63129
false => Err(()),
64130
}
65131
}
132+
133+
#[cfg(test)]
134+
mod tests {
135+
use super::*;
136+
137+
use hex_lit::hex;
138+
139+
#[test]
140+
fn test_kdf() {
141+
// Matches the deterministic HMAC result returned by test/hardware-fakes/src/fake_securechip.c.
142+
let result = kdf(b"stub input").unwrap();
143+
let expected = hex!("3d7caa0407f18f6b15a6202843c883f326d614996df67940af210d91aff5b9c8");
144+
assert_eq!(result.as_slice(), expected.as_slice());
145+
}
146+
}

src/securechip/securechip.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <stddef.h>
2323
#include <stdint.h>
2424

25+
// Keep in sync with securechip.rs `SECURECHIP_ERRORS`.
2526
typedef enum {
2627
// Errors common to any securechip implementation
2728
SC_ERR_IFS = -1,

0 commit comments

Comments
 (0)