1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-26 00:08:10 +00:00

feat(storage): Support PIN and wipe code of unlimited length.

This commit is contained in:
Andrew Kozlik 2020-08-13 18:51:11 +02:00 committed by Andrew Kozlik
parent 1cc719bf49
commit c68e91c12a
3 changed files with 333 additions and 124 deletions

View File

@ -35,33 +35,47 @@
// The APP namespace which is reserved for storage related values.
#define APP_STORAGE 0x00
// Norcow storage key of the PIN entry log and PIN success log.
// Norcow storage keys.
// PIN entry log and PIN success log.
#define PIN_LOGS_KEY ((APP_STORAGE << 8) | 0x01)
// Norcow storage key of the combined salt, EDEK, ESAK and PIN verification code
// entry.
// Combined salt, EDEK, ESAK and PIN verification code entry.
#define EDEK_PVC_KEY ((APP_STORAGE << 8) | 0x02)
// Norcow storage key of the PIN set flag.
// PIN set flag.
#define PIN_NOT_SET_KEY ((APP_STORAGE << 8) | 0x03)
// Norcow storage key of the storage version.
// Authenticated storage version.
// NOTE: This should equal the norcow version unless an upgrade is in progress.
#define VERSION_KEY ((APP_STORAGE << 8) | 0x04)
// Norcow storage key of the storage authentication tag.
// Storage authentication tag.
#define STORAGE_TAG_KEY ((APP_STORAGE << 8) | 0x05)
// Norcow storage key of the wipe code data.
// Wipe code data. Introduced in storage version 2.
#define WIPE_CODE_DATA_KEY ((APP_STORAGE << 8) | 0x06)
// Norcow storage key of the storage upgrade flag.
// Storage upgrade flag. Introduced in storage version 2.
#define STORAGE_UPGRADED_KEY ((APP_STORAGE << 8) | 0x07)
// The PIN value corresponding to an invalid PIN.
#define PIN_INVALID 0
// Unauthenticated storage version. Introduced in storage version 3.
// NOTE: This should always equal the value in VERSION_KEY.
#define UNAUTH_VERSION_KEY ((APP_STORAGE << 8) | 0x08)
// The PIN value corresponding to an empty PIN.
#define PIN_EMPTY 1
const uint8_t *PIN_EMPTY = (const uint8_t *)"";
// The uint32 representation of an empty PIN, used prior to storage version 3.
const uint32_t V0_PIN_EMPTY = 1;
// Maximum number of PIN digits allowed prior to storage version 3.
#define V0_MAX_PIN_LEN 9
// Maximum length of the wipe code.
// Some limit should be imposed on the length, because the wipe code takes up
// storage space proportional to the length, as opposed to the PIN, which takes
// up constant storage space.
#define MAX_WIPE_CODE_LEN 50
// Maximum number of failed unlock attempts.
// NOTE: The PIN counter logic relies on this constant being less than or equal
@ -114,23 +128,20 @@
// The length of the ChaCha20 block in bytes.
#define CHACHA20_BLOCK_SIZE 64
// The length of the wipe code in bytes.
#define WIPE_CODE_SIZE (sizeof(uint32_t))
// The byte length of the salt used in checking the wipe code.
#define WIPE_CODE_SALT_SIZE 8
// The byte length of the tag used in checking the wipe code.
#define WIPE_CODE_TAG_SIZE 8
// The total length of the WIPE_CODE_DATA_KEY entry.
#define WIPE_CODE_DATA_SIZE \
(WIPE_CODE_SIZE + WIPE_CODE_SALT_SIZE + WIPE_CODE_TAG_SIZE)
// The value corresponding to an unconfigured wipe code.
// NOTE: This is intentionally different from PIN_EMPTY so that we don't need
// NOTE: This is intentionally different from an empty PIN so that we don't need
// special handling when both the PIN and wipe code are not set.
#define WIPE_CODE_EMPTY 0
const uint8_t WIPE_CODE_EMPTY[] = {0, 0, 0, 0};
#define WIPE_CODE_EMPTY_LEN 4
// The uint32 representation of an empty wipe code used in storage version 2.
#define V2_WIPE_CODE_EMPTY 0
// The length of the counter tail in words.
#define COUNTER_TAIL_WORDS 2
@ -164,7 +175,10 @@ static void __handle_fault(const char *msg, const char *file, int line,
const char *func);
#define handle_fault(msg) (__handle_fault(msg, __FILE__, __LINE__, __func__))
static uint32_t pin_to_int(const uint8_t *pin, size_t pin_len);
static secbool storage_upgrade(void);
static secbool storage_upgrade_unlocked(const uint8_t *pin, size_t pin_len,
const uint8_t *ext_salt);
static secbool storage_set_encrypted(const uint16_t key, const void *val,
const uint16_t len);
static secbool storage_get_encrypted(const uint16_t key, void *val_dest,
@ -359,29 +373,53 @@ static secbool auth_get(uint16_t key, const void **val, uint16_t *len) {
return sectrue;
}
static secbool set_wipe_code(uint32_t wipe_code) {
if (wipe_code == PIN_EMPTY) {
static secbool set_wipe_code(const uint8_t *wipe_code, size_t wipe_code_len) {
if (wipe_code_len > MAX_WIPE_CODE_LEN ||
wipe_code_len > UINT16_MAX - WIPE_CODE_SALT_SIZE - WIPE_CODE_TAG_SIZE) {
return secfalse;
}
if (wipe_code_len == 0) {
// This is to avoid having to check pin != PIN_EMPTY when checking the wipe
// code.
wipe_code = WIPE_CODE_EMPTY;
wipe_code_len = WIPE_CODE_EMPTY_LEN;
}
// The format of the WIPE_CODE_DATA_KEY entry is:
// wipe code (4 bytes), random salt (16 bytes), authentication tag (16 bytes)
// NOTE: We allocate extra space for the HMAC computation.
uint8_t wipe_code_data[WIPE_CODE_SIZE + WIPE_CODE_SALT_SIZE +
SHA256_DIGEST_LENGTH] = {0};
uint8_t *salt = wipe_code_data + WIPE_CODE_SIZE;
uint8_t *tag = wipe_code_data + WIPE_CODE_SIZE + WIPE_CODE_SALT_SIZE;
memcpy(wipe_code_data, &wipe_code, sizeof(wipe_code));
memzero(&wipe_code, sizeof(wipe_code));
// wipe code (variable), random salt (16 bytes), authentication tag (16 bytes)
// NOTE: We allocate extra space for the HMAC result.
uint8_t salt_and_tag[WIPE_CODE_SALT_SIZE + SHA256_DIGEST_LENGTH] = {0};
uint8_t *salt = salt_and_tag;
uint8_t *tag = salt_and_tag + WIPE_CODE_SALT_SIZE;
random_buffer(salt, WIPE_CODE_SALT_SIZE);
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, wipe_code_data, WIPE_CODE_SIZE, tag);
return norcow_set(WIPE_CODE_DATA_KEY, wipe_code_data, WIPE_CODE_DATA_SIZE);
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, wipe_code, wipe_code_len, tag);
// Preallocate the entry in the flash storage.
if (sectrue !=
norcow_set(WIPE_CODE_DATA_KEY, NULL,
wipe_code_len + WIPE_CODE_SALT_SIZE + WIPE_CODE_TAG_SIZE)) {
return secfalse;
}
// Write wipe code into the preallocated entry.
if (sectrue !=
norcow_update_bytes(WIPE_CODE_DATA_KEY, 0, wipe_code, wipe_code_len)) {
return secfalse;
}
// Write salt and tag into the preallocated entry.
if (sectrue !=
norcow_update_bytes(WIPE_CODE_DATA_KEY, wipe_code_len, salt_and_tag,
WIPE_CODE_SALT_SIZE + WIPE_CODE_TAG_SIZE)) {
return secfalse;
}
return sectrue;
}
static secbool is_not_wipe_code(uint32_t pin) {
uint8_t wipe_code[WIPE_CODE_SIZE] = {0};
static secbool is_not_wipe_code(const uint8_t *pin, size_t pin_len) {
uint8_t salt[WIPE_CODE_SALT_SIZE] = {0};
uint8_t stored_tag[WIPE_CODE_TAG_SIZE] = {0};
uint8_t computed_tag1[SHA256_DIGEST_LENGTH] = {0};
@ -391,20 +429,20 @@ static secbool is_not_wipe_code(uint32_t pin) {
const void *wipe_code_data = NULL;
uint16_t len = 0;
if (sectrue != norcow_get(WIPE_CODE_DATA_KEY, &wipe_code_data, &len) ||
len != WIPE_CODE_DATA_SIZE) {
len <= WIPE_CODE_SALT_SIZE + WIPE_CODE_TAG_SIZE) {
handle_fault("no wipe code");
return secfalse;
}
memcpy(wipe_code, wipe_code_data, sizeof(wipe_code));
memcpy(salt, (uint8_t *)wipe_code_data + WIPE_CODE_SIZE, sizeof(salt));
const uint8_t *wipe_code = (const uint8_t *)wipe_code_data;
size_t wipe_code_len = len - WIPE_CODE_SALT_SIZE - WIPE_CODE_TAG_SIZE;
memcpy(salt, (uint8_t *)wipe_code_data + wipe_code_len, sizeof(salt));
memcpy(stored_tag,
(uint8_t *)wipe_code_data + WIPE_CODE_SIZE + WIPE_CODE_SALT_SIZE,
(uint8_t *)wipe_code_data + wipe_code_len + WIPE_CODE_SALT_SIZE,
sizeof(stored_tag));
// Check integrity in case of flash read manipulation attack.
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, wipe_code, sizeof(wipe_code),
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, wipe_code, wipe_code_len,
computed_tag1);
memzero(wipe_code, sizeof(wipe_code));
if (sectrue != secequal(stored_tag, computed_tag1, sizeof(stored_tag))) {
handle_fault("wipe code tag");
return secfalse;
@ -412,20 +450,17 @@ static secbool is_not_wipe_code(uint32_t pin) {
// Prepare the authentication tag of the entered PIN.
wait_random();
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, (const uint8_t *)&pin, WIPE_CODE_SIZE,
computed_tag1);
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, pin, pin_len, computed_tag1);
// Recompute to check for fault injection attack.
wait_random();
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, (const uint8_t *)&pin, WIPE_CODE_SIZE,
computed_tag2);
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, pin, pin_len, computed_tag2);
memzero(salt, sizeof(salt));
if (sectrue !=
secequal(computed_tag1, computed_tag2, sizeof(computed_tag1))) {
handle_fault("wipe code fault");
return secfalse;
}
memzero(&pin, sizeof(pin));
// Compare wipe code with the entered PIN via the authentication tag.
wait_random();
@ -436,14 +471,10 @@ static secbool is_not_wipe_code(uint32_t pin) {
return sectrue;
}
static void derive_kek(uint32_t pin, const uint8_t *random_salt,
const uint8_t *ext_salt,
static void derive_kek(const uint8_t *pin, size_t pin_len,
const uint8_t *random_salt, const uint8_t *ext_salt,
uint8_t kek[SHA256_DIGEST_LENGTH],
uint8_t keiv[SHA256_DIGEST_LENGTH]) {
#if BYTE_ORDER == BIG_ENDIAN
REVERSE32(pin, pin);
#endif
uint8_t salt[HARDWARE_SALT_SIZE + RANDOM_SALT_SIZE + EXTERNAL_SALT_SIZE] = {
0};
size_t salt_len = 0;
@ -465,8 +496,7 @@ static void derive_kek(uint32_t pin, const uint8_t *random_salt,
}
PBKDF2_HMAC_SHA256_CTX ctx = {0};
pbkdf2_hmac_sha256_Init(&ctx, (const uint8_t *)&pin, sizeof(pin), salt,
salt_len, 1);
pbkdf2_hmac_sha256_Init(&ctx, pin, pin_len, salt, salt_len, 1);
for (int i = 1; i <= 5; i++) {
pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10);
if (ui_callback && ui_message) {
@ -477,8 +507,7 @@ static void derive_kek(uint32_t pin, const uint8_t *random_salt,
}
pbkdf2_hmac_sha256_Final(&ctx, kek);
pbkdf2_hmac_sha256_Init(&ctx, (const uint8_t *)&pin, sizeof(pin), salt,
salt_len, 2);
pbkdf2_hmac_sha256_Init(&ctx, pin, pin_len, salt, salt_len, 2);
for (int i = 6; i <= 10; i++) {
pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10);
if (ui_callback && ui_message) {
@ -491,17 +520,11 @@ static void derive_kek(uint32_t pin, const uint8_t *random_salt,
ui_rem -= DERIVE_SECS;
memzero(&ctx, sizeof(PBKDF2_HMAC_SHA256_CTX));
memzero(&pin, sizeof(pin));
memzero(&salt, sizeof(salt));
}
static secbool set_pin(uint32_t pin, const uint8_t *ext_salt) {
// Fail if the PIN is the same as the wipe code. Ignore during upgrade.
if (norcow_active_version != 0 && sectrue != is_not_wipe_code(pin)) {
memzero(&pin, sizeof(pin));
return secfalse;
}
static secbool set_pin(const uint8_t *pin, size_t pin_len,
const uint8_t *ext_salt) {
// Encrypt the cached keys using the new PIN and set the new PVC.
uint8_t buffer[RANDOM_SALT_SIZE + KEYS_SIZE + POLY1305_TAG_SIZE] = {0};
uint8_t *rand_salt = buffer;
@ -512,7 +535,7 @@ static secbool set_pin(uint32_t pin, const uint8_t *ext_salt) {
uint8_t keiv[SHA256_DIGEST_LENGTH] = {0};
chacha20poly1305_ctx ctx = {0};
random_buffer(rand_salt, RANDOM_SALT_SIZE);
derive_kek(pin, rand_salt, ext_salt, kek, keiv);
derive_kek(pin, pin_len, rand_salt, ext_salt, kek, keiv);
rfc7539_init(&ctx, kek, keiv);
memzero(kek, sizeof(kek));
memzero(keiv, sizeof(keiv));
@ -524,14 +547,13 @@ static secbool set_pin(uint32_t pin, const uint8_t *ext_salt) {
memzero(buffer, sizeof(buffer));
if (ret == sectrue) {
if (pin == PIN_EMPTY) {
if (pin_len == 0) {
ret = norcow_set(PIN_NOT_SET_KEY, &TRUE_BYTE, sizeof(TRUE_BYTE));
} else {
ret = norcow_set(PIN_NOT_SET_KEY, &FALSE_BYTE, sizeof(FALSE_BYTE));
}
}
memzero(&pin, sizeof(pin));
return ret;
}
@ -637,15 +659,18 @@ static void init_wiped_storage(void) {
ensure(auth_init(), "set_storage_auth_tag failed");
ensure(storage_set_encrypted(VERSION_KEY, &version, sizeof(version)),
"set_storage_version failed");
ensure(norcow_set(UNAUTH_VERSION_KEY, &version, sizeof(version)),
"set_unauth_storage_version failed");
ensure(norcow_set(STORAGE_UPGRADED_KEY, &FALSE_WORD, sizeof(FALSE_WORD)),
"set_storage_not_upgraded failed");
ensure(pin_logs_init(0), "init_pin_logs failed");
ensure(set_wipe_code(WIPE_CODE_EMPTY), "set_wipe_code failed");
ensure(set_wipe_code(WIPE_CODE_EMPTY, WIPE_CODE_EMPTY_LEN),
"set_wipe_code failed");
ui_total = DERIVE_SECS;
ui_rem = ui_total;
ui_message = PROCESSING_MSG;
ensure(set_pin(PIN_EMPTY, NULL), "init_pin failed");
ensure(set_pin(PIN_EMPTY, PIN_EMPTY_LEN, NULL), "init_pin failed");
}
void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt,
@ -861,6 +886,18 @@ void storage_lock(void) {
memzero(authentication_sum, sizeof(authentication_sum));
}
// Returns the storage version that was used to lock the storage.
static uint32_t get_lock_version(void) {
const void *val = NULL;
uint16_t len = 0;
if (sectrue != norcow_get(UNAUTH_VERSION_KEY, &val, &len) ||
len != sizeof(uint32_t)) {
handle_fault("no lock version");
}
return *(uint32_t *)val;
}
secbool check_storage_version(void) {
uint32_t version = 0;
uint16_t len = 0;
@ -870,12 +907,19 @@ secbool check_storage_version(void) {
handle_fault("storage version check");
return secfalse;
}
if (version != get_lock_version()) {
handle_fault("storage version check");
return secfalse;
}
const void *storage_upgraded = NULL;
if (sectrue != norcow_get(STORAGE_UPGRADED_KEY, &storage_upgraded, &len) ||
len != sizeof(TRUE_WORD)) {
handle_fault("storage version check");
return secfalse;
}
if (version > norcow_active_version) {
// Attack: Storage was downgraded.
storage_wipe();
@ -892,6 +936,8 @@ secbool check_storage_version(void) {
norcow_set(STORAGE_UPGRADED_KEY, &FALSE_WORD, sizeof(FALSE_WORD));
storage_set_encrypted(VERSION_KEY, &norcow_active_version,
sizeof(norcow_active_version));
norcow_set(UNAUTH_VERSION_KEY, &norcow_active_version,
sizeof(norcow_active_version));
} else {
// Standard operation. The storage was neither upgraded nor downgraded.
if (*(const uint32_t *)storage_upgraded != FALSE_WORD) {
@ -939,23 +985,41 @@ static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) {
memcpy(cached_keys, keys, sizeof(keys));
memzero(keys, sizeof(keys));
memzero(tag, sizeof(tag));
// Check that the authenticated version number matches the norcow version.
// NOTE: This also initializes the authentication_sum by calling
// storage_get_encrypted() which calls auth_get().
return check_storage_version();
return sectrue;
}
static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
if (sectrue != initialized || pin == PIN_INVALID) {
return secfalse;
static void ensure_not_wipe_code(const uint8_t *pin, size_t pin_len) {
if (sectrue != is_not_wipe_code(pin, pin_len)) {
storage_wipe();
error_shutdown("You have entered the", "wipe code. All private",
"data has been erased.", NULL);
}
}
static secbool unlock(const uint8_t *pin, size_t pin_len,
const uint8_t *ext_salt) {
const uint8_t *unlock_pin = pin;
size_t unlock_pin_len = pin_len;
// In case of an upgrade from version 1 or 2, encode the PIN to the old format
// and bump the total time of UI progress to account for the set_pin() call in
// storage_upgrade_unlocked().
uint32_t legacy_pin = 0;
if (get_lock_version() <= 2) {
ui_total += DERIVE_SECS;
ui_rem += DERIVE_SECS;
legacy_pin = pin_to_int(pin, pin_len);
unlock_pin = (const uint8_t *)&legacy_pin;
unlock_pin_len = sizeof(legacy_pin);
}
storage_ensure_not_wipe_code(pin);
// Now we can check for wipe code.
ensure_not_wipe_code(unlock_pin, unlock_pin_len);
// Get the pin failure counter
uint32_t ctr = 0;
if (sectrue != pin_get_fails(&ctr)) {
memzero(&pin, sizeof(pin));
memzero(&legacy_pin, sizeof(legacy_pin));
return secfalse;
}
@ -981,6 +1045,7 @@ static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
progress = ((ui_total - ui_rem) * 10 + i) * 100 / ui_total;
}
if (sectrue == ui_callback(ui_rem, progress, ui_message)) {
memzero(&legacy_pin, sizeof(legacy_pin));
return secfalse;
}
}
@ -995,14 +1060,15 @@ static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
if (sectrue != initialized ||
sectrue != norcow_get(EDEK_PVC_KEY, &rand_salt, &len) ||
len != RANDOM_SALT_SIZE + KEYS_SIZE + PVC_SIZE) {
memzero(&pin, sizeof(pin));
memzero(&legacy_pin, sizeof(legacy_pin));
handle_fault("no EDEK");
return secfalse;
}
uint8_t kek[SHA256_DIGEST_LENGTH] = {0};
uint8_t keiv[SHA256_DIGEST_LENGTH] = {0};
derive_kek(pin, (const uint8_t *)rand_salt, ext_salt, kek, keiv);
memzero(&pin, sizeof(pin));
derive_kek(unlock_pin, unlock_pin_len, (const uint8_t *)rand_salt, ext_salt,
kek, keiv);
memzero(&legacy_pin, sizeof(legacy_pin));
// First, we increase PIN fail counter in storage, even before checking the
// PIN. If the PIN is correct, we reset the counter afterwards. If not, we
@ -1032,16 +1098,31 @@ static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
memzero(kek, sizeof(kek));
memzero(keiv, sizeof(keiv));
// Check for storage upgrades that need to be performed after unlocking and
// check that the authenticated version number matches the unauthenticated
// version and norcow version.
// NOTE: This also initializes the authentication_sum by calling
// storage_get_encrypted() which calls auth_get().
if (sectrue != storage_upgrade_unlocked(pin, pin_len, ext_salt) ||
sectrue != check_storage_version()) {
return secfalse;
}
unlocked = sectrue;
// Finally set the counter to 0 to indicate success.
return pin_fails_reset();
}
secbool storage_unlock(uint32_t pin, const uint8_t *ext_salt) {
secbool storage_unlock(const uint8_t *pin, size_t pin_len,
const uint8_t *ext_salt) {
if (sectrue != initialized || pin == NULL) {
return secfalse;
}
ui_total = DERIVE_SECS;
ui_rem = ui_total;
if (pin == PIN_EMPTY) {
if (pin_len == 0) {
if (ui_message == NULL) {
ui_message = STARTING_MSG;
} else {
@ -1050,7 +1131,7 @@ secbool storage_unlock(uint32_t pin, const uint8_t *ext_salt) {
} else {
ui_message = VERIFYING_PIN_MSG;
}
return unlock(pin, ext_salt);
return unlock(pin, pin_len, ext_salt);
}
/*
@ -1317,34 +1398,43 @@ uint32_t storage_get_pin_rem(void) {
return PIN_MAX_TRIES - ctr;
}
secbool storage_change_pin(uint32_t oldpin, uint32_t newpin,
secbool storage_change_pin(const uint8_t *oldpin, size_t oldpin_len,
const uint8_t *newpin, size_t newpin_len,
const uint8_t *old_ext_salt,
const uint8_t *new_ext_salt) {
if (sectrue != initialized || oldpin == PIN_INVALID ||
newpin == PIN_INVALID) {
if (sectrue != initialized || oldpin == NULL || newpin == NULL) {
return secfalse;
}
ui_total = 2 * DERIVE_SECS;
ui_rem = ui_total;
ui_message = (oldpin != PIN_EMPTY && newpin == PIN_EMPTY) ? VERIFYING_PIN_MSG
: PROCESSING_MSG;
ui_message =
(oldpin_len != 0 && newpin_len == 0) ? VERIFYING_PIN_MSG : PROCESSING_MSG;
if (sectrue != unlock(oldpin, old_ext_salt)) {
if (sectrue != unlock(oldpin, oldpin_len, old_ext_salt)) {
return secfalse;
}
secbool ret = set_pin(newpin, new_ext_salt);
memzero(&oldpin, sizeof(oldpin));
memzero(&newpin, sizeof(newpin));
return ret;
// Fail if the new PIN is the same as the wipe code.
if (sectrue != is_not_wipe_code(newpin, newpin_len)) {
return secfalse;
}
return set_pin(newpin, newpin_len, new_ext_salt);
}
void storage_ensure_not_wipe_code(uint32_t pin) {
if (sectrue != is_not_wipe_code(pin)) {
storage_wipe();
error_shutdown("You have entered the", "wipe code. All private",
"data has been erased.", NULL);
void storage_ensure_not_wipe_code(const uint8_t *pin, size_t pin_len) {
// If we are unlocking the storage during upgrade from version 2 or lower,
// then encode the PIN to the old format.
uint32_t legacy_pin = 0;
if (get_lock_version() <= 2) {
legacy_pin = pin_to_int(pin, pin_len);
pin = (const uint8_t *)&legacy_pin;
pin_len = sizeof(legacy_pin);
}
ensure_not_wipe_code(pin, pin_len);
memzero(&legacy_pin, sizeof(legacy_pin));
}
secbool storage_has_wipe_code(void) {
@ -1352,29 +1442,28 @@ secbool storage_has_wipe_code(void) {
return secfalse;
}
return is_not_wipe_code(WIPE_CODE_EMPTY);
return is_not_wipe_code(WIPE_CODE_EMPTY, WIPE_CODE_EMPTY_LEN);
}
secbool storage_change_wipe_code(uint32_t pin, const uint8_t *ext_salt,
uint32_t wipe_code) {
if (sectrue != initialized || (pin != PIN_EMPTY && pin == wipe_code) ||
pin == PIN_INVALID || wipe_code == PIN_INVALID) {
memzero(&pin, sizeof(pin));
memzero(&wipe_code, sizeof(wipe_code));
secbool storage_change_wipe_code(const uint8_t *pin, size_t pin_len,
const uint8_t *ext_salt,
const uint8_t *wipe_code,
size_t wipe_code_len) {
if (sectrue != initialized || pin == NULL || wipe_code == NULL ||
(pin_len != 0 && pin_len == wipe_code_len &&
memcmp(pin, wipe_code, pin_len) == 0)) {
return secfalse;
}
ui_total = DERIVE_SECS;
ui_rem = ui_total;
ui_message = (pin != PIN_EMPTY && wipe_code == PIN_EMPTY) ? VERIFYING_PIN_MSG
: PROCESSING_MSG;
ui_message =
(pin_len != 0 && wipe_code_len == 0) ? VERIFYING_PIN_MSG : PROCESSING_MSG;
secbool ret = secfalse;
if (sectrue == unlock(pin, ext_salt)) {
ret = set_wipe_code(wipe_code);
if (sectrue == unlock(pin, pin_len, ext_salt)) {
ret = set_wipe_code(wipe_code, wipe_code_len);
}
memzero(&pin, sizeof(pin));
memzero(&wipe_code, sizeof(wipe_code));
return ret;
}
@ -1450,7 +1539,59 @@ static secbool v0_pin_get_fails(uint32_t *ctr) {
return sectrue;
}
// Legacy conversion of PIN to the uint32 scheme that was used prior to storage
// version 3.
static uint32_t pin_to_int(const uint8_t *pin, size_t pin_len) {
if (pin_len > V0_MAX_PIN_LEN) {
return 0;
}
uint32_t val = 1;
size_t i = 0;
for (i = 0; i < pin_len; ++i) {
if (pin[i] < '0' || pin[i] > '9') {
return 0;
}
val = 10 * val + pin[i] - '0';
}
return val;
}
// Legacy conversion of wipe code from the uint32 scheme that was used prior to
// storage version 3.
static char *int_to_wipe_code(uint32_t val) {
static char wipe_code[V0_MAX_PIN_LEN + 1] = {0};
size_t pos = sizeof(wipe_code) - 1;
wipe_code[pos] = '\0';
// Handle the special representation of an empty wipe code.
if (val == V2_WIPE_CODE_EMPTY) {
return &wipe_code[pos];
}
if (val == V0_PIN_EMPTY) {
return NULL;
}
// Convert a non-empty wipe code.
while (val != 1) {
if (pos == 0) {
return NULL;
}
pos--;
wipe_code[pos] = '0' + (val % 10);
val /= 10;
}
return &wipe_code[pos];
}
static secbool storage_upgrade(void) {
// Storage version 0: plaintext norcow
// Storage version 1: encrypted norcow
// Storage version 2: adds 9 digit wipe code
// Storage version 3: adds variable length PIN and wipe code
const uint16_t V0_PIN_KEY = 0x0000;
const uint16_t V0_PIN_FAIL_KEY = 0x0001;
uint16_t key = 0;
@ -1475,10 +1616,12 @@ static secbool storage_upgrade(void) {
ui_total = DERIVE_SECS;
ui_rem = ui_total;
ui_message = PROCESSING_MSG;
if (sectrue == norcow_get(V0_PIN_KEY, &val, &len)) {
set_pin(*(const uint32_t *)val, NULL);
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);
} else {
set_pin(PIN_EMPTY, NULL);
set_pin((const uint8_t *)&V0_PIN_EMPTY, sizeof(V0_PIN_EMPTY), NULL);
ret = norcow_set(PIN_NOT_SET_KEY, &TRUE_BYTE, sizeof(TRUE_BYTE));
}
// Convert PIN failure counter.
@ -1516,17 +1659,75 @@ static secbool storage_upgrade(void) {
}
}
// Set wipe code.
if (norcow_active_version <= 1) {
if (sectrue != set_wipe_code(WIPE_CODE_EMPTY)) {
if (sectrue != set_wipe_code(WIPE_CODE_EMPTY, WIPE_CODE_EMPTY_LEN)) {
return secfalse;
}
}
if (sectrue !=
norcow_set(STORAGE_UPGRADED_KEY, &TRUE_WORD, sizeof(TRUE_WORD))) {
return secfalse;
if (norcow_active_version <= 2) {
// Set UNAUTH_VERSION_KEY, so that it matches VERSION_KEY.
uint32_t version = 1;
// The storage may have gone through an upgrade to version 2 without having
// been unlocked. We can tell by looking at STORAGE_UPGRADED_KEY.
if (sectrue == norcow_get(STORAGE_UPGRADED_KEY, &val, &len) &&
len == sizeof(FALSE_WORD) && *((uint32_t *)val) == FALSE_WORD) {
version = 2;
}
if (sectrue != norcow_set(UNAUTH_VERSION_KEY, &version, sizeof(version))) {
return secfalse;
}
}
norcow_set(STORAGE_UPGRADED_KEY, &TRUE_WORD, sizeof(TRUE_WORD));
norcow_active_version = NORCOW_VERSION;
return norcow_upgrade_finish();
}
static secbool storage_upgrade_unlocked(const uint8_t *pin, size_t pin_len,
const uint8_t *ext_salt) {
uint32_t version = 0;
uint16_t len = 0;
if (sectrue !=
storage_get_encrypted(VERSION_KEY, &version, sizeof(version), &len) ||
len != sizeof(version)) {
handle_fault("storage version check");
return secfalse;
}
secbool ret = sectrue;
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)) {
return secfalse;
}
}
if (version == 2) {
// Upgrade WIPE_CODE_DATA_KEY from the old uint32 scheme to the new
// variable-length scheme.
const void *wipe_code_data = NULL;
if (sectrue != norcow_get(WIPE_CODE_DATA_KEY, &wipe_code_data, &len) ||
len < sizeof(uint32_t)) {
handle_fault("no wipe code");
return secfalse;
}
char *wipe_code = int_to_wipe_code(*(uint32_t *)wipe_code_data);
if (wipe_code == NULL) {
handle_fault("invalid wipe code");
return secfalse;
}
size_t wipe_code_len = strnlen(wipe_code, V0_MAX_PIN_LEN);
ret = set_wipe_code((const uint8_t *)wipe_code, wipe_code_len);
memzero(wipe_code, wipe_code_len);
}
return ret;
}

View File

@ -37,6 +37,10 @@
// Mask for extracting the "real" app_id.
#define FLAGS_APPID 0x3F
// The PIN value corresponding to an empty PIN.
extern const uint8_t *PIN_EMPTY;
#define PIN_EMPTY_LEN 0
typedef secbool (*PIN_UI_WAIT_CALLBACK)(uint32_t wait, uint32_t progress,
const char *message);
@ -45,17 +49,21 @@ void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt,
void storage_wipe(void);
secbool storage_is_unlocked(void);
void storage_lock(void);
secbool storage_unlock(uint32_t pin, const uint8_t *ext_salt);
secbool storage_unlock(const uint8_t *pin, size_t pin_len,
const uint8_t *ext_salt);
secbool storage_has_pin(void);
secbool storage_pin_fails_increase(void);
uint32_t storage_get_pin_rem(void);
secbool storage_change_pin(uint32_t oldpin, uint32_t newpin,
secbool storage_change_pin(const uint8_t *oldpin, size_t oldpin_len,
const uint8_t *newpin, size_t newpin_len,
const uint8_t *old_ext_salt,
const uint8_t *new_ext_salt);
void storage_ensure_not_wipe_code(uint32_t pin);
void storage_ensure_not_wipe_code(const uint8_t *pin, size_t pin_len);
secbool storage_has_wipe_code(void);
secbool storage_change_wipe_code(uint32_t pin, const uint8_t *ext_salt,
uint32_t wipe_code);
secbool storage_change_wipe_code(const uint8_t *pin, size_t pin_len,
const uint8_t *ext_salt,
const uint8_t *wipe_code,
size_t wipe_code_len);
secbool storage_has(const uint16_t key);
secbool storage_get(const uint16_t key, void *val, const uint16_t max_len,
uint16_t *len);

View File

@ -42,6 +42,6 @@
/*
* Current storage version.
*/
#define NORCOW_VERSION ((uint32_t)0x00000002)
#define NORCOW_VERSION ((uint32_t)0x00000003)
#endif