Skip to content

Commit d09951f

Browse files
committed
keystore: display unlock error code
Users reported a panic on the device after unlock: "keystore unlock failed". This was a panic because it was never supposed to happen. The expected error (incorrect password) was caught and handled with a special message. Since it is happening in for users anyway sometimes, we don't panic anymore and we pass through granular error codes and display them to the user, so we can get a better idea of what is going wrong. The next commit will also display the securechip error code in case of a securechip failure.
1 parent e7c4c5e commit d09951f

File tree

6 files changed

+56
-31
lines changed

6 files changed

+56
-31
lines changed

src/keystore.c

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ static uint8_t* _get_bip39_seed(void)
108108
return _retained_bip39_seed;
109109
}
110110

111-
static bool _stretch_password(const char* password, uint8_t* kdf_out)
111+
static keystore_error_t _stretch_password(const char* password, uint8_t* kdf_out)
112112
{
113113
uint8_t password_salted_hashed[32] = {0};
114114
UTIL_CLEANUP_32(password_salted_hashed);
@@ -117,7 +117,7 @@ static bool _stretch_password(const char* password, uint8_t* kdf_out)
117117
strlen(password),
118118
"keystore_seed_access_in",
119119
password_salted_hashed)) {
120-
return false;
120+
return KEYSTORE_ERR_SALT;
121121
}
122122

123123
uint8_t kdf_in[32] = {0};
@@ -127,13 +127,13 @@ static bool _stretch_password(const char* password, uint8_t* kdf_out)
127127
// First KDF on SECURECHIP_SLOT_ROLLKEY increments the monotonic
128128
// counter. Call only once!
129129
if (!securechip_kdf(SECURECHIP_SLOT_ROLLKEY, kdf_in, 32, kdf_out)) {
130-
return false;
130+
return KEYSTORE_ERR_SC_KDF;
131131
}
132132
// Second KDF does not use the counter and we call it multiple times.
133133
for (int i = 0; i < KDF_NUM_ITERATIONS; i++) {
134134
memcpy(kdf_in, kdf_out, 32);
135135
if (!securechip_kdf(SECURECHIP_SLOT_KDF, kdf_in, 32, kdf_out)) {
136-
return false;
136+
return KEYSTORE_ERR_SC_KDF;
137137
}
138138
}
139139

@@ -142,17 +142,18 @@ static bool _stretch_password(const char* password, uint8_t* kdf_out)
142142
strlen(password),
143143
"keystore_seed_access_out",
144144
password_salted_hashed)) {
145-
return false;
145+
return KEYSTORE_ERR_SALT;
146146
}
147147
if (wally_hmac_sha256(
148148
password_salted_hashed, sizeof(password_salted_hashed), kdf_out, 32, kdf_out, 32) !=
149149
WALLY_OK) {
150-
return false;
150+
return KEYSTORE_ERR_HASH;
151151
}
152152

153-
return true;
153+
return KEYSTORE_OK;
154154
}
155-
static bool _get_and_decrypt_seed(
155+
156+
static keystore_error_t _get_and_decrypt_seed(
156157
const char* password,
157158
uint8_t* decrypted_seed_out,
158159
size_t* decrypted_seed_len_out,
@@ -162,12 +163,13 @@ static bool _get_and_decrypt_seed(
162163
UTIL_CLEANUP_32(encrypted_seed_and_hmac);
163164
uint8_t encrypted_len;
164165
if (!memory_get_encrypted_seed_and_hmac(encrypted_seed_and_hmac, &encrypted_len)) {
165-
return false;
166+
return KEYSTORE_ERR_MEMORY;
166167
}
167168
uint8_t secret[32];
168169
UTIL_CLEANUP_32(secret);
169-
if (!_stretch_password(password, secret)) {
170-
return false;
170+
keystore_error_t result = _stretch_password(password, secret);
171+
if (result != KEYSTORE_OK) {
172+
return result;
171173
}
172174
if (encrypted_len < 49) {
173175
Abort("_get_and_decrypt_seed: underflow / zero size");
@@ -179,13 +181,13 @@ static bool _get_and_decrypt_seed(
179181
if (*password_correct_out) {
180182
if (!_validate_seed_length(decrypted_len)) {
181183
util_zero(decrypted, sizeof(decrypted));
182-
return false;
184+
return KEYSTORE_ERR_SEED_SIZE;
183185
}
184186
*decrypted_seed_len_out = decrypted_len;
185187
memcpy(decrypted_seed_out, decrypted, decrypted_len);
186188
}
187189
util_zero(decrypted, sizeof(decrypted));
188-
return true;
190+
return KEYSTORE_OK;
189191
}
190192

191193
static bool _verify_seed(
@@ -197,7 +199,8 @@ static bool _verify_seed(
197199
size_t seed_len;
198200
UTIL_CLEANUP_32(decrypted_seed);
199201
bool password_correct = false;
200-
if (!_get_and_decrypt_seed(password, decrypted_seed, &seed_len, &password_correct)) {
202+
if (_get_and_decrypt_seed(password, decrypted_seed, &seed_len, &password_correct) !=
203+
KEYSTORE_OK) {
201204
return false;
202205
}
203206
if (!password_correct) {
@@ -232,7 +235,7 @@ bool keystore_encrypt_and_store_seed(
232235
}
233236
uint8_t secret[32] = {0};
234237
UTIL_CLEANUP_32(secret);
235-
if (!_stretch_password(password, secret)) {
238+
if (_stretch_password(password, secret) != KEYSTORE_OK) {
236239
return false;
237240
}
238241

@@ -302,7 +305,7 @@ static void _free_string(char** str)
302305
keystore_error_t keystore_unlock(const char* password, uint8_t* remaining_attempts_out)
303306
{
304307
if (!memory_is_seeded()) {
305-
return KEYSTORE_ERR_GENERIC;
308+
return KEYSTORE_ERR_UNSEEDED;
306309
}
307310
uint8_t failed_attempts = bitbox02_smarteeprom_get_unlock_attempts();
308311
if (failed_attempts >= MAX_UNLOCK_ATTEMPTS) {
@@ -320,8 +323,9 @@ keystore_error_t keystore_unlock(const char* password, uint8_t* remaining_attemp
320323
UTIL_CLEANUP_32(seed);
321324
size_t seed_len;
322325
bool password_correct = false;
323-
if (!_get_and_decrypt_seed(password, seed, &seed_len, &password_correct)) {
324-
return KEYSTORE_ERR_GENERIC;
326+
keystore_error_t result = _get_and_decrypt_seed(password, seed, &seed_len, &password_correct);
327+
if (result != KEYSTORE_OK) {
328+
return result;
325329
}
326330
if (password_correct) {
327331
if (_is_unlocked_device) {

src/keystore.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ typedef enum {
3636
KEYSTORE_OK,
3737
KEYSTORE_ERR_INCORRECT_PASSWORD,
3838
KEYSTORE_ERR_MAX_ATTEMPTS_EXCEEDED,
39-
KEYSTORE_ERR_GENERIC,
39+
KEYSTORE_ERR_UNSEEDED,
40+
KEYSTORE_ERR_MEMORY,
41+
KEYSTORE_ERR_SC_KDF,
42+
KEYSTORE_ERR_SEED_SIZE,
43+
KEYSTORE_ERR_SALT,
44+
KEYSTORE_ERR_HASH,
4045
} keystore_error_t;
4146

4247
#ifdef TESTING
@@ -91,10 +96,7 @@ USE_RESULT bool keystore_create_and_store_seed(
9196
* If zero, the keystore is locked until the device is reset.
9297
* @return
9398
* - KEYSTORE_OK if they keystore was successfully unlocked
94-
* - KEYSTORE_ERR_INCORRECT_PASSWORD if the password was wrong
95-
* - KEYSTORE_ERR_MAX_ATTEMPTS_EXCEEDED if there were too many unsuccessful attempts. The device is
96-
* reset in this case.
97-
* - KEYSTORE_ERR_GENERIC if there was a fatal memory error.
99+
* - KEYSTORE_ERR_* if unsuccessful.
98100
* Only call this if memory_is_seeded() returns true.
99101
*/
100102
USE_RESULT keystore_error_t keystore_unlock(const char* password, uint8_t* remaining_attempts_out);

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
use crate::pb;
1616

17+
use crate::workflow::unlock::UnlockError;
18+
1719
#[allow(dead_code)]
1820
#[derive(Debug, PartialEq)]
1921
pub enum Error {
@@ -67,11 +69,11 @@ impl core::convert::From<crate::workflow::verify_message::Error> for Error {
6769
}
6870
}
6971

70-
impl core::convert::From<crate::workflow::unlock::UnlockError> for Error {
71-
fn from(error: crate::workflow::unlock::UnlockError) -> Self {
72+
impl core::convert::From<UnlockError> for Error {
73+
fn from(error: UnlockError) -> Self {
7274
match error {
73-
crate::workflow::unlock::UnlockError::UserAbort => Error::UserAbort,
74-
crate::workflow::unlock::UnlockError::IncorrectPassword => Error::Generic,
75+
UnlockError::UserAbort => Error::UserAbort,
76+
UnlockError::IncorrectPassword | UnlockError::Generic => Error::Generic,
7577
}
7678
}
7779
}

src/rust/bitbox02-rust/src/workflow/unlock.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ async fn confirm_mnemonic_passphrase(passphrase: &str) -> Result<(), confirm::Us
5353
pub enum UnlockError {
5454
UserAbort,
5555
IncorrectPassword,
56+
Generic,
5657
}
5758

5859
impl core::convert::From<super::cancel::Error> for UnlockError {
@@ -84,7 +85,11 @@ pub async fn unlock_keystore(
8485
status(&msg, false).await;
8586
Err(UnlockError::IncorrectPassword)
8687
}
87-
_ => panic!("keystore unlock failed"),
88+
Err(err) => {
89+
let msg = format!("keystore unlock failed\n{:?}", err);
90+
status(&msg, false).await;
91+
Err(UnlockError::Generic)
92+
}
8893
}
8994
}
9095

src/rust/bitbox02/src/keystore.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ pub fn is_locked() -> bool {
3535
pub enum Error {
3636
CannotUnlockBIP39,
3737
IncorrectPassword { remaining_attempts: u8 },
38+
MaxAttemptsExceeded,
39+
Unseeded,
40+
Memory,
41+
ScKdf,
42+
Salt,
43+
Hash,
44+
SeedSize,
3845
Unknown,
3946
}
4047

@@ -45,8 +52,13 @@ pub fn unlock(password: &SafeInputString) -> Result<(), Error> {
4552
keystore_error_t::KEYSTORE_ERR_INCORRECT_PASSWORD => {
4653
Err(Error::IncorrectPassword { remaining_attempts })
4754
}
48-
keystore_error_t::KEYSTORE_ERR_MAX_ATTEMPTS_EXCEEDED => Err(Error::Unknown),
49-
keystore_error_t::KEYSTORE_ERR_GENERIC => Err(Error::Unknown),
55+
keystore_error_t::KEYSTORE_ERR_MAX_ATTEMPTS_EXCEEDED => Err(Error::MaxAttemptsExceeded),
56+
keystore_error_t::KEYSTORE_ERR_UNSEEDED => Err(Error::Unseeded),
57+
keystore_error_t::KEYSTORE_ERR_MEMORY => Err(Error::Memory),
58+
keystore_error_t::KEYSTORE_ERR_SEED_SIZE => Err(Error::SeedSize),
59+
keystore_error_t::KEYSTORE_ERR_SC_KDF => Err(Error::ScKdf),
60+
keystore_error_t::KEYSTORE_ERR_SALT => Err(Error::Salt),
61+
keystore_error_t::KEYSTORE_ERR_HASH => Err(Error::Hash),
5062
}
5163
}
5264

test/unit-test/test_keystore.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ static void _test_keystore_unlock(void** state)
417417
uint8_t remaining_attempts;
418418

419419
will_return(__wrap_memory_is_seeded, false);
420-
assert_int_equal(KEYSTORE_ERR_GENERIC, keystore_unlock(PASSWORD, &remaining_attempts));
420+
assert_int_equal(KEYSTORE_ERR_UNSEEDED, keystore_unlock(PASSWORD, &remaining_attempts));
421421
_expect_encrypt_and_store_seed();
422422
assert_true(keystore_encrypt_and_store_seed(_mock_seed, 32, PASSWORD));
423423
_expect_seeded(false);

0 commit comments

Comments
 (0)