storage: Implement storage_change_wipe_code().

pull/720/head
Andrew Kozlik 5 years ago
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…
Cancel
Save