mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-31 02:48:44 +00:00
feat(storage): Add PIN health check.
This commit is contained in:
parent
87f90774a9
commit
486f58d1c5
@ -46,6 +46,10 @@
|
||||
// optiga_pin_verify().
|
||||
#define OPTIGA_PIN_DERIVE_MS 1200
|
||||
|
||||
// The number of milliseconds it takes to execute optiga_pin_verify() for an
|
||||
// invalid PIN.
|
||||
#define OPTIGA_PIN_DERIVE_INVALID_MS 400
|
||||
|
||||
typedef secbool (*OPTIGA_UI_PROGRESS)(uint32_t elapsed_ms);
|
||||
|
||||
int __wur optiga_sign(uint8_t index, const uint8_t *digest, size_t digest_size,
|
||||
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "chacha20poly1305/rfc7539.h"
|
||||
@ -182,7 +183,7 @@ static secbool storage_set_encrypted(const uint16_t key, const void *val,
|
||||
static secbool storage_get_encrypted(const uint16_t key, void *val_dest,
|
||||
const uint16_t max_len, uint16_t *len);
|
||||
static secbool decrypt_dek(const uint8_t *pin, size_t pin_len,
|
||||
const uint8_t *ext_salt);
|
||||
const uint8_t *ext_salt, int pbkdf2_iterations);
|
||||
|
||||
// The number of milliseconds required to compute PBKDF2.
|
||||
static int pbkdf2_ms(int pbkdf2_iterations) {
|
||||
@ -190,10 +191,15 @@ static int pbkdf2_ms(int pbkdf2_iterations) {
|
||||
}
|
||||
|
||||
// The number of milliseconds required to derive the KEK and KEIV.
|
||||
static int pin_derive_ms(int pbkdf2_iterations) {
|
||||
static int pin_derive_ms(bool pin_valid, int pbkdf2_iterations) {
|
||||
(void)pin_valid;
|
||||
int time_ms = pbkdf2_ms(pbkdf2_iterations);
|
||||
#if USE_OPTIGA
|
||||
time_ms += OPTIGA_PIN_DERIVE_MS;
|
||||
if (pin_valid) {
|
||||
time_ms += OPTIGA_PIN_DERIVE_MS;
|
||||
} else {
|
||||
time_ms += OPTIGA_PIN_DERIVE_INVALID_MS;
|
||||
}
|
||||
#endif
|
||||
return time_ms;
|
||||
}
|
||||
@ -531,7 +537,7 @@ static void pbkdf2_update_progressive(PBKDF2_HMAC_SHA256_CTX *ctx,
|
||||
#if !USE_OPTIGA
|
||||
static void derive_kek(const uint8_t *pin, size_t pin_len,
|
||||
const uint8_t *storage_salt, const uint8_t *ext_salt,
|
||||
uint8_t kek[SHA256_DIGEST_LENGTH],
|
||||
int pbkdf2_iterations, uint8_t kek[SHA256_DIGEST_LENGTH],
|
||||
uint8_t keiv[SHA256_DIGEST_LENGTH]) {
|
||||
uint8_t salt[HARDWARE_SALT_SIZE + STORAGE_SALT_SIZE + EXTERNAL_SALT_SIZE] = {
|
||||
0};
|
||||
@ -550,11 +556,11 @@ static void derive_kek(const uint8_t *pin, size_t pin_len,
|
||||
|
||||
PBKDF2_HMAC_SHA256_CTX ctx = {0};
|
||||
pbkdf2_hmac_sha256_Init(&ctx, pin, pin_len, salt, salt_len, 1);
|
||||
pbkdf2_update_progressive(&ctx, PBKDF2_ITER_COUNT / 2);
|
||||
pbkdf2_update_progressive(&ctx, pbkdf2_iterations / 2);
|
||||
pbkdf2_hmac_sha256_Final(&ctx, kek);
|
||||
|
||||
pbkdf2_hmac_sha256_Init(&ctx, pin, pin_len, salt, salt_len, 2);
|
||||
pbkdf2_update_progressive(&ctx, PBKDF2_ITER_COUNT / 2);
|
||||
pbkdf2_update_progressive(&ctx, pbkdf2_iterations / 2);
|
||||
pbkdf2_hmac_sha256_Final(&ctx, keiv);
|
||||
|
||||
memzero(&ctx, sizeof(PBKDF2_HMAC_SHA256_CTX));
|
||||
@ -565,7 +571,7 @@ static void derive_kek(const uint8_t *pin, size_t pin_len,
|
||||
#if USE_OPTIGA
|
||||
static void stretch_pin_optiga(const uint8_t *pin, size_t pin_len,
|
||||
const uint8_t storage_salt[STORAGE_SALT_SIZE],
|
||||
const uint8_t *ext_salt,
|
||||
const uint8_t *ext_salt, int pbkdf2_iterations,
|
||||
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]) {
|
||||
// Combining the PIN with the storage salt aims to ensure that if the
|
||||
// MCU-Optiga communication is compromised, then a user with a low-entropy PIN
|
||||
@ -593,7 +599,7 @@ static void stretch_pin_optiga(const uint8_t *pin, size_t pin_len,
|
||||
PBKDF2_HMAC_SHA256_CTX ctx = {0};
|
||||
pbkdf2_hmac_sha256_Init(&ctx, pin, pin_len, salt, salt_len, 1);
|
||||
memzero(&salt, sizeof(salt));
|
||||
pbkdf2_update_progressive(&ctx, PBKDF2_ITER_COUNT);
|
||||
pbkdf2_update_progressive(&ctx, pbkdf2_iterations);
|
||||
pbkdf2_hmac_sha256_Final(&ctx, stretched_pin);
|
||||
|
||||
memzero(&ctx, sizeof(ctx));
|
||||
@ -622,12 +628,14 @@ static void derive_kek_optiga(
|
||||
static secbool __wur derive_kek_set(const uint8_t *pin, size_t pin_len,
|
||||
const uint8_t *storage_salt,
|
||||
const uint8_t *ext_salt,
|
||||
int pbkdf2_iterations,
|
||||
uint8_t kek[SHA256_DIGEST_LENGTH],
|
||||
uint8_t keiv[SHA256_DIGEST_LENGTH]) {
|
||||
#if USE_OPTIGA
|
||||
uint8_t optiga_secret[OPTIGA_PIN_SECRET_SIZE] = {0};
|
||||
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE] = {0};
|
||||
stretch_pin_optiga(pin, pin_len, storage_salt, ext_salt, stretched_pin);
|
||||
stretch_pin_optiga(pin, pin_len, storage_salt, ext_salt, pbkdf2_iterations,
|
||||
stretched_pin);
|
||||
int ret = optiga_pin_set(ui_progress, stretched_pin, optiga_secret);
|
||||
memzero(stretched_pin, sizeof(stretched_pin));
|
||||
if (ret != OPTIGA_SUCCESS) {
|
||||
@ -637,7 +645,8 @@ static secbool __wur derive_kek_set(const uint8_t *pin, size_t pin_len,
|
||||
derive_kek_optiga(optiga_secret, kek, keiv);
|
||||
memzero(optiga_secret, sizeof(optiga_secret));
|
||||
#else
|
||||
derive_kek(pin, pin_len, storage_salt, ext_salt, kek, keiv);
|
||||
derive_kek(pin, pin_len, storage_salt, ext_salt, pbkdf2_iterations, kek,
|
||||
keiv);
|
||||
#endif
|
||||
return sectrue;
|
||||
}
|
||||
@ -645,12 +654,14 @@ static secbool __wur derive_kek_set(const uint8_t *pin, size_t pin_len,
|
||||
static secbool __wur derive_kek_unlock(const uint8_t *pin, size_t pin_len,
|
||||
const uint8_t *storage_salt,
|
||||
const uint8_t *ext_salt,
|
||||
int pbkdf2_iterations,
|
||||
uint8_t kek[SHA256_DIGEST_LENGTH],
|
||||
uint8_t keiv[SHA256_DIGEST_LENGTH]) {
|
||||
#if USE_OPTIGA
|
||||
uint8_t optiga_secret[OPTIGA_PIN_SECRET_SIZE] = {0};
|
||||
uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE] = {0};
|
||||
stretch_pin_optiga(pin, pin_len, storage_salt, ext_salt, stretched_pin);
|
||||
stretch_pin_optiga(pin, pin_len, storage_salt, ext_salt, pbkdf2_iterations,
|
||||
stretched_pin);
|
||||
int ret = optiga_pin_verify(ui_progress, stretched_pin, optiga_secret);
|
||||
memzero(stretched_pin, sizeof(stretched_pin));
|
||||
if (ret != OPTIGA_SUCCESS) {
|
||||
@ -667,13 +678,14 @@ static secbool __wur derive_kek_unlock(const uint8_t *pin, size_t pin_len,
|
||||
derive_kek_optiga(optiga_secret, kek, keiv);
|
||||
memzero(optiga_secret, sizeof(optiga_secret));
|
||||
#else
|
||||
derive_kek(pin, pin_len, storage_salt, ext_salt, kek, keiv);
|
||||
derive_kek(pin, pin_len, storage_salt, ext_salt, pbkdf2_iterations, kek,
|
||||
keiv);
|
||||
#endif
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
static secbool set_pin(const uint8_t *pin, size_t pin_len,
|
||||
const uint8_t *ext_salt) {
|
||||
const uint8_t *ext_salt, int pbkdf2_iterations) {
|
||||
// Encrypt the cached keys using the new PIN and set the new PVC.
|
||||
uint8_t buffer[STORAGE_SALT_SIZE + KEYS_SIZE + POLY1305_TAG_SIZE] = {0};
|
||||
uint8_t *rand_salt = buffer;
|
||||
@ -685,7 +697,8 @@ static secbool set_pin(const uint8_t *pin, size_t pin_len,
|
||||
chacha20poly1305_ctx ctx = {0};
|
||||
random_buffer(rand_salt, STORAGE_SALT_SIZE);
|
||||
ui_progress(0);
|
||||
ensure(derive_kek_set(pin, pin_len, rand_salt, ext_salt, kek, keiv),
|
||||
ensure(derive_kek_set(pin, pin_len, rand_salt, ext_salt, pbkdf2_iterations,
|
||||
kek, keiv),
|
||||
"derive_kek_set failed");
|
||||
rfc7539_init(&ctx, kek, keiv);
|
||||
memzero(kek, sizeof(kek));
|
||||
@ -826,10 +839,32 @@ static void init_wiped_storage(void) {
|
||||
ensure(set_wipe_code(WIPE_CODE_EMPTY, WIPE_CODE_EMPTY_LEN),
|
||||
"set_wipe_code failed");
|
||||
|
||||
ui_total = PIN_DERIVE_MS;
|
||||
ui_total = pin_derive_ms(true, PBKDF2_ITER_COUNT);
|
||||
ui_rem = ui_total;
|
||||
ui_message = PROCESSING_MSG;
|
||||
ensure(set_pin(PIN_EMPTY, PIN_EMPTY_LEN, NULL), "init_pin failed");
|
||||
|
||||
#if PYOPT
|
||||
// Health check to make sure that an invalid PIN does not unlock and the valid
|
||||
// PIN does. Skip in debug builds to avoid excessive wear of Optiga counters.
|
||||
ui_total += 2 * pin_derive_ms(true, 2) + pin_derive_ms(false, 2);
|
||||
ui_rem = ui_total;
|
||||
const uint8_t PIN_TEST_GOOD[] = {0x01};
|
||||
const uint8_t PIN_TEST_BAD[] = {0x02};
|
||||
if (set_pin(PIN_TEST_GOOD, sizeof(PIN_TEST_GOOD), NULL, 2) != sectrue) {
|
||||
norcow_wipe();
|
||||
ensure(secfalse, "pin_health_check_0 failed");
|
||||
}
|
||||
if (decrypt_dek(PIN_TEST_BAD, sizeof(PIN_TEST_BAD), NULL, 2) != secfalse) {
|
||||
norcow_wipe();
|
||||
ensure(secfalse, "pin_health_check_1 failed");
|
||||
}
|
||||
if (decrypt_dek(PIN_TEST_GOOD, sizeof(PIN_TEST_GOOD), NULL, 2) != sectrue) {
|
||||
norcow_wipe();
|
||||
ensure(secfalse, "pin_health_check_2 failed");
|
||||
}
|
||||
#endif
|
||||
ensure(set_pin(PIN_EMPTY, PIN_EMPTY_LEN, NULL, PBKDF2_ITER_COUNT),
|
||||
"init_pin failed");
|
||||
}
|
||||
|
||||
void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt,
|
||||
@ -1140,7 +1175,7 @@ secbool check_storage_version(void) {
|
||||
}
|
||||
|
||||
static secbool decrypt_dek(const uint8_t *pin, size_t pin_len,
|
||||
const uint8_t *ext_salt) {
|
||||
const uint8_t *ext_salt, int pbkdf2_iterations) {
|
||||
// Read the random salt from EDEK_PVC_KEY and use it to derive the KEK and
|
||||
// KEIV from the PIN.
|
||||
const void *buffer = NULL;
|
||||
@ -1161,8 +1196,8 @@ static secbool decrypt_dek(const uint8_t *pin, size_t pin_len,
|
||||
|
||||
uint8_t kek[SHA256_DIGEST_LENGTH] = {0};
|
||||
uint8_t keiv[SHA256_DIGEST_LENGTH] = {0};
|
||||
if (sectrue !=
|
||||
derive_kek_unlock(pin, pin_len, rand_salt, ext_salt, kek, keiv)) {
|
||||
if (sectrue != derive_kek_unlock(pin, pin_len, rand_salt, ext_salt,
|
||||
pbkdf2_iterations, kek, keiv)) {
|
||||
memzero(kek, sizeof(kek));
|
||||
memzero(keiv, sizeof(keiv));
|
||||
return secfalse;
|
||||
@ -1209,8 +1244,8 @@ static secbool unlock(const uint8_t *pin, size_t pin_len,
|
||||
// storage_upgrade_unlocked().
|
||||
uint32_t legacy_pin = 0;
|
||||
if (get_lock_version() <= 2) {
|
||||
ui_total += pin_derive_ms(PBKDF2_ITER_COUNT);
|
||||
ui_rem += pin_derive_ms(PBKDF2_ITER_COUNT);
|
||||
ui_total += pin_derive_ms(true, PBKDF2_ITER_COUNT);
|
||||
ui_rem += pin_derive_ms(true, PBKDF2_ITER_COUNT);
|
||||
legacy_pin = pin_to_int(pin, pin_len);
|
||||
unlock_pin = (const uint8_t *)&legacy_pin;
|
||||
unlock_pin_len = sizeof(legacy_pin);
|
||||
@ -1264,7 +1299,8 @@ static secbool unlock(const uint8_t *pin, size_t pin_len,
|
||||
}
|
||||
|
||||
// Check whether the entered PIN is correct.
|
||||
if (sectrue != decrypt_dek(unlock_pin, unlock_pin_len, ext_salt)) {
|
||||
if (sectrue !=
|
||||
decrypt_dek(unlock_pin, unlock_pin_len, ext_salt, PBKDF2_ITER_COUNT)) {
|
||||
memzero(&legacy_pin, sizeof(legacy_pin));
|
||||
// Wipe storage if too many failures
|
||||
wait_random();
|
||||
@ -1308,7 +1344,7 @@ secbool storage_unlock(const uint8_t *pin, size_t pin_len,
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
ui_total = pin_derive_ms(PBKDF2_ITER_COUNT);
|
||||
ui_total = pin_derive_ms(true, PBKDF2_ITER_COUNT);
|
||||
ui_rem = ui_total;
|
||||
if (pin_len == 0) {
|
||||
if (ui_message == NULL) {
|
||||
@ -1603,7 +1639,7 @@ secbool storage_change_pin(const uint8_t *oldpin, size_t oldpin_len,
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
ui_total = 2 * pin_derive_ms(PBKDF2_ITER_COUNT);
|
||||
ui_total = 2 * pin_derive_ms(true, PBKDF2_ITER_COUNT);
|
||||
ui_rem = ui_total;
|
||||
ui_message =
|
||||
(oldpin_len != 0 && newpin_len == 0) ? VERIFYING_PIN_MSG : PROCESSING_MSG;
|
||||
@ -1617,7 +1653,7 @@ secbool storage_change_pin(const uint8_t *oldpin, size_t oldpin_len,
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
return set_pin(newpin, newpin_len, new_ext_salt);
|
||||
return set_pin(newpin, newpin_len, new_ext_salt, PBKDF2_ITER_COUNT);
|
||||
}
|
||||
|
||||
void storage_ensure_not_wipe_code(const uint8_t *pin, size_t pin_len) {
|
||||
@ -1652,7 +1688,7 @@ secbool storage_change_wipe_code(const uint8_t *pin, size_t pin_len,
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
ui_total = pin_derive_ms(PBKDF2_ITER_COUNT);
|
||||
ui_total = pin_derive_ms(true, PBKDF2_ITER_COUNT);
|
||||
ui_rem = ui_total;
|
||||
ui_message =
|
||||
(pin_len != 0 && wipe_code_len == 0) ? VERIFYING_PIN_MSG : PROCESSING_MSG;
|
||||
@ -1810,14 +1846,15 @@ static secbool storage_upgrade(void) {
|
||||
}
|
||||
|
||||
// Set EDEK_PVC_KEY and PIN_NOT_SET_KEY.
|
||||
ui_total = pin_derive_ms(PBKDF2_ITER_COUNT);
|
||||
ui_total = pin_derive_ms(true, PBKDF2_ITER_COUNT);
|
||||
ui_rem = ui_total;
|
||||
ui_message = PROCESSING_MSG;
|
||||
secbool found = norcow_get(V0_PIN_KEY, &val, &len);
|
||||
if (sectrue == found && *(const uint32_t *)val != V0_PIN_EMPTY) {
|
||||
set_pin((const uint8_t *)val, len, NULL);
|
||||
set_pin((const uint8_t *)val, len, NULL, PBKDF2_ITER_COUNT);
|
||||
} else {
|
||||
set_pin((const uint8_t *)&V0_PIN_EMPTY, sizeof(V0_PIN_EMPTY), NULL);
|
||||
set_pin((const uint8_t *)&V0_PIN_EMPTY, sizeof(V0_PIN_EMPTY), NULL,
|
||||
PBKDF2_ITER_COUNT);
|
||||
ret = norcow_set(PIN_NOT_SET_KEY, &TRUE_BYTE, sizeof(TRUE_BYTE));
|
||||
}
|
||||
|
||||
@ -1900,7 +1937,7 @@ static secbool storage_upgrade_unlocked(const uint8_t *pin, size_t pin_len,
|
||||
if (version <= 2) {
|
||||
// Upgrade EDEK_PVC_KEY from the old uint32 PIN scheme to the new
|
||||
// variable-length PIN scheme.
|
||||
if (sectrue != set_pin(pin, pin_len, ext_salt)) {
|
||||
if (sectrue != set_pin(pin, pin_len, ext_salt, PBKDF2_ITER_COUNT)) {
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user