mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-16 01:22:02 +00:00
storage: Implement storage_change_wipe_code().
This commit is contained in:
parent
83fab3c220
commit
a9b98ab966
@ -50,6 +50,9 @@
|
||||
// Norcow storage key of the storage authentication tag.
|
||||
#define STORAGE_TAG_KEY ((APP_STORAGE << 8) | 0x05)
|
||||
|
||||
// Norcow storage key of the wipe code data.
|
||||
#define WIPE_CODE_DATA_KEY ((APP_STORAGE << 8) | 0x06)
|
||||
|
||||
// The PIN value corresponding to an empty PIN.
|
||||
#define PIN_EMPTY 1
|
||||
|
||||
@ -104,6 +107,24 @@
|
||||
// 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
|
||||
// special handling when both the PIN and wipe code are not set.
|
||||
#define WIPE_CODE_EMPTY 0
|
||||
|
||||
// The length of the counter tail in words.
|
||||
#define COUNTER_TAIL_WORDS 2
|
||||
|
||||
@ -323,6 +344,83 @@ 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) {
|
||||
// This is to avoid having to check pin != PIN_EMPTY when checking the wipe
|
||||
// code.
|
||||
wipe_code = WIPE_CODE_EMPTY;
|
||||
}
|
||||
|
||||
// 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));
|
||||
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);
|
||||
}
|
||||
|
||||
static secbool is_not_wipe_code(uint32_t pin) {
|
||||
uint8_t wipe_code[WIPE_CODE_SIZE] = {0};
|
||||
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};
|
||||
uint8_t computed_tag2[SHA256_DIGEST_LENGTH] = {0};
|
||||
|
||||
// Read the wipe code data from the storage.
|
||||
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) {
|
||||
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));
|
||||
memcpy(stored_tag,
|
||||
(uint8_t *)wipe_code_data + WIPE_CODE_SIZE + 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),
|
||||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
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();
|
||||
if (secfalse != secequal(stored_tag, computed_tag1, sizeof(stored_tag))) {
|
||||
return secfalse;
|
||||
}
|
||||
memzero(stored_tag, sizeof(stored_tag));
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
static void derive_kek(uint32_t pin, const uint8_t *random_salt,
|
||||
const uint8_t *ext_salt,
|
||||
uint8_t kek[SHA256_DIGEST_LENGTH],
|
||||
@ -383,6 +481,13 @@ static void derive_kek(uint32_t pin, const uint8_t *random_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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
uint8_t *ekeys = buffer + RANDOM_SALT_SIZE;
|
||||
@ -517,6 +622,8 @@ static void init_wiped_storage(void) {
|
||||
ensure(storage_set_encrypted(VERSION_KEY, &version, sizeof(version)),
|
||||
"set_storage_version failed");
|
||||
ensure(pin_logs_init(0), "init_pin_logs failed");
|
||||
ensure(set_wipe_code(WIPE_CODE_EMPTY), "set_wipe_code failed");
|
||||
|
||||
ui_total = DERIVE_SECS;
|
||||
ui_rem = ui_total;
|
||||
ui_message = PROCESSING_MSG;
|
||||
@ -738,6 +845,25 @@ void storage_lock(void) {
|
||||
memzero(authentication_sum, sizeof(authentication_sum));
|
||||
}
|
||||
|
||||
secbool check_storage_version(void) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (version > norcow_active_version) {
|
||||
storage_wipe();
|
||||
} else if (version < norcow_active_version) {
|
||||
storage_set_encrypted(VERSION_KEY, &norcow_active_version,
|
||||
sizeof(norcow_active_version));
|
||||
}
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) {
|
||||
const void *buffer = NULL;
|
||||
uint16_t len = 0;
|
||||
@ -776,17 +902,9 @@ static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) {
|
||||
memzero(tag, sizeof(tag));
|
||||
|
||||
// Check that the authenticated version number matches the norcow version.
|
||||
// NOTE: storage_get_encrypted() calls auth_get(), which initializes the
|
||||
// authentication_sum.
|
||||
uint32_t version = 0;
|
||||
if (sectrue !=
|
||||
storage_get_encrypted(VERSION_KEY, &version, sizeof(version), &len) ||
|
||||
len != sizeof(version) || version != norcow_active_version) {
|
||||
handle_fault("storage version check");
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
return sectrue;
|
||||
// NOTE: This also initializes the authentication_sum by calling
|
||||
// storage_get_encrypted() which calls auth_get().
|
||||
return check_storage_version();
|
||||
}
|
||||
|
||||
static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
|
||||
@ -794,10 +912,16 @@ static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
// Check whether the user entered the wipe code.
|
||||
if (sectrue != is_not_wipe_code(pin)) {
|
||||
storage_wipe();
|
||||
error_shutdown("You have entered the", "wipe code. All private",
|
||||
"data has been erased.", NULL);
|
||||
}
|
||||
|
||||
// Get the pin failure counter
|
||||
uint32_t ctr = 0;
|
||||
if (sectrue != pin_get_fails(&ctr)) {
|
||||
memzero(&pin, sizeof(pin));
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
@ -837,7 +961,6 @@ 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));
|
||||
handle_fault("no EDEK");
|
||||
return secfalse;
|
||||
}
|
||||
@ -860,7 +983,7 @@ static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
// Check that the PIN was correct.
|
||||
// Check whether the entered PIN is correct.
|
||||
if (sectrue != decrypt_dek(kek, keiv)) {
|
||||
// Wipe storage if too many failures
|
||||
wait_random();
|
||||
@ -1175,6 +1298,28 @@ secbool storage_change_pin(uint32_t oldpin, uint32_t newpin,
|
||||
return ret;
|
||||
}
|
||||
|
||||
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)) {
|
||||
memzero(&pin, sizeof(pin));
|
||||
memzero(&wipe_code, sizeof(wipe_code));
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
ui_total = DERIVE_SECS;
|
||||
ui_rem = ui_total;
|
||||
ui_message = (pin != PIN_EMPTY && wipe_code == PIN_EMPTY) ? VERIFYING_PIN_MSG
|
||||
: PROCESSING_MSG;
|
||||
|
||||
secbool ret = secfalse;
|
||||
if (sectrue == unlock(pin, ext_salt)) {
|
||||
ret = set_wipe_code(wipe_code);
|
||||
}
|
||||
memzero(&pin, sizeof(pin));
|
||||
memzero(&wipe_code, sizeof(wipe_code));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void storage_wipe(void) {
|
||||
norcow_wipe();
|
||||
norcow_active_version = NORCOW_VERSION;
|
||||
@ -1253,6 +1398,7 @@ static secbool storage_upgrade(void) {
|
||||
uint16_t key = 0;
|
||||
uint16_t len = 0;
|
||||
const void *val = NULL;
|
||||
secbool ret = secfalse;
|
||||
|
||||
if (norcow_active_version == 0) {
|
||||
random_buffer(cached_keys, sizeof(cached_keys));
|
||||
@ -1261,7 +1407,7 @@ static secbool storage_upgrade(void) {
|
||||
auth_init();
|
||||
|
||||
// Set the new storage version number.
|
||||
uint32_t version = NORCOW_VERSION;
|
||||
uint32_t version = 1;
|
||||
if (sectrue !=
|
||||
storage_set_encrypted(VERSION_KEY, &version, sizeof(version))) {
|
||||
return secfalse;
|
||||
@ -1289,7 +1435,6 @@ static secbool storage_upgrade(void) {
|
||||
continue;
|
||||
}
|
||||
|
||||
secbool ret = secfalse;
|
||||
if (((key >> 8) & FLAG_PUBLIC) != 0) {
|
||||
ret = norcow_set(key, val, len);
|
||||
} else {
|
||||
@ -1304,7 +1449,19 @@ static secbool storage_upgrade(void) {
|
||||
unlocked = secfalse;
|
||||
memzero(cached_keys, sizeof(cached_keys));
|
||||
} else {
|
||||
return secfalse;
|
||||
// Copy all entries.
|
||||
uint32_t offset = 0;
|
||||
while (sectrue == norcow_get_next(&offset, &key, &val, &len)) {
|
||||
if (sectrue != norcow_set(key, val, len)) {
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (norcow_active_version <= 1) {
|
||||
if (sectrue != set_wipe_code(WIPE_CODE_EMPTY)) {
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
|
||||
norcow_active_version = NORCOW_VERSION;
|
||||
|
@ -45,16 +45,18 @@ 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(const uint32_t pin, const uint8_t *ext_salt);
|
||||
secbool storage_unlock(uint32_t pin, 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(const uint32_t oldpin, const uint32_t newpin,
|
||||
secbool storage_change_pin(uint32_t oldpin, uint32_t newpin,
|
||||
const uint8_t *old_ext_salt,
|
||||
const uint8_t *new_ext_salt);
|
||||
secbool storage_change_wipe_code(uint32_t pin, const uint8_t *ext_salt,
|
||||
uint32_t wipe_code);
|
||||
secbool storage_get(const uint16_t key, void *val, const uint16_t max_len,
|
||||
uint16_t *len);
|
||||
secbool storage_set(const uint16_t key, const void *val, uint16_t len);
|
||||
secbool storage_set(const uint16_t key, const void *val, const uint16_t len);
|
||||
secbool storage_delete(const uint16_t key);
|
||||
secbool storage_set_counter(const uint16_t key, const uint32_t count);
|
||||
secbool storage_next_counter(const uint16_t key, uint32_t *count);
|
||||
|
@ -40,6 +40,6 @@
|
||||
/*
|
||||
* Current storage version.
|
||||
*/
|
||||
#define NORCOW_VERSION ((uint32_t)0x00000001)
|
||||
#define NORCOW_VERSION ((uint32_t)0x00000002)
|
||||
|
||||
#endif
|
||||
|
@ -15,6 +15,9 @@ VERSION_KEY = (PIN_APP_ID << 8) | 0x04
|
||||
# Norcow storage key of the storage authentication tag.
|
||||
SAT_KEY = (PIN_APP_ID << 8) | 0x05
|
||||
|
||||
# Norcow storage key of the wipe code data.
|
||||
WIPE_CODE_DATA_KEY = (PIN_APP_ID << 8) | 0x06
|
||||
|
||||
# The PIN value corresponding to an empty PIN.
|
||||
PIN_EMPTY = 1
|
||||
|
||||
@ -46,6 +49,17 @@ KEK_SIZE = 32
|
||||
# The length of KEIV in bytes.
|
||||
KEIV_SIZE = 12
|
||||
|
||||
# The byte length of the salt used in checking the wipe code.
|
||||
WIPE_CODE_SALT_SIZE = 8
|
||||
|
||||
# The byte length of the tag used in checking the wipe code.
|
||||
WIPE_CODE_TAG_SIZE = 8
|
||||
|
||||
# The value corresponding to an unconfigured wipe code.
|
||||
# NOTE: This is intentionally different from PIN_EMPTY so that we don't need
|
||||
# special handling when both the PIN and wipe code are not set.
|
||||
WIPE_CODE_EMPTY = 0
|
||||
|
||||
# Size of counter. 4B integer and 8B tail.
|
||||
COUNTER_TAIL = 12
|
||||
COUNTER_TAIL_SIZE = 8
|
||||
@ -106,7 +120,7 @@ NORCOW_SECTOR_SIZE = 64 * 1024
|
||||
NORCOW_MAGIC = b"NRC2"
|
||||
|
||||
# Norcow version, set in the storage header, but also as an encrypted item.
|
||||
NORCOW_VERSION = b"\x01\x00\x00\x00"
|
||||
NORCOW_VERSION = b"\x02\x00\x00\x00"
|
||||
|
||||
# Norcow magic combined with the version, which is stored as its negation.
|
||||
NORCOW_MAGIC_AND_VERSION = NORCOW_MAGIC + bytes(
|
||||
|
@ -38,8 +38,9 @@ class Storage:
|
||||
self.sak = prng.random_buffer(consts.SAK_SIZE)
|
||||
|
||||
self.nc.set(consts.SAT_KEY, crypto.init_hmacs(self.sak))
|
||||
self._set_encrypt(consts.VERSION_KEY, b"\x01\x00\x00\x00")
|
||||
self._set_encrypt(consts.VERSION_KEY, b"\x02\x00\x00\x00")
|
||||
self.pin_log.init()
|
||||
self._set_wipe_code(consts.WIPE_CODE_EMPTY)
|
||||
self._set_pin(consts.PIN_EMPTY)
|
||||
self.unlocked = False
|
||||
|
||||
@ -59,6 +60,14 @@ class Storage:
|
||||
else:
|
||||
self._set_bool(consts.PIN_NOT_SET_KEY, False)
|
||||
|
||||
def _set_wipe_code(self, wipe_code: int):
|
||||
if wipe_code == consts.PIN_EMPTY:
|
||||
wipe_code = consts.WIPE_CODE_EMPTY
|
||||
wipe_code_bytes = wipe_code.to_bytes(4, "little")
|
||||
salt = prng.random_buffer(consts.WIPE_CODE_SALT_SIZE)
|
||||
tag = crypto._hmac(salt, wipe_code_bytes)[: consts.WIPE_CODE_TAG_SIZE]
|
||||
self.nc.set(consts.WIPE_CODE_DATA_KEY, wipe_code_bytes + salt + tag)
|
||||
|
||||
def wipe(self):
|
||||
self.nc.wipe()
|
||||
self._init_pin()
|
||||
|
@ -9,7 +9,7 @@ def test_norcow_set():
|
||||
n.init()
|
||||
n.set(0x0001, b"123")
|
||||
data = n._dump()[0][:256]
|
||||
assert data[:8] == b"NRC2\xFE\xFF\xFF\xFF"
|
||||
assert data[:8] == consts.NORCOW_MAGIC_AND_VERSION
|
||||
assert data[8:10] == b"\x01\x00" # app + key
|
||||
assert data[10:12] == b"\x03\x00" # length
|
||||
assert data[12:15] == b"123" # data
|
||||
@ -18,7 +18,7 @@ def test_norcow_set():
|
||||
n.wipe()
|
||||
n.set(0x0901, b"hello")
|
||||
data = n._dump()[0][:256]
|
||||
assert data[:8] == b"NRC2\xFE\xFF\xFF\xFF"
|
||||
assert data[:8] == consts.NORCOW_MAGIC_AND_VERSION
|
||||
assert data[8:10] == b"\x01\x09" # app + key
|
||||
assert data[10:12] == b"\x05\x00" # length
|
||||
assert data[12:17] == b"hello" # data
|
||||
@ -63,7 +63,8 @@ def test_norcow_get_item():
|
||||
assert value == b"123"
|
||||
assert (
|
||||
n._dump()[0][:40].hex()
|
||||
== "4e524332feffffff010003003132330002000300343536000101030037383900ffffffffffffffff"
|
||||
== consts.NORCOW_MAGIC_AND_VERSION.hex()
|
||||
+ "010003003132330002000300343536000101030037383900ffffffffffffffff"
|
||||
)
|
||||
|
||||
# replacing item with the same value (update)
|
||||
@ -72,7 +73,8 @@ def test_norcow_get_item():
|
||||
assert value == b"789"
|
||||
assert (
|
||||
n._dump()[0][:40].hex()
|
||||
== "4e524332feffffff010003003132330002000300343536000101030037383900ffffffffffffffff"
|
||||
== consts.NORCOW_MAGIC_AND_VERSION.hex()
|
||||
+ "010003003132330002000300343536000101030037383900ffffffffffffffff"
|
||||
)
|
||||
|
||||
# replacing item with value with less 1 bits than before (update)
|
||||
@ -81,7 +83,8 @@ def test_norcow_get_item():
|
||||
assert value == b"788"
|
||||
assert (
|
||||
n._dump()[0][:40].hex()
|
||||
== "4e524332feffffff010003003132330002000300343536000101030037383800ffffffffffffffff"
|
||||
== consts.NORCOW_MAGIC_AND_VERSION.hex()
|
||||
+ "010003003132330002000300343536000101030037383800ffffffffffffffff"
|
||||
)
|
||||
|
||||
# replacing item with value with more 1 bits than before (wipe and new entry)
|
||||
@ -90,7 +93,8 @@ def test_norcow_get_item():
|
||||
assert value == b"787"
|
||||
assert (
|
||||
n._dump()[0][:44].hex()
|
||||
== "4e524332feffffff0100030031323300020003003435360000000300000000000101030037383700ffffffff"
|
||||
== consts.NORCOW_MAGIC_AND_VERSION.hex()
|
||||
+ "0100030031323300020003003435360000000300000000000101030037383700ffffffff"
|
||||
)
|
||||
|
||||
n.set(0x0002, b"world")
|
||||
|
Loading…
Reference in New Issue
Block a user