feat(storage): implemented more effective pin logs for blockwise storage

[no changelog]
pull/3522/head
tychovrahe 3 months ago committed by TychoVrahe
parent 919d752598
commit 434ed04b7f

@ -169,13 +169,15 @@ where `⨁` denotes the n-ary bitwise XOR operation and KEY<sub><i>i</i></sub> |
- 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)
```

@ -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;
}

@ -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);
}

@ -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) {

@ -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.

@ -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

@ -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

@ -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:

@ -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)

@ -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""):
"""

@ -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:]

@ -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

@ -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)

@ -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)

Loading…
Cancel
Save