From 434ed04b7f72e71e67925c8ddcd2d4b181ba4a5b Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Mon, 5 Feb 2024 12:52:08 +0100 Subject: [PATCH] feat(storage): implemented more effective pin logs for blockwise storage [no changelog] --- docs/storage/index.md | 70 ++++- storage/pinlogs_bitwise.h | 267 +++++++++++++++++ storage/pinlogs_blockwise.h | 87 ++++++ storage/storage.c | 273 +----------------- storage/tests/python/src/consts.py | 17 -- storage/tests/python/src/helpers.py | 7 - storage/tests/python/src/norcow.py | 8 + .../src/{pin_log.py => pin_log_bitwise.py} | 65 +++-- storage/tests/python/src/pin_log_blockwise.py | 59 ++++ storage/tests/python/src/storage.py | 3 +- storage/tests/python/tests/test_helpers.py | 7 +- storage/tests/python/tests/test_pin_log.py | 4 +- storage/tests/tests/test_compact.py | 2 +- storage/tests/tests/test_upgrade.py | 4 +- 14 files changed, 542 insertions(+), 331 deletions(-) create mode 100644 storage/pinlogs_bitwise.h create mode 100644 storage/pinlogs_blockwise.h rename storage/tests/python/src/{pin_log.py => pin_log_bitwise.py} (66%) create mode 100644 storage/tests/python/src/pin_log_blockwise.py diff --git a/docs/storage/index.md b/docs/storage/index.md index 2b0c46e9b..5be1c994e 100644 --- a/docs/storage/index.md +++ b/docs/storage/index.md @@ -169,13 +169,15 @@ where `⨁` denotes the n-ary bitwise XOR operation and KEYi | - Instead of using separate IVs for each entry we considered using a single IV for the entire sector. Upon sector compaction a new IV would have to be generated and the encrypted data would have to be reencrypted under the new IV. A possible issue with this approach is that compaction cannot happen without the DEK, i.e. generally data could not be written to the flash storage without knowing the PIN. This property might not always be desirable. -## New measures for PIN entry counter protection +## PIN entry counter protection + +### Bitwise flash The former implementation of the PIN entry counter was vulnerable to fault injection attacks. Under the former implementation the PIN counter storage entry consisted of 32 words initialized to 0xFFFFFFFF. The first non-zero word in this area was the current PIN failure counter. Before verifying the PIN the lowest bit with value 1 was set to 0, i.e. a value of FFFFFFFC indicated two PIN entries. Upon successful PIN entry, the word was set to 0x00000000, indicating that the next word was the PIN failure counter. Allegedly, by manipulating the voltage on the USB input an attacker could convince the device to read the PIN entry counter as 0xFFFFFFFF even if some of the bits had been set to 0. -### Design goals +#### Design goals - Make it easy to decrement the counter by changing a 1 bit to 0. - Make it hard to reset the counter by a fault injection, i.e. counter values should not have an overly simple binary representation like 0xFFFFFFFF. @@ -183,7 +185,7 @@ Under the former implementation the PIN counter storage entry consisted of 32 wo - Optimize the format for successful PIN entry. - Minimize the number of branching operations. Avoid loops, instead utilize bitwise and arithmetic operations when processing the PIN counter data. -### Proposal summary +#### Proposal summary Under the former implementation, for every unsuccessful PIN entry we discarded one bit from the counter, while for every successful PIN entry we discard an entire word. In the new implementation we optimize the counter operations for successful PIN entry. @@ -198,7 +200,7 @@ Before every PIN verification the highest 1-bit in the `pin_entry_log` is set to In actual fact the logs are not written to the flash storage exactly as shown above, but they are stored in a form that should protect them against fault injection attacks. Only half of the stored bits carry information, the other half acts as "guard bits". So a stored value `...001110...` could look like `...0g0gg1g11g0g...`, where `g` denotes a guard bit. The positions and the values of the guard bits are determined by a guard key. The `guard_key` is a randomly generated uint32 value stored as an entry in the flash memory in cleartext. The assumption behind this is that an attacker attempting to reset or decrement the PIN counter by a fault injection is not able to read the flash storage. However, the value of `guard_key` also needs to be protected against fault injection, so the set of valid `guard_key` values should be limited by some condition which is easy to verify, such as `guard_key mod M == C`, where `M` and `C` a suitably chosen constants. The constants should be chosen so that the binary representation of any valid `guard_key` value has Hamming weight between 8 and 24. These conditions are discussed below. -### Storage format +#### Storage format The PIN log has APP = 0 and KEY = 1. The DATA part of the entry consists of 33 words (132 bytes, assuming 32-bit words): @@ -208,7 +210,7 @@ The PIN log has APP = 0 and KEY = 1. The DATA part of the entry consists of 33 w Each log is stored in big-endian word order. The byte order of each word is platform dependent. -### Guard key validation +#### Guard key validation The guard_key is said to be valid if the following three conditions hold true: @@ -237,7 +239,7 @@ int key_validity(uint32_t guard_key) } ``` -### Key generation +#### Key generation The `guard_key` may be generated in the following way: @@ -247,7 +249,7 @@ The `guard_key` may be generated in the following way: Note that on average steps 1 to 3 are repeated about one hundred times. -### Key expansion +#### Key expansion The `guard_key` is read from storage, its value is checked for validity and used to compute the `guard_mask` (indicating the positions of the guard bits) and guard value (indicating the values of the guard bits on their actual positions): @@ -280,13 +282,13 @@ guard = (((guard_key & LOW_MASK) << 1) & guard_key) | (((~guard_key) & LOW_MASK) & (guard_key >> 1)) ``` -### Log initialization +#### Log initialization Each log is stored as 16 consecutive words each initialized to: `guard | ~guard_mask` -### Removing and adding guard bits +#### Removing and adding guard bits After reading a word from the flash storage we verify the format by checking the condition: @@ -306,7 +308,7 @@ The guard bits can be added back as follows: `word = (word & ~guard_mask) | guard` -### Determining the number of PIN failures +#### Determining the number of PIN failures Remove the guard bits from the words of the `pin_entry_log` using the operations described above and verify that the result has form 0\*1\* by checking the condition: @@ -329,3 +331,51 @@ count = (count + (count >> 4)) & 0x0F0F0F0F count = count + (count >> 8) count = (count + (count >> 16)) & 0x3F ``` + +### Blockwise flash + +For blockwise flash, the PIN counter protection was significantly simplified. This is fine because models with this type of flash +use secure element for additional protection. The flash is also ECC protected, so fault injection attacks are harder. + + +#### Design goals + +- Fit one block to not waste too much space when counting PIN failures. +- Provide a simple way to check the PIN counter value. +- Make it reasonably hard to reset the counter by a fault injection. +- Significantly reduce the code complexity. + +#### Format + +The counter is 8 bit value, stored in such a way that every 1 bit is expanded to 01 and every 0 bit is expanded to 10. +The resulting 16 bit value is written into the flash block as many times as needed to fill the block. + +#### Expanding the counter + +The counter is expanded by the following procedure: + +```c +c = ((c << 4) | c) & 0x0f0f; +c = ((c << 2) | c) & 0x3333; +c = ((c << 1) | c) & 0x5555; +c = ((c << 1) | c) ^ 0xaaaa; +``` + +#### Compressing the counter + +The counter is compressed by the following procedure: + +```c +c = c & 0x5555; +c = ((c >> 1) | c) & 0x3333; +c = ((c >> 2) | c) & 0x0f0f; +c = ((c >> 4) | c) & 0x00ff; +``` + +#### Checking the counter + +The counter format is checked by the following operation: + +```c +((c ^ (c << 1)) & 0xAAAA != 0xAAAA) +``` diff --git a/storage/pinlogs_bitwise.h b/storage/pinlogs_bitwise.h new file mode 100644 index 000000000..042df4b84 --- /dev/null +++ b/storage/pinlogs_bitwise.h @@ -0,0 +1,267 @@ + +// Values used in the guard key integrity check. +#define GUARD_KEY_MODULUS 6311 +#define GUARD_KEY_REMAINDER 15 + +#define LOW_MASK 0x55555555 + +// The length of the guard key in words. +#define GUARD_KEY_WORDS 1 + +// The length of the PIN entry log or the PIN success log in words. +#define PIN_LOG_WORDS 16 + +// The length of a word in bytes. +#define WORD_SIZE (sizeof(uint32_t)) + +static secbool check_guard_key(const uint32_t guard_key) { + if (guard_key % GUARD_KEY_MODULUS != GUARD_KEY_REMAINDER) { + return secfalse; + } + + // Check that each byte of (guard_key & 0xAAAAAAAA) has exactly two bits set. + uint32_t count = (guard_key & 0x22222222) + ((guard_key >> 2) & 0x22222222); + count = count + (count >> 4); + if ((count & 0x0e0e0e0e) != 0x04040404) { + return secfalse; + } + + // Check that the guard_key does not contain a run of 5 (or more) zeros or + // ones. + uint32_t zero_runs = ~guard_key; + zero_runs = zero_runs & (zero_runs >> 2); + zero_runs = zero_runs & (zero_runs >> 1); + zero_runs = zero_runs & (zero_runs >> 1); + + uint32_t one_runs = guard_key; + one_runs = one_runs & (one_runs >> 2); + one_runs = one_runs & (one_runs >> 1); + one_runs = one_runs & (one_runs >> 1); + + if ((one_runs != 0) || (zero_runs != 0)) { + return secfalse; + } + + return sectrue; +} + +static uint32_t generate_guard_key(void) { + uint32_t guard_key = 0; + do { + guard_key = random_uniform((UINT32_MAX / GUARD_KEY_MODULUS) + 1) * + GUARD_KEY_MODULUS + + GUARD_KEY_REMAINDER; + } while (sectrue != check_guard_key(guard_key)); + return guard_key; +} + +static secbool expand_guard_key(const uint32_t guard_key, uint32_t *guard_mask, + uint32_t *guard) { + if (sectrue != check_guard_key(guard_key)) { + handle_fault("guard key check"); + return secfalse; + } + *guard_mask = ((guard_key & LOW_MASK) << 1) | ((~guard_key) & LOW_MASK); + *guard = (((guard_key & LOW_MASK) << 1) & guard_key) | + (((~guard_key) & LOW_MASK) & (guard_key >> 1)); + return sectrue; +} + +static secbool pin_logs_init(uint32_t fails) { + if (fails >= PIN_MAX_TRIES) { + return secfalse; + } + + // The format of the PIN_LOGS_KEY entry is: + // guard_key (1 word), pin_success_log (PIN_LOG_WORDS), pin_entry_log + // (PIN_LOG_WORDS) + uint32_t logs[GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS] = {0}; + + logs[0] = generate_guard_key(); + + uint32_t guard_mask = 0; + uint32_t guard = 0; + wait_random(); + if (sectrue != expand_guard_key(logs[0], &guard_mask, &guard)) { + return secfalse; + } + + uint32_t unused = guard | ~guard_mask; + for (size_t i = 0; i < 2 * PIN_LOG_WORDS; ++i) { + logs[GUARD_KEY_WORDS + i] = unused; + } + + // Set the first word of the PIN entry log to indicate the requested number of + // fails. + logs[GUARD_KEY_WORDS + PIN_LOG_WORDS] = + ((((uint32_t)0xFFFFFFFF) >> (2 * fails)) & ~guard_mask) | guard; + + return norcow_set(PIN_LOGS_KEY, logs, sizeof(logs)); +} + +static secbool pin_fails_reset(void) { + const void *logs = NULL; + uint16_t len = 0; + + if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || + len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) { + return secfalse; + } + + uint32_t new_logs[GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS]; + secbool edited = secfalse; + memcpy(new_logs, logs, len); + + uint32_t guard_mask = 0; + uint32_t guard = 0; + wait_random(); + if (sectrue != + expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) { + return secfalse; + } + + uint32_t unused = guard | ~guard_mask; + const uint32_t *success_log = ((const uint32_t *)logs) + GUARD_KEY_WORDS; + const uint32_t *entry_log = success_log + PIN_LOG_WORDS; + for (size_t i = 0; i < PIN_LOG_WORDS; ++i) { + if (entry_log[i] == unused) { + if (edited == sectrue) { + return norcow_set(PIN_LOGS_KEY, new_logs, sizeof(new_logs)); + } + return sectrue; + } + if (success_log[i] != guard) { + if (new_logs[(i + GUARD_KEY_WORDS)] != entry_log[i]) { + edited = sectrue; + new_logs[(i + GUARD_KEY_WORDS)] = entry_log[i]; + } + } + } + return pin_logs_init(0); +} + +secbool pin_fails_increase(void) { + const void *logs = NULL; + uint16_t len = 0; + + wait_random(); + if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || + len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) { + handle_fault("no PIN logs"); + return secfalse; + } + + uint32_t new_logs[GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS]; + memcpy(new_logs, logs, len); + + uint32_t guard_mask = 0; + uint32_t guard = 0; + wait_random(); + if (sectrue != + expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) { + handle_fault("guard key expansion"); + return secfalse; + } + + const uint32_t *entry_log = + ((const uint32_t *)logs) + GUARD_KEY_WORDS + PIN_LOG_WORDS; + for (size_t i = 0; i < PIN_LOG_WORDS; ++i) { + wait_random(); + if ((entry_log[i] & guard_mask) != guard) { + handle_fault("guard bits check"); + return secfalse; + } + if (entry_log[i] != guard) { + wait_random(); + uint32_t word = entry_log[i] & ~guard_mask; + word = ((word >> 1) | word) & LOW_MASK; + word = (word >> 2) | (word >> 1); + + wait_random(); + + new_logs[(i + GUARD_KEY_WORDS + PIN_LOG_WORDS)] = + (word & ~guard_mask) | guard; + if (sectrue != norcow_set(PIN_LOGS_KEY, new_logs, sizeof(new_logs))) { + handle_fault("PIN logs update"); + return secfalse; + } + return sectrue; + } + } + handle_fault("PIN log exhausted"); + return secfalse; +} + +static secbool pin_get_fails(uint32_t *ctr) { + *ctr = PIN_MAX_TRIES; + + const void *logs = NULL; + uint16_t len = 0; + wait_random(); + if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || + len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) { + handle_fault("no PIN logs"); + return secfalse; + } + + uint32_t guard_mask = 0; + uint32_t guard = 0; + wait_random(); + if (sectrue != + expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) { + handle_fault("guard key expansion"); + return secfalse; + } + const uint32_t unused = guard | ~guard_mask; + + const uint32_t *success_log = ((const uint32_t *)logs) + GUARD_KEY_WORDS; + const uint32_t *entry_log = success_log + PIN_LOG_WORDS; + volatile int current = -1; + volatile size_t i = 0; + for (i = 0; i < PIN_LOG_WORDS; ++i) { + if ((entry_log[i] & guard_mask) != guard || + (success_log[i] & guard_mask) != guard || + (entry_log[i] & success_log[i]) != entry_log[i]) { + handle_fault("PIN logs format check"); + return secfalse; + } + + if (current == -1) { + if (entry_log[i] != guard) { + current = i; + } + } else { + if (entry_log[i] != unused) { + handle_fault("PIN entry log format check"); + return secfalse; + } + } + } + + if (current < 0 || current >= PIN_LOG_WORDS || i != PIN_LOG_WORDS) { + handle_fault("PIN log exhausted"); + return secfalse; + } + + // Strip the guard bits from the current entry word and duplicate each data + // bit. + wait_random(); + uint32_t word = entry_log[current] & ~guard_mask; + word = ((word >> 1) | word) & LOW_MASK; + word = word | (word << 1); + // Verify that the entry word has form 0*1*. + if ((word & (word + 1)) != 0) { + handle_fault("PIN entry log format check"); + return secfalse; + } + + if (current == 0) { + ++current; + } + + // Count the number of set bits in the two current words of the success log. + wait_random(); + *ctr = hamming_weight(success_log[current - 1] ^ entry_log[current - 1]) + + hamming_weight(success_log[current] ^ entry_log[current]); + return sectrue; +} diff --git a/storage/pinlogs_blockwise.h b/storage/pinlogs_blockwise.h new file mode 100644 index 000000000..72f04aa0d --- /dev/null +++ b/storage/pinlogs_blockwise.h @@ -0,0 +1,87 @@ + +#if FLASH_BLOCK_WORDS <= 1 +#error "FLASH_BLOCK_WORDS must be at least 2 to fit the counter and header" +#endif + +#define PIN_LOG_HALFWORDS (((FLASH_BLOCK_WORDS - 1) * sizeof(uint32_t)) / 2) + +static uint16_t expand_counter(uint16_t c) { + c = ((c << 4) | c) & 0x0f0f; + c = ((c << 2) | c) & 0x3333; + c = ((c << 1) | c) & 0x5555; + c = ((c << 1) | c) ^ 0xaaaa; + return c; +} + +static uint16_t compress_counter(uint16_t c) { + if (((c ^ (c << 1)) & 0xAAAA) != 0xAAAA) { + handle_fault("ill-formed counter"); + } + c = c & 0x5555; + c = ((c >> 1) | c) & 0x3333; + c = ((c >> 2) | c) & 0x0f0f; + c = ((c >> 4) | c) & 0x00ff; + return c; +} + +static secbool pin_get_fails(uint32_t *ctr) { + const void *logs = NULL; + uint16_t len = 0; + + wait_random(); + + if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || + len != PIN_LOG_HALFWORDS * sizeof(uint16_t)) { + handle_fault("no PIN logs"); + return secfalse; + } + + uint16_t c = compress_counter(((uint16_t *)logs)[0]); + + uint16_t correct_bytes_cnt = 0; + + for (uint8_t i = 0; i < PIN_LOG_HALFWORDS; i++) { + wait_random(); + correct_bytes_cnt += compress_counter(((uint16_t *)logs)[i]) == c; + *ctr = c; + } + + if (correct_bytes_cnt != PIN_LOG_HALFWORDS) { + handle_fault("PIN logs corrupted"); + return secfalse; + } + + return sectrue * (correct_bytes_cnt == PIN_LOG_HALFWORDS); +} + +static secbool pin_logs_init(uint32_t fails) { + wait_random(); + + uint16_t logs[PIN_LOG_HALFWORDS]; + uint16_t ctr = expand_counter(fails); + + for (uint8_t i = 0; i < PIN_LOG_HALFWORDS; i++) { + logs[i] = ctr; + } + + if (fails != compress_counter(ctr)) { + handle_fault("PIN logs increase failed"); + return secfalse; + } + + return norcow_set(PIN_LOGS_KEY, logs, sizeof(logs)); +} + +static secbool pin_fails_reset(void) { return pin_logs_init(0); } + +secbool pin_fails_increase(void) { + uint32_t fails; + + if (sectrue != pin_get_fails(&fails)) { + return secfalse; + } + + fails++; + + return pin_logs_init(fails); +} diff --git a/storage/storage.c b/storage/storage.c index a43a02fa9..f22c35d13 100644 --- a/storage/storage.c +++ b/storage/storage.c @@ -36,8 +36,6 @@ #include "optiga.h" #endif -#define LOW_MASK 0x55555555 - // The APP namespace which is reserved for storage related values. #define APP_STORAGE 0x00 @@ -96,15 +94,6 @@ const uint32_t V0_PIN_EMPTY = 1; #define PIN_DERIVE_MS PIN_PBKDF2_MS #endif -// The length of the guard key in words. -#define GUARD_KEY_WORDS 1 - -// The length of the PIN entry log or the PIN success log in words. -#define PIN_LOG_WORDS 16 - -// The length of a word in bytes. -#define WORD_SIZE (sizeof(uint32_t)) - // The length of the hashed hardware salt in bytes. #define HARDWARE_SALT_SIZE SHA256_DIGEST_LENGTH @@ -148,10 +137,6 @@ const uint8_t WIPE_CODE_EMPTY[] = {0, 0, 0, 0}; // The uint32 representation of an empty wipe code used in storage version 2. #define V2_WIPE_CODE_EMPTY 0 -// Values used in the guard key integrity check. -#define GUARD_KEY_MODULUS 6311 -#define GUARD_KEY_REMAINDER 15 - // TODO: handle translation const char *const VERIFYING_PIN_MSG = "Verifying PIN"; const char *const PROCESSING_MSG = "Processing"; @@ -188,6 +173,13 @@ 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); +#include "flash.h" +#ifdef FLASH_BIT_ACCESS +#include "pinlogs_bitwise.h" +#else +#include "pinlogs_blockwise.h" +#endif + static secbool secequal(const void *ptr1, const void *ptr2, size_t n) { const uint8_t *p1 = ptr1; const uint8_t *p2 = ptr2; @@ -669,91 +661,6 @@ static secbool set_pin(const uint8_t *pin, size_t pin_len, return ret; } -static secbool check_guard_key(const uint32_t guard_key) { - if (guard_key % GUARD_KEY_MODULUS != GUARD_KEY_REMAINDER) { - return secfalse; - } - - // Check that each byte of (guard_key & 0xAAAAAAAA) has exactly two bits set. - uint32_t count = (guard_key & 0x22222222) + ((guard_key >> 2) & 0x22222222); - count = count + (count >> 4); - if ((count & 0x0e0e0e0e) != 0x04040404) { - return secfalse; - } - - // Check that the guard_key does not contain a run of 5 (or more) zeros or - // ones. - uint32_t zero_runs = ~guard_key; - zero_runs = zero_runs & (zero_runs >> 2); - zero_runs = zero_runs & (zero_runs >> 1); - zero_runs = zero_runs & (zero_runs >> 1); - - uint32_t one_runs = guard_key; - one_runs = one_runs & (one_runs >> 2); - one_runs = one_runs & (one_runs >> 1); - one_runs = one_runs & (one_runs >> 1); - - if ((one_runs != 0) || (zero_runs != 0)) { - return secfalse; - } - - return sectrue; -} - -static uint32_t generate_guard_key(void) { - uint32_t guard_key = 0; - do { - guard_key = random_uniform((UINT32_MAX / GUARD_KEY_MODULUS) + 1) * - GUARD_KEY_MODULUS + - GUARD_KEY_REMAINDER; - } while (sectrue != check_guard_key(guard_key)); - return guard_key; -} - -static secbool expand_guard_key(const uint32_t guard_key, uint32_t *guard_mask, - uint32_t *guard) { - if (sectrue != check_guard_key(guard_key)) { - handle_fault("guard key check"); - return secfalse; - } - *guard_mask = ((guard_key & LOW_MASK) << 1) | ((~guard_key) & LOW_MASK); - *guard = (((guard_key & LOW_MASK) << 1) & guard_key) | - (((~guard_key) & LOW_MASK) & (guard_key >> 1)); - return sectrue; -} - -static secbool pin_logs_init(uint32_t fails) { - if (fails >= PIN_MAX_TRIES) { - return secfalse; - } - - // The format of the PIN_LOGS_KEY entry is: - // guard_key (1 word), pin_success_log (PIN_LOG_WORDS), pin_entry_log - // (PIN_LOG_WORDS) - uint32_t logs[GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS] = {0}; - - logs[0] = generate_guard_key(); - - uint32_t guard_mask = 0; - uint32_t guard = 0; - wait_random(); - if (sectrue != expand_guard_key(logs[0], &guard_mask, &guard)) { - return secfalse; - } - - uint32_t unused = guard | ~guard_mask; - for (size_t i = 0; i < 2 * PIN_LOG_WORDS; ++i) { - logs[GUARD_KEY_WORDS + i] = unused; - } - - // Set the first word of the PIN entry log to indicate the requested number of - // fails. - logs[GUARD_KEY_WORDS + PIN_LOG_WORDS] = - ((((uint32_t)0xFFFFFFFF) >> (2 * fails)) & ~guard_mask) | guard; - - return norcow_set(PIN_LOGS_KEY, logs, sizeof(logs)); -} - /* * Initializes the values of VERSION_KEY, EDEK_PVC_KEY, PIN_NOT_SET_KEY and * PIN_LOGS_KEY using an empty PIN. This function should be called to initialize @@ -820,175 +727,11 @@ void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt, memzero(cached_keys, sizeof(cached_keys)); } -static secbool pin_fails_reset(void) { - const void *logs = NULL; - uint16_t len = 0; - - if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || - len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) { - return secfalse; - } - - uint32_t new_logs[GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS]; - secbool edited = secfalse; - memcpy(new_logs, logs, len); - - uint32_t guard_mask = 0; - uint32_t guard = 0; - wait_random(); - if (sectrue != - expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) { - return secfalse; - } - - uint32_t unused = guard | ~guard_mask; - const uint32_t *success_log = ((const uint32_t *)logs) + GUARD_KEY_WORDS; - const uint32_t *entry_log = success_log + PIN_LOG_WORDS; - for (size_t i = 0; i < PIN_LOG_WORDS; ++i) { - if (entry_log[i] == unused) { - if (edited == sectrue) { - return norcow_set(PIN_LOGS_KEY, new_logs, sizeof(new_logs)); - } - return sectrue; - } - if (success_log[i] != guard) { - if (new_logs[(i + GUARD_KEY_WORDS)] != entry_log[i]) { - edited = sectrue; - new_logs[(i + GUARD_KEY_WORDS)] = entry_log[i]; - } - } - } - return pin_logs_init(0); -} - secbool storage_pin_fails_increase(void) { if (sectrue != initialized) { return secfalse; } - - const void *logs = NULL; - uint16_t len = 0; - - wait_random(); - if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || - len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) { - handle_fault("no PIN logs"); - return secfalse; - } - - uint32_t new_logs[GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS]; - memcpy(new_logs, logs, len); - - uint32_t guard_mask = 0; - uint32_t guard = 0; - wait_random(); - if (sectrue != - expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) { - handle_fault("guard key expansion"); - return secfalse; - } - - const uint32_t *entry_log = - ((const uint32_t *)logs) + GUARD_KEY_WORDS + PIN_LOG_WORDS; - for (size_t i = 0; i < PIN_LOG_WORDS; ++i) { - wait_random(); - if ((entry_log[i] & guard_mask) != guard) { - handle_fault("guard bits check"); - return secfalse; - } - if (entry_log[i] != guard) { - wait_random(); - uint32_t word = entry_log[i] & ~guard_mask; - word = ((word >> 1) | word) & LOW_MASK; - word = (word >> 2) | (word >> 1); - - wait_random(); - - new_logs[(i + GUARD_KEY_WORDS + PIN_LOG_WORDS)] = - (word & ~guard_mask) | guard; - if (sectrue != norcow_set(PIN_LOGS_KEY, new_logs, sizeof(new_logs))) { - handle_fault("PIN logs update"); - return secfalse; - } - return sectrue; - } - } - handle_fault("PIN log exhausted"); - return secfalse; -} - -static secbool pin_get_fails(uint32_t *ctr) { - *ctr = PIN_MAX_TRIES; - - const void *logs = NULL; - uint16_t len = 0; - wait_random(); - if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) || - len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) { - handle_fault("no PIN logs"); - return secfalse; - } - - uint32_t guard_mask = 0; - uint32_t guard = 0; - wait_random(); - if (sectrue != - expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) { - handle_fault("guard key expansion"); - return secfalse; - } - const uint32_t unused = guard | ~guard_mask; - - const uint32_t *success_log = ((const uint32_t *)logs) + GUARD_KEY_WORDS; - const uint32_t *entry_log = success_log + PIN_LOG_WORDS; - volatile int current = -1; - volatile size_t i = 0; - for (i = 0; i < PIN_LOG_WORDS; ++i) { - if ((entry_log[i] & guard_mask) != guard || - (success_log[i] & guard_mask) != guard || - (entry_log[i] & success_log[i]) != entry_log[i]) { - handle_fault("PIN logs format check"); - return secfalse; - } - - if (current == -1) { - if (entry_log[i] != guard) { - current = i; - } - } else { - if (entry_log[i] != unused) { - handle_fault("PIN entry log format check"); - return secfalse; - } - } - } - - if (current < 0 || current >= PIN_LOG_WORDS || i != PIN_LOG_WORDS) { - handle_fault("PIN log exhausted"); - return secfalse; - } - - // Strip the guard bits from the current entry word and duplicate each data - // bit. - wait_random(); - uint32_t word = entry_log[current] & ~guard_mask; - word = ((word >> 1) | word) & LOW_MASK; - word = word | (word << 1); - // Verify that the entry word has form 0*1*. - if ((word & (word + 1)) != 0) { - handle_fault("PIN entry log format check"); - return secfalse; - } - - if (current == 0) { - ++current; - } - - // Count the number of set bits in the two current words of the success log. - wait_random(); - *ctr = hamming_weight(success_log[current - 1] ^ entry_log[current - 1]) + - hamming_weight(success_log[current] ^ entry_log[current]); - return sectrue; + return pin_fails_increase(); } secbool storage_is_unlocked(void) { diff --git a/storage/tests/python/src/consts.py b/storage/tests/python/src/consts.py index 63d7846d9..e2edb33cc 100644 --- a/storage/tests/python/src/consts.py +++ b/storage/tests/python/src/consts.py @@ -78,23 +78,6 @@ COUNTER_MAX_TAIL = 64 # Storage key of the PIN entry log and PIN success log. PIN_LOG_KEY = (PIN_APP_ID << 8) | 0x01 -# Length of items in the PIN entry log -PIN_LOG_GUARD_KEY_SIZE = 4 - -# Values used for the guard key integrity check. -GUARD_KEY_MODULUS = 6311 -GUARD_KEY_REMAINDER = 15 -GUARD_KEY_RANDOM_MAX = (0xFFFFFFFF // GUARD_KEY_MODULUS) + 1 - -# Length of both success log and entry log -PIN_LOG_SIZE = 64 - -# Used for in guard bits operations. -LOW_MASK = 0x55555555 - -# Log initialized to all FFs. -ALL_FF_LOG = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - # ----- Bytes ----- # If the top bit of APP is set, then the value is not encrypted. diff --git a/storage/tests/python/src/helpers.py b/storage/tests/python/src/helpers.py index 83ec34e2e..a41c8dd0d 100644 --- a/storage/tests/python/src/helpers.py +++ b/storage/tests/python/src/helpers.py @@ -3,13 +3,6 @@ import sys from . import consts -def expand_to_log_size(value: int) -> int: - result = 0 - for i in range(0, consts.PIN_LOG_SIZE, 4): - result = result | (value << i * 8) - return result - - def to_int_by_words(array: bytes) -> int: """ Converts array of bytes into an int by reading word size diff --git a/storage/tests/python/src/norcow.py b/storage/tests/python/src/norcow.py index 37b605240..d49a7639c 100644 --- a/storage/tests/python/src/norcow.py +++ b/storage/tests/python/src/norcow.py @@ -1,6 +1,8 @@ from struct import pack, unpack from . import consts +from .pin_log_bitwise import PinLogBitwise +from .pin_log_blockwise import PinLogBlockwise def align_int(i: int, align: int): @@ -174,6 +176,9 @@ class NorcowBitwise(Norcow): self.item_prefix_len = 4 self.lib_name = "libtrezor-storage.so" + def get_pin_log(self): + return PinLogBitwise(self) + def get_lib_name(self): return self.lib_name @@ -231,6 +236,9 @@ class NorcowBlockwise(Norcow): self.item_prefix_len = 4 * consts.WORD_SIZE + 1 self.lib_name = "libtrezor-storage-qw.so" + def get_pin_log(self): + return PinLogBlockwise(self) + def get_lib_name(self): return self.lib_name diff --git a/storage/tests/python/src/pin_log.py b/storage/tests/python/src/pin_log_bitwise.py similarity index 66% rename from storage/tests/python/src/pin_log.py rename to storage/tests/python/src/pin_log_bitwise.py index dc67e178a..a7f37ecc4 100644 --- a/storage/tests/python/src/pin_log.py +++ b/storage/tests/python/src/pin_log_bitwise.py @@ -1,7 +1,31 @@ from . import consts, helpers, prng +# Length of items in the PIN entry log +PIN_LOG_GUARD_KEY_SIZE = 4 -class PinLog: +# Values used for the guard key integrity check. +GUARD_KEY_MODULUS = 6311 +GUARD_KEY_REMAINDER = 15 +GUARD_KEY_RANDOM_MAX = (0xFFFFFFFF // GUARD_KEY_MODULUS) + 1 + +# Length of both success log and entry log +PIN_LOG_SIZE = 64 + +# Used for in guard bits operations. +LOW_MASK = 0x55555555 + +# Log initialized to all FFs. +ALL_FF_LOG = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + + +def expand_to_log_size(value: int) -> int: + result = 0 + for i in range(0, PIN_LOG_SIZE, 4): + result = result | (value << i * 8) + return result + + +class PinLogBitwise: def __init__(self, norcow): self.norcow = norcow @@ -9,21 +33,21 @@ class PinLog: guard_key = self._generate_guard_key() guard_mask, guard = self.derive_guard_mask_and_value(guard_key) - pin_success_log = (~guard_mask & consts.ALL_FF_LOG) | guard - pin_entry_log = (~guard_mask & consts.ALL_FF_LOG) | guard + pin_success_log = (~guard_mask & ALL_FF_LOG) | guard + pin_entry_log = (~guard_mask & ALL_FF_LOG) | guard self._write_log(guard_key, pin_success_log, pin_entry_log) def derive_guard_mask_and_value(self, guard_key: int) -> (int, int): if guard_key > 0xFFFFFFFF: raise ValueError("Invalid guard key") - guard_mask = ((guard_key & consts.LOW_MASK) << 1) | ( - (~guard_key & 0xFFFFFFFF) & consts.LOW_MASK + guard_mask = ((guard_key & LOW_MASK) << 1) | ( + (~guard_key & 0xFFFFFFFF) & LOW_MASK ) - guard = (((guard_key & consts.LOW_MASK) << 1) & guard_key) | ( - ((~guard_key & 0xFFFFFFFF) & consts.LOW_MASK) & (guard_key >> 1) + guard = (((guard_key & LOW_MASK) << 1) & guard_key) | ( + ((~guard_key & 0xFFFFFFFF) & LOW_MASK) & (guard_key >> 1) ) - return helpers.expand_to_log_size(guard_mask), helpers.expand_to_log_size(guard) + return expand_to_log_size(guard_mask), expand_to_log_size(guard) def write_attempt(self): guard_key, pin_success_log, pin_entry_log = self._get_logs() @@ -32,9 +56,7 @@ class PinLog: clean_pin_entry_log = self.remove_guard_bits(guard_mask, pin_entry_log) clean_pin_entry_log = clean_pin_entry_log >> 2 # set 11 to 00 - pin_entry_log = ( - clean_pin_entry_log & (~guard_mask & consts.ALL_FF_LOG) - ) | guard + pin_entry_log = (clean_pin_entry_log & (~guard_mask & ALL_FF_LOG)) | guard self._write_log(guard_key, pin_success_log, pin_entry_log) @@ -60,15 +82,15 @@ class PinLog: with its neighbour value. Example: 0g0gg1 -> 000011 """ - log = log & (~guard_mask & consts.ALL_FF_LOG) - log = ((log >> 1) | log) & helpers.expand_to_log_size(consts.LOW_MASK) + log = log & (~guard_mask & ALL_FF_LOG) + log = ((log >> 1) | log) & expand_to_log_size(LOW_MASK) log = log | (log << 1) return log def _generate_guard_key(self) -> int: while True: - r = prng.random_uniform(consts.GUARD_KEY_RANDOM_MAX) - r = (r * consts.GUARD_KEY_MODULUS + consts.GUARD_KEY_REMAINDER) & 0xFFFFFFFF + r = prng.random_uniform(GUARD_KEY_RANDOM_MAX) + r = (r * GUARD_KEY_MODULUS + GUARD_KEY_REMAINDER) & 0xFFFFFFFF if self._check_guard_key(r): return r @@ -93,19 +115,18 @@ class PinLog: ((count & 0x0E0E0E0E) == 0x04040404) & (one_runs == 0) & (zero_runs == 0) - & (guard_key % consts.GUARD_KEY_MODULUS == consts.GUARD_KEY_REMAINDER) + & (guard_key % GUARD_KEY_MODULUS == GUARD_KEY_REMAINDER) ) def _get_logs(self) -> (int, int, int): pin_log = self.norcow.get(consts.PIN_LOG_KEY) - guard_key = pin_log[: consts.PIN_LOG_GUARD_KEY_SIZE] + guard_key = pin_log[:PIN_LOG_GUARD_KEY_SIZE] guard_key = helpers.word_to_int(guard_key) guard_mask, guard = self.derive_guard_mask_and_value(guard_key) - pin_entry_log = pin_log[consts.PIN_LOG_GUARD_KEY_SIZE + consts.PIN_LOG_SIZE :] + pin_entry_log = pin_log[PIN_LOG_GUARD_KEY_SIZE + PIN_LOG_SIZE :] pin_entry_log = helpers.to_int_by_words(pin_entry_log) pin_success_log = pin_log[ - consts.PIN_LOG_GUARD_KEY_SIZE : consts.PIN_LOG_GUARD_KEY_SIZE - + consts.PIN_LOG_SIZE + PIN_LOG_GUARD_KEY_SIZE : PIN_LOG_GUARD_KEY_SIZE + PIN_LOG_SIZE ] pin_success_log = helpers.to_int_by_words(pin_success_log) @@ -114,8 +135,8 @@ class PinLog: def _write_log(self, guard_key: int, pin_success_log: int, pin_entry_log: int): pin_log = ( helpers.int_to_word(guard_key) - + helpers.to_bytes_by_words(pin_success_log, consts.PIN_LOG_SIZE) - + helpers.to_bytes_by_words(pin_entry_log, consts.PIN_LOG_SIZE) + + helpers.to_bytes_by_words(pin_success_log, PIN_LOG_SIZE) + + helpers.to_bytes_by_words(pin_entry_log, PIN_LOG_SIZE) ) if self.norcow.is_byte_access(): try: diff --git a/storage/tests/python/src/pin_log_blockwise.py b/storage/tests/python/src/pin_log_blockwise.py new file mode 100644 index 000000000..d646fefbb --- /dev/null +++ b/storage/tests/python/src/pin_log_blockwise.py @@ -0,0 +1,59 @@ +from . import consts + +PIN_LOG_HALFWORDS = int(3 * (consts.WORD_SIZE / 2)) + + +def expand_counter(c: int) -> int: + c = ((c << 4) | c) & 0x0F0F + c = ((c << 2) | c) & 0x3333 + c = ((c << 1) | c) & 0x5555 + c = ((c << 1) | c) ^ 0xAAAA + return c + + +def compress_counter(c: int) -> int: + c = c & 0x5555 + c = ((c >> 1) | c) & 0x3333 + c = ((c >> 2) | c) & 0x0F0F + c = ((c >> 4) | c) & 0x00FF + return c + + +class PinLogBlockwise: + def __init__(self, norcow): + self.norcow = norcow + + def init(self): + self._write_log(0) + + def write_attempt(self): + self._write_log(self.get_failures_count() + 1) + + def write_success(self): + self._write_log(0) + + def get_failures_count(self) -> int: + return self._get_logs() + + def _get_logs(self) -> int: + logs = self.norcow.get(consts.PIN_LOG_KEY) + + if logs is None: + raise ValueError("No PIN logs") + + ctr = int.from_bytes(logs[:2], "little") + + fails = compress_counter(ctr) + + for i in range(2, PIN_LOG_HALFWORDS, 2): + if fails != compress_counter(int.from_bytes(logs[i : i + 2], "little")): + raise ValueError("PIN logs corrupted") + + return fails + + def _write_log(self, fails: int): + ctr = expand_counter(fails) + data = ctr.to_bytes(2, "little") + for _ in range(1, PIN_LOG_HALFWORDS): + data += ctr.to_bytes(2, "little") + self.norcow.set(consts.PIN_LOG_KEY, data) diff --git a/storage/tests/python/src/storage.py b/storage/tests/python/src/storage.py index 661c1a36f..5a0c67aae 100644 --- a/storage/tests/python/src/storage.py +++ b/storage/tests/python/src/storage.py @@ -2,7 +2,6 @@ import hashlib import sys from . import consts, crypto, helpers, prng -from .pin_log import PinLog class Storage: @@ -12,7 +11,7 @@ class Storage: self.dek = None self.sak = None self.nc = norcow_class() - self.pin_log = PinLog(self.nc) + self.pin_log = self.nc.get_pin_log() def init(self, hardware_salt: bytes = b""): """ diff --git a/storage/tests/python/tests/test_helpers.py b/storage/tests/python/tests/test_helpers.py index 4e5a58f71..1c8c1e637 100644 --- a/storage/tests/python/tests/test_helpers.py +++ b/storage/tests/python/tests/test_helpers.py @@ -1,13 +1,14 @@ -from ..src import consts, helpers +from ..src import helpers +from ..src.pin_log_bitwise import PIN_LOG_SIZE def test_read_bytes_by_words(): array = b"\x04\x03\x02\x01\x08\x07\x06\x05" n = helpers.to_int_by_words(array) assert n == 0x0102030405060708 - assert array == helpers.to_bytes_by_words(n, consts.PIN_LOG_SIZE)[56:] + assert array == helpers.to_bytes_by_words(n, PIN_LOG_SIZE)[56:] array = b"\xFF\xFF\xFF\x01\x01\x05\x09\x01" n = helpers.to_int_by_words(array) assert n == 0x01FFFFFF01090501 - assert array == helpers.to_bytes_by_words(n, consts.PIN_LOG_SIZE)[56:] + assert array == helpers.to_bytes_by_words(n, PIN_LOG_SIZE)[56:] diff --git a/storage/tests/python/tests/test_pin_log.py b/storage/tests/python/tests/test_pin_log.py index ffa18ddc3..1943ce7e2 100644 --- a/storage/tests/python/tests/test_pin_log.py +++ b/storage/tests/python/tests/test_pin_log.py @@ -1,10 +1,10 @@ -from ..src import pin_log, prng +from ..src import pin_log_bitwise, prng def test_generate_guard_key(): prng.random_reseed(0) - p = pin_log.PinLog(None) + p = pin_log_bitwise.PinLogBitwise(None) assert p._generate_guard_key() == 2267428717 assert p._generate_guard_key() == 1653399972 diff --git a/storage/tests/tests/test_compact.py b/storage/tests/tests/test_compact.py index 97184eeb4..70afaa73e 100644 --- a/storage/tests/tests/test_compact.py +++ b/storage/tests/tests/test_compact.py @@ -7,7 +7,7 @@ from . import common @pytest.mark.parametrize( - "nc_class,reserve", [(NorcowBlockwise, 1213), (NorcowBitwise, 600)] + "nc_class,reserve", [(NorcowBlockwise, 800), (NorcowBitwise, 600)] ) def test_compact(nc_class, reserve): sc, sp = common.init(nc_class, unlock=True) diff --git a/storage/tests/tests/test_upgrade.py b/storage/tests/tests/test_upgrade.py index 3468f6f37..5a465bf4b 100644 --- a/storage/tests/tests/test_upgrade.py +++ b/storage/tests/tests/test_upgrade.py @@ -2,7 +2,7 @@ import pytest from c0.storage import Storage as StorageC0 from c.storage import Storage as StorageC -from python.src.norcow import NC_CLASSES +from python.src.norcow import NorcowBitwise from python.src.storage import Storage as StoragePy from . import common @@ -60,7 +60,7 @@ def test_upgrade(): check_values(sc1) -@pytest.mark.parametrize("nc_class", NC_CLASSES) +@pytest.mark.parametrize("nc_class", [NorcowBitwise]) def test_python_set_sectors(nc_class): sp0 = StoragePy(nc_class) sp0.init(common.test_uid)