Skip to content

Commit ff343f8

Browse files
committed
keystore: port _test_keystore_create_and_store_seed to Rust
1 parent 0c56a0e commit ff343f8

File tree

5 files changed

+84
-121
lines changed

5 files changed

+84
-121
lines changed

src/rust/bitbox02-sys/build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ const ALLOWLIST_FNS: &[&str] = &[
9797
"memory_check_noise_remote_static_pubkey",
9898
"memory_get_attestation_bootloader_hash",
9999
"memory_get_attestation_pubkey_and_certificate",
100+
"memory_get_encrypted_seed_and_hmac",
100101
"memory_get_device_name",
101102
"memory_get_noise_static_private_key",
102103
"memory_get_seed_birthdate",
@@ -125,6 +126,7 @@ const ALLOWLIST_FNS: &[&str] = &[
125126
"progress_create",
126127
"progress_set",
127128
"random_32_bytes_mcu",
129+
"random_32_bytes",
128130
"random_mock_reset",
129131
"reboot_to_bootloader",
130132
"reset_reset",

src/rust/bitbox02/src/keystore.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,68 @@ mod tests {
724724
assert_eq!(decrypted.as_slice(), expected_bip39_seed.as_slice());
725725
}
726726

727+
#[test]
728+
fn test_create_and_store_seed() {
729+
let mock_salt_root =
730+
hex::decode("3333333333333333444444444444444411111111111111112222222222222222")
731+
.unwrap();
732+
733+
let host_entropy =
734+
hex::decode("25569b9a11f9db6560459e8e48b4727a4c935300143d978989ed55db1d1b9cbe25569b9a11f9db6560459e8e48b4727a4c935300143d978989ed55db1d1b9cbe")
735+
.unwrap();
736+
737+
// Invalid seed lengths
738+
for size in [8, 24, 40] {
739+
assert!(matches!(
740+
create_and_store_seed("password", &host_entropy[..size]),
741+
Err(Error::SeedSize)
742+
));
743+
}
744+
745+
// Hack to get the random bytes that will be used.
746+
let seed_random = {
747+
crate::random::mock_reset();
748+
crate::random::random_32_bytes()
749+
};
750+
751+
// Derived from mock_salt_root and "password".
752+
let password_salted_hashed =
753+
hex::decode("e8c70a20d9108fbb9454b1b8e2d7373e78cbaf9de025ab2d4f4d3c7a6711694c")
754+
.unwrap();
755+
756+
// expected_seed = seed_random ^ host_entropy ^ password_salted_hashed
757+
let expected_seed: Vec<u8> = seed_random
758+
.into_iter()
759+
.zip(host_entropy.iter())
760+
.zip(password_salted_hashed)
761+
.map(|((a, &b), c)| a ^ b ^ c)
762+
.collect();
763+
764+
for size in [16, 32] {
765+
mock_memory();
766+
crate::random::mock_reset();
767+
crate::memory::set_salt_root(mock_salt_root.as_slice().try_into().unwrap()).unwrap();
768+
lock();
769+
770+
assert!(create_and_store_seed("password", &host_entropy[..size]).is_ok());
771+
assert!(unlock("password").is_ok());
772+
assert_eq!(copy_seed().unwrap().as_slice(), &expected_seed[..size]);
773+
// Check the seed has been stored encrypted with the expected encryption key.
774+
// Decrypt and check seed.
775+
let cipher = crate::memory::get_encrypted_seed_and_hmac().unwrap();
776+
777+
// Same as Python:
778+
// import hmac, hashlib; hmac.digest(b"unit-test", b"password", hashlib.sha256).hex()
779+
// See also: mock_securechip.c
780+
let expected_encryption_key =
781+
hex::decode("e56de448f5f1d29cdcc0e0099007309afe4d5a3ef2349e99dcc41840ad98409e")
782+
.unwrap();
783+
let decrypted =
784+
bitbox_aes::decrypt_with_hmac(&expected_encryption_key, &cipher).unwrap();
785+
assert_eq!(decrypted.as_slice(), &expected_seed[..size]);
786+
}
787+
}
788+
727789
// This tests that you can create a keystore, unlock it, and then do this again. This is an
728790
// expected workflow for when the wallet setup process is restarted after seeding and unlocking,
729791
// but before creating a backup, in which case a new seed is created.

src/rust/bitbox02/src/memory.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ pub fn get_attestation_pubkey_and_certificate(
9292
}
9393
}
9494

95+
#[cfg(feature = "testing")]
96+
pub fn get_encrypted_seed_and_hmac() -> Result<alloc::vec::Vec<u8>, ()> {
97+
let mut out = vec![0u8; 96];
98+
let mut len = 0u8;
99+
match unsafe { bitbox02_sys::memory_get_encrypted_seed_and_hmac(out.as_mut_ptr(), &mut len) } {
100+
true => {
101+
out.truncate(len as _);
102+
Ok(out)
103+
}
104+
false => Err(()),
105+
}
106+
}
107+
95108
pub fn get_noise_static_private_key() -> Result<zeroize::Zeroizing<[u8; 32]>, ()> {
96109
let mut out = zeroize::Zeroizing::new([0u8; 32]);
97110
match unsafe { bitbox02_sys::memory_get_noise_static_private_key(out.as_mut_ptr()) } {

src/rust/bitbox02/src/random.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ pub fn mcu_32_bytes(out: &mut [u8; 32]) {
2929
}
3030
}
3131

32+
#[cfg(feature = "testing")]
33+
pub fn random_32_bytes() -> alloc::boxed::Box<zeroize::Zeroizing<[u8; 32]>> {
34+
let mut out = alloc::boxed::Box::new(zeroize::Zeroizing::new([0u8; 32]));
35+
unsafe { bitbox02_sys::random_32_bytes(out.as_mut_ptr()) }
36+
out
37+
}
38+
3239
#[cfg(feature = "testing")]
3340
pub fn mock_reset() {
3441
unsafe {

test/unit-test/test_keystore.c

Lines changed: 0 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -17,128 +17,10 @@
1717
#include <stddef.h>
1818
#include <cmocka.h>
1919

20-
#include <cipher/cipher.h>
2120
#include <keystore.h>
22-
#include <memory/bitbox02_smarteeprom.h>
23-
#include <memory/memory.h>
24-
#include <memory/smarteeprom.h>
25-
#include <mock_memory.h>
26-
#include <secp256k1_ecdsa_s2c.h>
27-
#include <secp256k1_recovery.h>
2821
#include <secp256k1_schnorrsig.h>
29-
#include <securechip/securechip.h>
30-
#include <util.h>
3122

3223
#include <stdint.h>
33-
#include <stdio.h>
34-
#include <string.h>
35-
36-
#define PASSWORD ("password")
37-
38-
static uint8_t _salt_root[KEYSTORE_MAX_SEED_LENGTH] = {
39-
0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
40-
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
41-
};
42-
43-
// Same as Python:
44-
// import hmac, hashlib; hmac.digest(b"unit-test", b"password", hashlib.sha256).hex()
45-
// See also: securechip_mock.c
46-
static uint8_t _expected_secret[32] =
47-
"\xe5\x6d\xe4\x48\xf5\xf1\xd2\x9c\xdc\xc0\xe0\x09\x90\x07\x30\x9a\xfe\x4d\x5a\x3e\xf2\x34\x9e"
48-
"\x99\xdc\xc4\x18\x40\xad\x98\x40\x9e";
49-
50-
int __real_secp256k1_anti_exfil_sign(
51-
const secp256k1_context* ctx,
52-
secp256k1_ecdsa_signature* sig,
53-
const unsigned char* msg32,
54-
const unsigned char* seckey,
55-
const unsigned char* host_data32,
56-
int* recid);
57-
58-
static const unsigned char* _sign_expected_msg = NULL;
59-
static const unsigned char* _sign_expected_seckey = NULL;
60-
int __wrap_secp256k1_anti_exfil_sign(
61-
const secp256k1_context* ctx,
62-
secp256k1_ecdsa_signature* sig,
63-
const unsigned char* msg32,
64-
const unsigned char* seckey,
65-
const unsigned char* host_data32,
66-
int* recid)
67-
{
68-
if (_sign_expected_msg != NULL) {
69-
assert_memory_equal(_sign_expected_msg, msg32, 32);
70-
_sign_expected_msg = NULL;
71-
}
72-
if (_sign_expected_seckey != NULL) {
73-
assert_memory_equal(_sign_expected_seckey, seckey, 32);
74-
_sign_expected_seckey = NULL;
75-
}
76-
return __real_secp256k1_anti_exfil_sign(ctx, sig, msg32, seckey, host_data32, recid);
77-
}
78-
79-
static bool _reset_reset_called = false;
80-
void __wrap_reset_reset(void)
81-
{
82-
_reset_reset_called = true;
83-
}
84-
85-
void __wrap_random_32_bytes(uint8_t* buf)
86-
{
87-
memcpy(buf, (const void*)mock(), 32);
88-
}
89-
90-
void _mock_unlocked(const uint8_t* seed, size_t seed_len, const uint8_t* bip39_seed)
91-
{
92-
keystore_mock_unlocked(seed, seed_len, bip39_seed);
93-
}
94-
95-
static void _expect_encrypt_and_store_seed(void)
96-
{
97-
will_return(__wrap_memory_is_initialized, false);
98-
}
99-
100-
static void _test_keystore_create_and_store_seed(void** state)
101-
{
102-
const uint8_t seed_random[32] =
103-
"\x98\xef\xa1\xb6\x0a\x83\x39\x16\x61\xa2\x4d\xc7\x4a\x80\x4f\x34\x36\xe8\x33\xe0\xaa\xbe"
104-
"\x75\xe9\x71\x1e\x5d\xef\x3a\x8f\x9f\x7c";
105-
const uint8_t host_entropy[32] =
106-
"\x25\x56\x9b\x9a\x11\xf9\xdb\x65\x60\x45\x9e\x8e\x48\xb4\x72\x7a\x4c\x93\x53\x00\x14\x3d"
107-
"\x97\x89\x89\xed\x55\xdb\x1d\x1b\x9c\xbe";
108-
// expected_seed = seed_random ^ host_entropy ^ password_salted_hashed
109-
const uint8_t expected_seed[32] =
110-
"\x55\x7e\x30\x0c\xc2\x6a\x6d\xc8\x95\xb3\x62\xf1\xe0\xe3\x0a\x70\x02\xb0\xcf\x7d\x5e\xa6"
111-
"\x49\x4d\xb7\xbe\x34\x4e\x40\x85\x6a\x8e";
112-
113-
// Invalid seed lengths.
114-
assert_int_equal(
115-
keystore_create_and_store_seed(PASSWORD, host_entropy, 8), KEYSTORE_ERR_SEED_SIZE);
116-
assert_int_equal(
117-
keystore_create_and_store_seed(PASSWORD, host_entropy, 24), KEYSTORE_ERR_SEED_SIZE);
118-
assert_int_equal(
119-
keystore_create_and_store_seed(PASSWORD, host_entropy, 40), KEYSTORE_ERR_SEED_SIZE);
120-
121-
size_t test_sizes[2] = {16, 32};
122-
for (size_t i = 0; i < sizeof(test_sizes) / sizeof(test_sizes[0]); i++) {
123-
size_t seed_len = test_sizes[i];
124-
// Seed random is xored with host entropy and the salted/hashed user password.
125-
will_return(__wrap_random_32_bytes, seed_random);
126-
_expect_encrypt_and_store_seed();
127-
assert_int_equal(
128-
keystore_create_and_store_seed(PASSWORD, host_entropy, seed_len), KEYSTORE_OK);
129-
130-
// Decrypt and check seed.
131-
uint8_t encrypted_seed_and_hmac[96] = {0};
132-
uint8_t len = 0;
133-
assert_true(memory_get_encrypted_seed_and_hmac(encrypted_seed_and_hmac, &len));
134-
size_t decrypted_len = len - 48;
135-
uint8_t out[decrypted_len];
136-
assert_true(cipher_aes_hmac_decrypt(
137-
encrypted_seed_and_hmac, len, out, &decrypted_len, _expected_secret));
138-
assert_int_equal(decrypted_len, seed_len);
139-
assert_memory_equal(expected_seed, out, seed_len);
140-
}
141-
}
14224

14325
// This tests that `secp256k1_schnorrsig_sign()` is the correct function to be used for schnorr sigs
14426
// in taproot. It is a separate test because there are test vectors available for this which cannot
@@ -200,10 +82,7 @@ static void _test_secp256k1_schnorr_sign(void** state)
20082

20183
int main(void)
20284
{
203-
mock_memory_set_salt_root(_salt_root);
204-
20585
const struct CMUnitTest tests[] = {
206-
cmocka_unit_test(_test_keystore_create_and_store_seed),
20786
cmocka_unit_test(_test_secp256k1_schnorr_sign),
20887
};
20988
return cmocka_run_group_tests(tests, NULL, NULL);

0 commit comments

Comments
 (0)