Skip to content

Commit 8e00687

Browse files
committed
Merge remote-tracking branch 'benma/bip39_12'
2 parents 8ffbb5e + 426c1ea commit 8e00687

File tree

9 files changed

+119
-33
lines changed

9 files changed

+119
-33
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- Attempt to fix flaky SD behavior
55
- Add securechip_model to DeviceInfo: ATECCC608A or ATECC608B.
66
- Added reboot purpose for clearer UX: "Proceed to upgrade?" vs. "Go to startup settings?"
7+
- Allow creation of 128 bit seeds (12 BIP39 recovery words)
78

89
## 9.5.0 [released 2021-03-10]
910
- RestoreFrommnemonic: ported to Rust. Will now return UserAbortError on user abort instead of GenericError.

py/bitbox02/bitbox02/bitbox02/bitbox02.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,18 @@ def set_device_name(self, device_name: str) -> None:
157157
request.device_name.name = device_name
158158
self._msg_query(request, expected_response="success")
159159

160-
def set_password(self) -> bool:
160+
def set_password(self, entropy_size: int = 32) -> bool:
161161
"""
162162
Returns True if the user entered the password correctly (passwords match).
163-
Returns False otherwise.
163+
Returns False otherwise. Entropy size determines the seed size in bytes; must be 16 or 32.
164164
"""
165+
assert entropy_size in (16, 32)
166+
if entropy_size == 16:
167+
self._require_atleast(semver.VersionInfo(9, 6, 0))
168+
165169
# pylint: disable=no-member
166170
request = hww.Request()
167-
request.set_password.entropy = os.urandom(32)
171+
request.set_password.entropy = os.urandom(entropy_size)
168172
try:
169173
self._msg_query(request, expected_response="success")
170174
except Bitbox02Exception as err:

src/keystore.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,14 @@ bool keystore_encrypt_and_store_seed(
257257
return true;
258258
}
259259

260-
bool keystore_create_and_store_seed(const char* password, const uint8_t* host_entropy)
260+
bool keystore_create_and_store_seed(
261+
const char* password,
262+
const uint8_t* host_entropy,
263+
size_t host_entropy_size)
261264
{
265+
if (host_entropy_size != 16 && host_entropy_size != 32) {
266+
return false;
267+
}
262268
if (KEYSTORE_MAX_SEED_LENGTH != RANDOM_NUM_SIZE) {
263269
Abort("keystore create: size mismatch");
264270
}
@@ -267,7 +273,7 @@ bool keystore_create_and_store_seed(const char* password, const uint8_t* host_en
267273
random_32_bytes(seed);
268274

269275
// Mix in Host entropy.
270-
for (size_t i = 0; i < KEYSTORE_MAX_SEED_LENGTH; i++) {
276+
for (size_t i = 0; i < host_entropy_size; i++) {
271277
seed[i] ^= host_entropy[i];
272278
}
273279

@@ -282,10 +288,10 @@ bool keystore_create_and_store_seed(const char* password, const uint8_t* host_en
282288
return false;
283289
}
284290

285-
for (size_t i = 0; i < KEYSTORE_MAX_SEED_LENGTH; i++) {
291+
for (size_t i = 0; i < host_entropy_size; i++) {
286292
seed[i] ^= password_salted_hashed[i];
287293
}
288-
return keystore_encrypt_and_store_seed(seed, KEYSTORE_MAX_SEED_LENGTH, password);
294+
return keystore_encrypt_and_store_seed(seed, host_entropy_size, password);
289295
}
290296

291297
static void _free_string(char** str)

src/keystore.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,16 @@ USE_RESULT bool keystore_encrypt_and_store_seed(
6868
const char* password);
6969

7070
/**
71-
Generates 32 bytes of entropy, mixes it with host_entropy, and stores it encrypted with the
72-
password.
73-
@param[in] host_entropy 32 bytes of entropy to be mixed in.
71+
Generates the seed, mixes it with host_entropy, and stores it encrypted with the
72+
password. The size of the host entropy determines the size of the seed. Can be either 16 or 32
73+
bytes, resulting in 12 or 24 BIP39 recovery words.
74+
@param[in] host_entropy bytes of entropy to be mixed in.
75+
@param[in] host_entropy_size must be 16 or 32.
7476
*/
75-
USE_RESULT bool keystore_create_and_store_seed(const char* password, const uint8_t* host_entropy);
77+
USE_RESULT bool keystore_create_and_store_seed(
78+
const char* password,
79+
const uint8_t* host_entropy,
80+
size_t host_entropy_size);
7681

7782
/** Unlocks the keystore seed or checks the password:
7883
* If the keystore is locked, it decrypts and loads the seed, unlocking the keystore:

src/rust/bitbox02-rust/src/hww/api/set_password.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,23 @@ use super::Error;
1616
use crate::pb;
1717

1818
use crate::workflow::password;
19-
use core::convert::TryInto;
2019
use pb::response::Response;
2120

2221
/// Handles the SetPassword api call. This has the user enter a password twice and creates the
2322
/// seed/keystore. After this call is finished, the keystore is fully unlocked.
2423
///
25-
/// `entropy` must be exactly 32 bytes and provides additional entropy used when
26-
/// creating the seed.
24+
/// `entropy` must be exactly 16 or 32 bytes and provides additional entropy used when creating the
25+
/// seed. If 16 bytes are provided, the seed will also be 16 bytes long, corresponding to 12 BIP39
26+
/// recovery words. If 32 bytes are provided, the seed will also be 32 bytes long, corresponding to
27+
/// 24 BIP39 recovery words.
2728
pub async fn process(
2829
pb::SetPasswordRequest { entropy }: &pb::SetPasswordRequest,
2930
) -> Result<Response, Error> {
30-
let entropy32: [u8; 32] = match entropy.as_slice().try_into() {
31-
Err(_) => return Err(Error::InvalidInput),
32-
Ok(e) => e,
33-
};
31+
if entropy.len() != 16 && entropy.len() != 32 {
32+
return Err(Error::InvalidInput);
33+
}
3434
let password = password::enter_twice().await?;
35-
if !bitbox02::keystore::create_and_store_seed(&password, &entropy32) {
35+
if !bitbox02::keystore::create_and_store_seed(&password, &entropy) {
3636
return Err(Error::Generic);
3737
}
3838
if bitbox02::keystore::unlock(&password).is_err() {

src/rust/bitbox02/src/keystore.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,13 @@ pub fn unlock_bip39(mnemonic_passphrase: &SafeInputString) -> Result<(), Error>
6262
}
6363
}
6464

65-
pub fn create_and_store_seed(password: &SafeInputString, host_entropy: &[u8; 32]) -> bool {
65+
pub fn create_and_store_seed(password: &SafeInputString, host_entropy: &[u8]) -> bool {
6666
unsafe {
67-
bitbox02_sys::keystore_create_and_store_seed(password.as_cstr(), host_entropy.as_ptr())
67+
bitbox02_sys::keystore_create_and_store_seed(
68+
password.as_cstr(),
69+
host_entropy.as_ptr(),
70+
host_entropy.len() as _,
71+
)
6872
}
6973
}
7074

test/device-test/src/test_backup.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ int main(void)
126126

127127
screen_print_debug("Creating initial backup...", 1000);
128128

129-
if (!keystore_create_and_store_seed("device-test", "host-entropy")) {
129+
uint8_t host_entropy[32] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
130+
if (!keystore_create_and_store_seed("device-test", host_entropy, sizeof(host_entropy))) {
130131
Abort("Failed to create keystore");
131132
}
132133
uint8_t remaining_attempts;

test/unit-test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ set(TEST_LIST
221221
hww
222222
"-Wl,--wrap=random_32_bytes,--wrap=workflow_confirm_dismiss"
223223
keystore
224-
"-Wl,--wrap=secp256k1_anti_klepto_sign,--wrap=memory_is_initialized,--wrap=memory_is_seeded,--wrap=memory_get_failed_unlock_attempts,--wrap=memory_reset_failed_unlock_attempts,--wrap=memory_increment_failed_unlock_attempts,--wrap=memory_set_encrypted_seed_and_hmac,--wrap=memory_get_encrypted_seed_and_hmac,--wrap=reset_reset,--wrap=salt_hash_data,--wrap=cipher_aes_hmac_encrypt"
224+
"-Wl,--wrap=secp256k1_anti_klepto_sign,--wrap=memory_is_initialized,--wrap=memory_is_seeded,--wrap=memory_get_failed_unlock_attempts,--wrap=memory_reset_failed_unlock_attempts,--wrap=memory_increment_failed_unlock_attempts,--wrap=memory_set_encrypted_seed_and_hmac,--wrap=memory_get_encrypted_seed_and_hmac,--wrap=reset_reset,--wrap=salt_hash_data,--wrap=cipher_aes_hmac_encrypt,--wrap=random_32_bytes"
225225
keystore_antiklepto
226226
"-Wl,--wrap=keystore_secp256k1_nonce_commit,--wrap=keystore_secp256k1_sign"
227227
keystore_functional

test/unit-test/test_keystore.c

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

20+
#include <cipher/cipher.h>
2021
#include <keystore.h>
2122
#include <memory/bitbox02_smarteeprom.h>
23+
#include <memory/memory.h>
2224
#include <memory/smarteeprom.h>
2325
#include <secp256k1_ecdsa_s2c.h>
2426
#include <secp256k1_recovery.h>
@@ -29,6 +31,8 @@
2931
#include <stdio.h>
3032
#include <string.h>
3133

34+
#define PASSWORD ("password")
35+
3236
static uint8_t _mock_seed[32] = {
3337
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
3438
0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
@@ -46,6 +50,8 @@ static uint8_t _mock_bip39_seed[64] = {
4650
0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
4751
};
4852

53+
const uint8_t _aes_iv[32] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
54+
4955
static const uint32_t _keypath[] = {
5056
44 + BIP32_INITIAL_HARDENED_CHILD,
5157
0 + BIP32_INITIAL_HARDENED_CHILD,
@@ -89,6 +95,13 @@ static uint8_t _kdf_out_3[32] = {
8995
0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
9096
};
9197

98+
// Fixture: hmac.new(_password_salted_hashed_stretch_out, _kdf_out_3,
99+
// hashlib.sha256).hexdigest()
100+
static uint8_t _expected_secret[32] = {
101+
0x39, 0xa7, 0x4f, 0x75, 0xb6, 0x9d, 0x6c, 0x84, 0x5e, 0x18, 0x91, 0x5b, 0xae, 0x29, 0xd1, 0x06,
102+
0x12, 0x12, 0x40, 0x37, 0x7a, 0x79, 0x97, 0x55, 0xd7, 0xcc, 0xe9, 0x26, 0x1e, 0x16, 0x91, 0x71,
103+
};
104+
92105
int __real_secp256k1_anti_klepto_sign(
93106
const secp256k1_context* ctx,
94107
secp256k1_ecdsa_signature* sig,
@@ -124,6 +137,8 @@ bool __wrap_salt_hash_data(
124137
const char* purpose,
125138
uint8_t* hash_out)
126139
{
140+
check_expected(data);
141+
check_expected(data_len);
127142
check_expected(purpose);
128143
memcpy(hash_out, (const void*)mock(), 32);
129144
return true;
@@ -163,6 +178,11 @@ void __wrap_reset_reset(void)
163178
_reset_reset_called = true;
164179
}
165180

181+
void __wrap_random_32_bytes(uint8_t* buf)
182+
{
183+
memcpy(buf, (const void*)mock(), 32);
184+
}
185+
166186
static bool _pubkeys_equal(
167187
const secp256k1_context* ctx,
168188
const secp256k1_pubkey* pubkey1,
@@ -296,6 +316,8 @@ static void _test_keystore_secp256k1_sign(void** state)
296316

297317
static void _expect_stretch(bool valid)
298318
{
319+
expect_memory(__wrap_salt_hash_data, data, PASSWORD, strlen(PASSWORD));
320+
expect_value(__wrap_salt_hash_data, data_len, strlen(PASSWORD));
299321
expect_string(__wrap_salt_hash_data, purpose, "keystore_seed_access_in");
300322
will_return(__wrap_salt_hash_data, _password_salted_hashed_stretch_in);
301323

@@ -314,6 +336,8 @@ static void _expect_stretch(bool valid)
314336
expect_memory(securechip_kdf, msg, _kdf_out_2, 32);
315337
will_return(securechip_kdf, _kdf_out_3);
316338

339+
expect_memory(__wrap_salt_hash_data, data, PASSWORD, strlen(PASSWORD));
340+
expect_value(__wrap_salt_hash_data, data_len, strlen(PASSWORD));
317341
expect_string(__wrap_salt_hash_data, purpose, "keystore_seed_access_out");
318342
will_return(
319343
__wrap_salt_hash_data,
@@ -327,18 +351,11 @@ static void _expect_encrypt_and_store_seed(void)
327351
_expect_stretch(true); // first stretch to encrypt
328352
_expect_stretch(true); // second stretch to verify
329353

330-
// Fixture: hmac.new(_password_salted_hashed_stretch_out, _kdf_out_3,
331-
// hashlib.sha256).hexdigest()
332-
static uint8_t expected_secret[32] = {
333-
0x39, 0xa7, 0x4f, 0x75, 0xb6, 0x9d, 0x6c, 0x84, 0x5e, 0x18, 0x91,
334-
0x5b, 0xae, 0x29, 0xd1, 0x06, 0x12, 0x12, 0x40, 0x37, 0x7a, 0x79,
335-
0x97, 0x55, 0xd7, 0xcc, 0xe9, 0x26, 0x1e, 0x16, 0x91, 0x71,
336-
};
337-
expect_memory(__wrap_cipher_aes_hmac_encrypt, secret, expected_secret, 32);
354+
expect_memory(__wrap_cipher_aes_hmac_encrypt, secret, _expected_secret, 32);
355+
// For the AES IV:
356+
will_return(__wrap_random_32_bytes, _aes_iv);
338357
}
339358

340-
#define PASSWORD ("password")
341-
342359
static void _test_keystore_encrypt_and_store_seed(void** state)
343360
{
344361
_expect_encrypt_and_store_seed();
@@ -532,6 +549,53 @@ static void _test_keystore_encode_xpub(void** state)
532549
"mzoD84tXp15pviBjgS4df");
533550
}
534551

552+
static void _test_keystore_create_and_store_seed(void** state)
553+
{
554+
const uint8_t seed_random[32] =
555+
"\x98\xef\xa1\xb6\x0a\x83\x39\x16\x61\xa2\x4d\xc7\x4a\x80\x4f\x34\x36\xe8\x33\xe0\xaa\xbe"
556+
"\x75\xe9\x71\x1e\x5d\xef\x3a\x8f\x9f\x7c";
557+
const uint8_t host_entropy[32] =
558+
"\x25\x56\x9b\x9a\x11\xf9\xdb\x65\x60\x45\x9e\x8e\x48\xb4\x72\x7a\x4c\x93\x53\x00\x14\x3d"
559+
"\x97\x89\x89\xed\x55\xdb\x1d\x1b\x9c\xbe";
560+
const uint8_t password_salted_hashed[32] =
561+
"\xad\xee\x84\x29\xf5\xb6\x70\xa9\xd7\x34\x17\x1b\x70\x87\xf3\x8f\x86\x6a\x7e\x26\x5f\x9d"
562+
"\x7d\x06\xf0\x0e\x6f\xa4\x17\x54\xac\x77";
563+
// expected_seed = seed_random ^ host_entropy ^ password_salted_hashed
564+
const uint8_t expected_seed[32] =
565+
"\x10\x57\xbe\x05\xee\xcc\x92\xda\xd6\xd3\xc4\x52\x72\xb3\xce\xc1\xfc\x11\x1e\xc6\xe1\x1e"
566+
"\x9f\x66\x08\xfd\x67\x90\x30\xc0\xaf\xb5";
567+
568+
// Invalid seed lengths.
569+
assert_false(keystore_create_and_store_seed(PASSWORD, host_entropy, 8));
570+
assert_false(keystore_create_and_store_seed(PASSWORD, host_entropy, 24));
571+
assert_false(keystore_create_and_store_seed(PASSWORD, host_entropy, 40));
572+
573+
size_t test_sizes[2] = {16, 32};
574+
for (size_t i = 0; i < sizeof(test_sizes) / sizeof(test_sizes[0]); i++) {
575+
size_t seed_len = test_sizes[i];
576+
// Seed random is xored with host entropy and the salted/hashed user password.
577+
will_return(__wrap_random_32_bytes, seed_random);
578+
expect_memory(__wrap_salt_hash_data, data, PASSWORD, strlen(PASSWORD));
579+
expect_value(__wrap_salt_hash_data, data_len, strlen(PASSWORD));
580+
expect_string(__wrap_salt_hash_data, purpose, "keystore_seed_generation");
581+
will_return(__wrap_salt_hash_data, password_salted_hashed);
582+
_expect_encrypt_and_store_seed();
583+
assert_true(keystore_create_and_store_seed(PASSWORD, host_entropy, seed_len));
584+
585+
// Decrypt and check seed.
586+
uint8_t encrypted_seed_and_hmac[96] = {0};
587+
uint8_t len = 0;
588+
assert_true(memory_get_encrypted_seed_and_hmac(encrypted_seed_and_hmac, &len));
589+
size_t decrypted_len = len - 48;
590+
uint8_t out[decrypted_len];
591+
assert_true(cipher_aes_hmac_decrypt(
592+
encrypted_seed_and_hmac, len, out, &decrypted_len, _expected_secret));
593+
assert_true(decrypted_len == seed_len);
594+
595+
assert_memory_equal(expected_seed, out, seed_len);
596+
}
597+
}
598+
535599
int main(void)
536600
{
537601
const struct CMUnitTest tests[] = {
@@ -545,6 +609,7 @@ int main(void)
545609
cmocka_unit_test(_test_keystore_lock),
546610
cmocka_unit_test(_test_keystore_get_bip39_mnemonic),
547611
cmocka_unit_test(_test_keystore_encode_xpub),
612+
cmocka_unit_test(_test_keystore_create_and_store_seed),
548613
};
549614
return cmocka_run_group_tests(tests, NULL, NULL);
550615
}

0 commit comments

Comments
 (0)