3636
3737// Change this ONLY via keystore_unlock() or keystore_lock()
3838static bool _is_unlocked_device = false;
39- // Must be defined if is_unlocked is true. Length of the seed store in `_retained_seed`. See also:
40- // `_validate_seed_length()`.
41- static size_t _seed_length = 0 ;
39+ // Stores a random key after unlock which, after stretching, is used to encrypt the retained seed.
40+ static uint8_t _unstretched_retained_seed_encryption_key [32 ] = {0 };
4241// Must be defined if is_unlocked is true. ONLY ACCESS THIS WITH keystore_copy_seed().
43- static uint8_t _retained_seed [KEYSTORE_MAX_SEED_LENGTH ] = {0 };
42+ // Stores the encrypted seed after unlock.
43+ static uint8_t _retained_seed_encrypted [KEYSTORE_MAX_SEED_LENGTH + 64 ] = {0 };
44+ static size_t _retained_seed_encrypted_len = 0 ;
4445
4546// Change this ONLY via keystore_unlock_bip39().
4647static bool _is_unlocked_bip39 = false;
48+ // Stores a random keyy after bip39-unlock which, after stretching, is used to encrypt the retained
49+ // bip39 seed.
50+ static uint8_t _unstretched_retained_bip39_seed_encryption_key [32 ] = {0 };
4751// Must be defined if _is_unlocked is true. ONLY ACCESS THIS WITH _copy_bip39_seed().
48- static uint8_t _retained_bip39_seed [64 ] = {0 };
49-
50- #ifdef TESTING
51- void keystore_mock_unlocked (const uint8_t * seed , size_t seed_len , const uint8_t * bip39_seed )
52- {
53- _is_unlocked_device = seed != NULL ;
54- if (seed != NULL ) {
55- _seed_length = seed_len ;
56- memcpy (_retained_seed , seed , seed_len );
57- }
58- _is_unlocked_bip39 = bip39_seed != NULL ;
59- if (bip39_seed != NULL ) {
60- memcpy (_retained_bip39_seed , bip39_seed , sizeof (_retained_bip39_seed ));
61- }
62- }
63- #endif
52+ // Stores the encrypted BIP-39 seed after bip39-unlock.
53+ static uint8_t _retained_bip39_seed_encrypted [64 + 64 ] = {0 };
54+ static size_t _retained_bip39_seed_encrypted_len = 0 ;
6455
6556/**
6657 * We allow seeds of 16, 24 or 32 bytes.
@@ -70,13 +61,56 @@ static bool _validate_seed_length(size_t seed_len)
7061 return seed_len == 16 || seed_len == 24 || seed_len == 32 ;
7162}
7263
64+ USE_RESULT static keystore_error_t _stretch_retained_seed_encryption_key (
65+ const uint8_t * encryption_key ,
66+ const char * purpose_in ,
67+ const char * purpose_out ,
68+ uint8_t * out )
69+ {
70+ uint8_t salted_hashed [32 ] = {0 };
71+ UTIL_CLEANUP_32 (salted_hashed );
72+ if (!salt_hash_data (encryption_key , 32 , purpose_in , salted_hashed )) {
73+ return KEYSTORE_ERR_SALT ;
74+ }
75+ if (securechip_kdf (SECURECHIP_SLOT_KDF , salted_hashed , 32 , out )) {
76+ return KEYSTORE_ERR_SECURECHIP ;
77+ }
78+ if (!salt_hash_data (encryption_key , 32 , purpose_out , salted_hashed )) {
79+ return KEYSTORE_ERR_SALT ;
80+ }
81+ if (wally_hmac_sha256 (salted_hashed , sizeof (salted_hashed ), out , 32 , out , 32 ) != WALLY_OK ) {
82+ return KEYSTORE_ERR_HASH ;
83+ }
84+ return KEYSTORE_OK ;
85+ }
86+
7387bool keystore_copy_seed (uint8_t * seed_out , size_t * length_out )
7488{
7589 if (!_is_unlocked_device ) {
7690 return false;
7791 }
78- memcpy (seed_out , _retained_seed , _seed_length );
79- * length_out = _seed_length ;
92+
93+ uint8_t retained_seed_encryption_key [32 ] = {0 };
94+ UTIL_CLEANUP_32 (retained_seed_encryption_key );
95+ if (_stretch_retained_seed_encryption_key (
96+ _unstretched_retained_seed_encryption_key ,
97+ "keystore_retained_seed_access_in" ,
98+ "keystore_retained_seed_access_out" ,
99+ retained_seed_encryption_key ) != KEYSTORE_OK ) {
100+ return false;
101+ }
102+ size_t len = _retained_seed_encrypted_len - 48 ;
103+ bool password_correct = cipher_aes_hmac_decrypt (
104+ _retained_seed_encrypted ,
105+ _retained_seed_encrypted_len ,
106+ seed_out ,
107+ & len ,
108+ retained_seed_encryption_key );
109+ if (!password_correct ) {
110+ // Should never happen.
111+ return false;
112+ }
113+ * length_out = len ;
80114 return true;
81115}
82116
@@ -92,13 +126,37 @@ static bool _copy_bip39_seed(uint8_t* bip39_seed_out)
92126 if (!_is_unlocked_bip39 ) {
93127 return false;
94128 }
129+
130+ uint8_t retained_bip39_seed_encryption_key [32 ] = {0 };
131+ UTIL_CLEANUP_32 (retained_bip39_seed_encryption_key );
132+ if (_stretch_retained_seed_encryption_key (
133+ _unstretched_retained_bip39_seed_encryption_key ,
134+ "keystore_retained_bip39_seed_access_in" ,
135+ "keystore_retained_bip39_seed_access_out" ,
136+ retained_bip39_seed_encryption_key ) != KEYSTORE_OK ) {
137+ return false;
138+ }
139+ size_t len = _retained_bip39_seed_encrypted_len - 48 ;
140+ bool password_correct = cipher_aes_hmac_decrypt (
141+ _retained_bip39_seed_encrypted ,
142+ _retained_bip39_seed_encrypted_len ,
143+ bip39_seed_out ,
144+ & len ,
145+ retained_bip39_seed_encryption_key );
146+ if (!password_correct ) {
147+ // Should never happen.
148+ return false;
149+ }
150+ if (len != 64 ) {
151+ // Should never happen.
152+ return false;
153+ }
95154 // sanity check
96155 uint8_t zero [64 ] = {0 };
97156 util_zero (zero , 64 );
98- if (MEMEQ (_retained_bip39_seed , zero , sizeof ( _retained_bip39_seed ) )) {
157+ if (MEMEQ (bip39_seed_out , zero , 64 )) {
99158 return false;
100159 }
101- memcpy (bip39_seed_out , _retained_bip39_seed , sizeof (_retained_bip39_seed ));
102160 return true;
103161}
104162
@@ -319,6 +377,67 @@ static void _free_string(char** str)
319377 wally_free_string (* str );
320378}
321379
380+ USE_RESULT static keystore_error_t _retain_seed (const uint8_t * seed , size_t seed_len )
381+ {
382+ random_32_bytes (_unstretched_retained_seed_encryption_key );
383+ uint8_t retained_seed_encryption_key [32 ] = {0 };
384+ UTIL_CLEANUP_32 (retained_seed_encryption_key );
385+ keystore_error_t result = _stretch_retained_seed_encryption_key (
386+ _unstretched_retained_seed_encryption_key ,
387+ "keystore_retained_seed_access_in" ,
388+ "keystore_retained_seed_access_out" ,
389+ retained_seed_encryption_key );
390+ if (result != KEYSTORE_OK ) {
391+ return result ;
392+ }
393+ size_t len = seed_len + 64 ;
394+ if (!cipher_aes_hmac_encrypt (
395+ seed , seed_len , _retained_seed_encrypted , & len , retained_seed_encryption_key )) {
396+ return KEYSTORE_ERR_ENCRYPT ;
397+ }
398+ _retained_seed_encrypted_len = len ;
399+ return KEYSTORE_OK ;
400+ }
401+
402+ USE_RESULT static bool _retain_bip39_seed (const uint8_t * bip39_seed )
403+ {
404+ random_32_bytes (_unstretched_retained_bip39_seed_encryption_key );
405+ uint8_t retained_bip39_seed_encryption_key [32 ] = {0 };
406+ UTIL_CLEANUP_32 (retained_bip39_seed_encryption_key );
407+ if (_stretch_retained_seed_encryption_key (
408+ _unstretched_retained_bip39_seed_encryption_key ,
409+ "keystore_retained_bip39_seed_access_in" ,
410+ "keystore_retained_bip39_seed_access_out" ,
411+ retained_bip39_seed_encryption_key ) != KEYSTORE_OK ) {
412+ return false;
413+ }
414+ size_t len = sizeof (_retained_bip39_seed_encrypted );
415+ if (!cipher_aes_hmac_encrypt (
416+ bip39_seed ,
417+ 64 ,
418+ _retained_bip39_seed_encrypted ,
419+ & len ,
420+ retained_bip39_seed_encryption_key )) {
421+ return false;
422+ }
423+ _retained_bip39_seed_encrypted_len = len ;
424+ return true;
425+ }
426+
427+ static void _delete_retained_seeds (void )
428+ {
429+ util_zero (
430+ _unstretched_retained_seed_encryption_key ,
431+ sizeof (_unstretched_retained_seed_encryption_key ));
432+ util_zero (_retained_seed_encrypted , sizeof (_retained_seed_encrypted ));
433+ _retained_seed_encrypted_len = 0 ;
434+ util_zero (
435+ _unstretched_retained_bip39_seed_encryption_key ,
436+ sizeof (_unstretched_retained_seed_encryption_key ));
437+ util_zero (_retained_bip39_seed_encrypted , sizeof (_retained_bip39_seed_encrypted ));
438+ _retained_bip39_seed_encrypted_len = 0 ;
439+ }
440+
322441keystore_error_t keystore_unlock (
323442 const char * password ,
324443 uint8_t * remaining_attempts_out ,
@@ -350,12 +469,19 @@ keystore_error_t keystore_unlock(
350469 if (result == KEYSTORE_OK ) {
351470 if (_is_unlocked_device ) {
352471 // Already unlocked. Fail if the seed changed under our feet (should never happen).
353- if (seed_len != _seed_length || !MEMEQ (_retained_seed , seed , _seed_length )) {
472+ uint8_t current_seed [KEYSTORE_MAX_SEED_LENGTH ] = {0 };
473+ size_t current_seed_len = 0 ;
474+ if (!keystore_copy_seed (current_seed , & current_seed_len )) {
475+ return KEYSTORE_ERR_DECRYPT ;
476+ }
477+ if (seed_len != current_seed_len || !MEMEQ (current_seed , seed , current_seed_len )) {
354478 Abort ("Seed has suddenly changed. This should never happen." );
355479 }
356480 } else {
357- memcpy (_retained_seed , seed , seed_len );
358- _seed_length = seed_len ;
481+ keystore_error_t retain_seed_result = _retain_seed (seed , seed_len );
482+ if (retain_seed_result != KEYSTORE_OK ) {
483+ return retain_seed_result ;
484+ }
359485 _is_unlocked_device = true;
360486 }
361487 bitbox02_smarteeprom_reset_unlock_attempts ();
@@ -396,7 +522,9 @@ bool keystore_unlock_bip39(const char* mnemonic_passphrase)
396522 mnemonic , mnemonic_passphrase , bip39_seed , sizeof (bip39_seed ), NULL ) != WALLY_OK ) {
397523 return false;
398524 }
399- memcpy (_retained_bip39_seed , bip39_seed , sizeof (bip39_seed ));
525+ if (!_retain_bip39_seed (bip39_seed )) {
526+ return false;
527+ }
400528 _is_unlocked_bip39 = true;
401529 return true;
402530}
@@ -405,9 +533,7 @@ void keystore_lock(void)
405533{
406534 _is_unlocked_device = false;
407535 _is_unlocked_bip39 = false;
408- _seed_length = 0 ;
409- util_zero (_retained_seed , sizeof (_retained_seed ));
410- util_zero (_retained_bip39_seed , sizeof (_retained_bip39_seed ));
536+ _delete_retained_seeds ();
411537}
412538
413539bool keystore_is_locked (void )
@@ -789,3 +915,33 @@ bool keystore_secp256k1_schnorr_bip86_sign(
789915 }
790916 return secp256k1_schnorrsig_verify (ctx , sig64_out , msg32 , 32 , & pubkey ) == 1 ;
791917}
918+
919+ #ifdef TESTING
920+ void keystore_mock_unlocked (const uint8_t * seed , size_t seed_len , const uint8_t * bip39_seed )
921+ {
922+ _is_unlocked_device = seed != NULL ;
923+ if (seed != NULL ) {
924+ if (_retain_seed (seed , seed_len ) != KEYSTORE_OK ) {
925+ Abort ("couldn't retain seed" );
926+ }
927+ }
928+ _is_unlocked_bip39 = bip39_seed != NULL ;
929+ if (bip39_seed != NULL ) {
930+ if (!_retain_bip39_seed (bip39_seed )) {
931+ Abort ("couldn't retain bip39 seed" );
932+ }
933+ }
934+ }
935+
936+ const uint8_t * keystore_test_get_retained_seed_encrypted (size_t * len_out )
937+ {
938+ * len_out = _retained_seed_encrypted_len ;
939+ return _retained_seed_encrypted ;
940+ }
941+
942+ const uint8_t * keystore_test_get_retained_bip39_seed_encrypted (size_t * len_out )
943+ {
944+ * len_out = _retained_bip39_seed_encrypted_len ;
945+ return _retained_bip39_seed_encrypted ;
946+ }
947+ #endif
0 commit comments