mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 15:38:11 +00:00
feat(storage): implemented more effective pin logs for blockwise storage
[no changelog]
This commit is contained in:
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.
|
- 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.
|
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.
|
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 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.
|
- 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.
|
- 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.
|
- 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.
|
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.
|
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):
|
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.
|
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:
|
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:
|
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.
|
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):
|
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))
|
(((~guard_key) & LOW_MASK) & (guard_key >> 1))
|
||||||
```
|
```
|
||||||
|
|
||||||
### Log initialization
|
#### Log initialization
|
||||||
|
|
||||||
Each log is stored as 16 consecutive words each initialized to:
|
Each log is stored as 16 consecutive words each initialized to:
|
||||||
|
|
||||||
`guard | ~guard_mask`
|
`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:
|
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`
|
`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:
|
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 >> 8)
|
||||||
count = (count + (count >> 16)) & 0x3F
|
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)
|
||||||
|
```
|
||||||
|
267
storage/pinlogs_bitwise.h
Normal file
267
storage/pinlogs_bitwise.h
Normal file
@ -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;
|
||||||
|
}
|
87
storage/pinlogs_blockwise.h
Normal file
87
storage/pinlogs_blockwise.h
Normal file
@ -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"
|
#include "optiga.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define LOW_MASK 0x55555555
|
|
||||||
|
|
||||||
// The APP namespace which is reserved for storage related values.
|
// The APP namespace which is reserved for storage related values.
|
||||||
#define APP_STORAGE 0x00
|
#define APP_STORAGE 0x00
|
||||||
|
|
||||||
@ -96,15 +94,6 @@ const uint32_t V0_PIN_EMPTY = 1;
|
|||||||
#define PIN_DERIVE_MS PIN_PBKDF2_MS
|
#define PIN_DERIVE_MS PIN_PBKDF2_MS
|
||||||
#endif
|
#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.
|
// The length of the hashed hardware salt in bytes.
|
||||||
#define HARDWARE_SALT_SIZE SHA256_DIGEST_LENGTH
|
#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.
|
// The uint32 representation of an empty wipe code used in storage version 2.
|
||||||
#define V2_WIPE_CODE_EMPTY 0
|
#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
|
// TODO: handle translation
|
||||||
const char *const VERIFYING_PIN_MSG = "Verifying PIN";
|
const char *const VERIFYING_PIN_MSG = "Verifying PIN";
|
||||||
const char *const PROCESSING_MSG = "Processing";
|
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,
|
static secbool storage_get_encrypted(const uint16_t key, void *val_dest,
|
||||||
const uint16_t max_len, uint16_t *len);
|
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) {
|
static secbool secequal(const void *ptr1, const void *ptr2, size_t n) {
|
||||||
const uint8_t *p1 = ptr1;
|
const uint8_t *p1 = ptr1;
|
||||||
const uint8_t *p2 = ptr2;
|
const uint8_t *p2 = ptr2;
|
||||||
@ -669,91 +661,6 @@ static secbool set_pin(const uint8_t *pin, size_t pin_len,
|
|||||||
return ret;
|
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
|
* 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
|
* 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));
|
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) {
|
secbool storage_pin_fails_increase(void) {
|
||||||
if (sectrue != initialized) {
|
if (sectrue != initialized) {
|
||||||
return secfalse;
|
return secfalse;
|
||||||
}
|
}
|
||||||
|
return pin_fails_increase();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
secbool storage_is_unlocked(void) {
|
secbool storage_is_unlocked(void) {
|
||||||
|
@ -78,23 +78,6 @@ COUNTER_MAX_TAIL = 64
|
|||||||
# Storage key of the PIN entry log and PIN success log.
|
# Storage key of the PIN entry log and PIN success log.
|
||||||
PIN_LOG_KEY = (PIN_APP_ID << 8) | 0x01
|
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 -----
|
# ----- Bytes -----
|
||||||
|
|
||||||
# If the top bit of APP is set, then the value is not encrypted.
|
# If the top bit of APP is set, then the value is not encrypted.
|
||||||
|
@ -3,13 +3,6 @@ import sys
|
|||||||
from . import consts
|
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:
|
def to_int_by_words(array: bytes) -> int:
|
||||||
"""
|
"""
|
||||||
Converts array of bytes into an int by reading word size
|
Converts array of bytes into an int by reading word size
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
from struct import pack, unpack
|
from struct import pack, unpack
|
||||||
|
|
||||||
from . import consts
|
from . import consts
|
||||||
|
from .pin_log_bitwise import PinLogBitwise
|
||||||
|
from .pin_log_blockwise import PinLogBlockwise
|
||||||
|
|
||||||
|
|
||||||
def align_int(i: int, align: int):
|
def align_int(i: int, align: int):
|
||||||
@ -174,6 +176,9 @@ class NorcowBitwise(Norcow):
|
|||||||
self.item_prefix_len = 4
|
self.item_prefix_len = 4
|
||||||
self.lib_name = "libtrezor-storage.so"
|
self.lib_name = "libtrezor-storage.so"
|
||||||
|
|
||||||
|
def get_pin_log(self):
|
||||||
|
return PinLogBitwise(self)
|
||||||
|
|
||||||
def get_lib_name(self):
|
def get_lib_name(self):
|
||||||
return self.lib_name
|
return self.lib_name
|
||||||
|
|
||||||
@ -231,6 +236,9 @@ class NorcowBlockwise(Norcow):
|
|||||||
self.item_prefix_len = 4 * consts.WORD_SIZE + 1
|
self.item_prefix_len = 4 * consts.WORD_SIZE + 1
|
||||||
self.lib_name = "libtrezor-storage-qw.so"
|
self.lib_name = "libtrezor-storage-qw.so"
|
||||||
|
|
||||||
|
def get_pin_log(self):
|
||||||
|
return PinLogBlockwise(self)
|
||||||
|
|
||||||
def get_lib_name(self):
|
def get_lib_name(self):
|
||||||
return self.lib_name
|
return self.lib_name
|
||||||
|
|
||||||
|
@ -1,7 +1,31 @@
|
|||||||
from . import consts, helpers, prng
|
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):
|
def __init__(self, norcow):
|
||||||
self.norcow = norcow
|
self.norcow = norcow
|
||||||
|
|
||||||
@ -9,21 +33,21 @@ class PinLog:
|
|||||||
guard_key = self._generate_guard_key()
|
guard_key = self._generate_guard_key()
|
||||||
guard_mask, guard = self.derive_guard_mask_and_value(guard_key)
|
guard_mask, guard = self.derive_guard_mask_and_value(guard_key)
|
||||||
|
|
||||||
pin_success_log = (~guard_mask & consts.ALL_FF_LOG) | guard
|
pin_success_log = (~guard_mask & ALL_FF_LOG) | guard
|
||||||
pin_entry_log = (~guard_mask & consts.ALL_FF_LOG) | guard
|
pin_entry_log = (~guard_mask & ALL_FF_LOG) | guard
|
||||||
self._write_log(guard_key, pin_success_log, pin_entry_log)
|
self._write_log(guard_key, pin_success_log, pin_entry_log)
|
||||||
|
|
||||||
def derive_guard_mask_and_value(self, guard_key: int) -> (int, int):
|
def derive_guard_mask_and_value(self, guard_key: int) -> (int, int):
|
||||||
if guard_key > 0xFFFFFFFF:
|
if guard_key > 0xFFFFFFFF:
|
||||||
raise ValueError("Invalid guard key")
|
raise ValueError("Invalid guard key")
|
||||||
|
|
||||||
guard_mask = ((guard_key & consts.LOW_MASK) << 1) | (
|
guard_mask = ((guard_key & LOW_MASK) << 1) | (
|
||||||
(~guard_key & 0xFFFFFFFF) & consts.LOW_MASK
|
(~guard_key & 0xFFFFFFFF) & LOW_MASK
|
||||||
)
|
)
|
||||||
guard = (((guard_key & consts.LOW_MASK) << 1) & guard_key) | (
|
guard = (((guard_key & LOW_MASK) << 1) & guard_key) | (
|
||||||
((~guard_key & 0xFFFFFFFF) & consts.LOW_MASK) & (guard_key >> 1)
|
((~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):
|
def write_attempt(self):
|
||||||
guard_key, pin_success_log, pin_entry_log = self._get_logs()
|
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 = self.remove_guard_bits(guard_mask, pin_entry_log)
|
||||||
clean_pin_entry_log = clean_pin_entry_log >> 2 # set 11 to 00
|
clean_pin_entry_log = clean_pin_entry_log >> 2 # set 11 to 00
|
||||||
pin_entry_log = (
|
pin_entry_log = (clean_pin_entry_log & (~guard_mask & ALL_FF_LOG)) | guard
|
||||||
clean_pin_entry_log & (~guard_mask & consts.ALL_FF_LOG)
|
|
||||||
) | guard
|
|
||||||
|
|
||||||
self._write_log(guard_key, pin_success_log, pin_entry_log)
|
self._write_log(guard_key, pin_success_log, pin_entry_log)
|
||||||
|
|
||||||
@ -60,15 +82,15 @@ class PinLog:
|
|||||||
with its neighbour value.
|
with its neighbour value.
|
||||||
Example: 0g0gg1 -> 000011
|
Example: 0g0gg1 -> 000011
|
||||||
"""
|
"""
|
||||||
log = log & (~guard_mask & consts.ALL_FF_LOG)
|
log = log & (~guard_mask & ALL_FF_LOG)
|
||||||
log = ((log >> 1) | log) & helpers.expand_to_log_size(consts.LOW_MASK)
|
log = ((log >> 1) | log) & expand_to_log_size(LOW_MASK)
|
||||||
log = log | (log << 1)
|
log = log | (log << 1)
|
||||||
return log
|
return log
|
||||||
|
|
||||||
def _generate_guard_key(self) -> int:
|
def _generate_guard_key(self) -> int:
|
||||||
while True:
|
while True:
|
||||||
r = prng.random_uniform(consts.GUARD_KEY_RANDOM_MAX)
|
r = prng.random_uniform(GUARD_KEY_RANDOM_MAX)
|
||||||
r = (r * consts.GUARD_KEY_MODULUS + consts.GUARD_KEY_REMAINDER) & 0xFFFFFFFF
|
r = (r * GUARD_KEY_MODULUS + GUARD_KEY_REMAINDER) & 0xFFFFFFFF
|
||||||
if self._check_guard_key(r):
|
if self._check_guard_key(r):
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@ -93,19 +115,18 @@ class PinLog:
|
|||||||
((count & 0x0E0E0E0E) == 0x04040404)
|
((count & 0x0E0E0E0E) == 0x04040404)
|
||||||
& (one_runs == 0)
|
& (one_runs == 0)
|
||||||
& (zero_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):
|
def _get_logs(self) -> (int, int, int):
|
||||||
pin_log = self.norcow.get(consts.PIN_LOG_KEY)
|
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_key = helpers.word_to_int(guard_key)
|
||||||
guard_mask, guard = self.derive_guard_mask_and_value(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_entry_log = helpers.to_int_by_words(pin_entry_log)
|
||||||
pin_success_log = pin_log[
|
pin_success_log = pin_log[
|
||||||
consts.PIN_LOG_GUARD_KEY_SIZE : consts.PIN_LOG_GUARD_KEY_SIZE
|
PIN_LOG_GUARD_KEY_SIZE : PIN_LOG_GUARD_KEY_SIZE + PIN_LOG_SIZE
|
||||||
+ consts.PIN_LOG_SIZE
|
|
||||||
]
|
]
|
||||||
pin_success_log = helpers.to_int_by_words(pin_success_log)
|
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):
|
def _write_log(self, guard_key: int, pin_success_log: int, pin_entry_log: int):
|
||||||
pin_log = (
|
pin_log = (
|
||||||
helpers.int_to_word(guard_key)
|
helpers.int_to_word(guard_key)
|
||||||
+ helpers.to_bytes_by_words(pin_success_log, consts.PIN_LOG_SIZE)
|
+ helpers.to_bytes_by_words(pin_success_log, PIN_LOG_SIZE)
|
||||||
+ helpers.to_bytes_by_words(pin_entry_log, consts.PIN_LOG_SIZE)
|
+ helpers.to_bytes_by_words(pin_entry_log, PIN_LOG_SIZE)
|
||||||
)
|
)
|
||||||
if self.norcow.is_byte_access():
|
if self.norcow.is_byte_access():
|
||||||
try:
|
try:
|
59
storage/tests/python/src/pin_log_blockwise.py
Normal file
59
storage/tests/python/src/pin_log_blockwise.py
Normal file
@ -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
|
import sys
|
||||||
|
|
||||||
from . import consts, crypto, helpers, prng
|
from . import consts, crypto, helpers, prng
|
||||||
from .pin_log import PinLog
|
|
||||||
|
|
||||||
|
|
||||||
class Storage:
|
class Storage:
|
||||||
@ -12,7 +11,7 @@ class Storage:
|
|||||||
self.dek = None
|
self.dek = None
|
||||||
self.sak = None
|
self.sak = None
|
||||||
self.nc = norcow_class()
|
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""):
|
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():
|
def test_read_bytes_by_words():
|
||||||
array = b"\x04\x03\x02\x01\x08\x07\x06\x05"
|
array = b"\x04\x03\x02\x01\x08\x07\x06\x05"
|
||||||
n = helpers.to_int_by_words(array)
|
n = helpers.to_int_by_words(array)
|
||||||
assert n == 0x0102030405060708
|
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"
|
array = b"\xFF\xFF\xFF\x01\x01\x05\x09\x01"
|
||||||
n = helpers.to_int_by_words(array)
|
n = helpers.to_int_by_words(array)
|
||||||
assert n == 0x01FFFFFF01090501
|
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():
|
def test_generate_guard_key():
|
||||||
prng.random_reseed(0)
|
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() == 2267428717
|
||||||
assert p._generate_guard_key() == 1653399972
|
assert p._generate_guard_key() == 1653399972
|
||||||
|
@ -7,7 +7,7 @@ from . import common
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"nc_class,reserve", [(NorcowBlockwise, 1213), (NorcowBitwise, 600)]
|
"nc_class,reserve", [(NorcowBlockwise, 800), (NorcowBitwise, 600)]
|
||||||
)
|
)
|
||||||
def test_compact(nc_class, reserve):
|
def test_compact(nc_class, reserve):
|
||||||
sc, sp = common.init(nc_class, unlock=True)
|
sc, sp = common.init(nc_class, unlock=True)
|
||||||
|
@ -2,7 +2,7 @@ import pytest
|
|||||||
from c0.storage import Storage as StorageC0
|
from c0.storage import Storage as StorageC0
|
||||||
from c.storage import Storage as StorageC
|
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 python.src.storage import Storage as StoragePy
|
||||||
|
|
||||||
from . import common
|
from . import common
|
||||||
@ -60,7 +60,7 @@ def test_upgrade():
|
|||||||
check_values(sc1)
|
check_values(sc1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("nc_class", NC_CLASSES)
|
@pytest.mark.parametrize("nc_class", [NorcowBitwise])
|
||||||
def test_python_set_sectors(nc_class):
|
def test_python_set_sectors(nc_class):
|
||||||
sp0 = StoragePy(nc_class)
|
sp0 = StoragePy(nc_class)
|
||||||
sp0.init(common.test_uid)
|
sp0.init(common.test_uid)
|
||||||
|
Loading…
Reference in New Issue
Block a user