You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/storage/tests/python/src/pin_log.py

124 lines
4.9 KiB

from . import consts, helpers, prng
class PinLog:
def __init__(self, norcow):
self.norcow = norcow
def init(self):
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
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 = (((guard_key & consts.LOW_MASK) << 1) & guard_key) | (
((~guard_key & 0xFFFFFFFF) & consts.LOW_MASK) & (guard_key >> 1)
)
return helpers.expand_to_log_size(guard_mask), helpers.expand_to_log_size(guard)
def write_attempt(self):
guard_key, pin_success_log, pin_entry_log = self._get_logs()
guard_mask, guard = self.derive_guard_mask_and_value(guard_key)
assert (pin_entry_log & guard_mask) == guard
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
self._write_log(guard_key, pin_success_log, pin_entry_log)
def write_success(self):
guard_key, pin_success_log, pin_entry_log = self._get_logs()
pin_success_log = pin_entry_log
self._write_log(guard_key, pin_success_log, pin_entry_log)
def get_failures_count(self) -> int:
guard_key, pin_succes_log, pin_entry_log = self._get_logs()
guard_mask, _ = self.derive_guard_mask_and_value(guard_key)
pin_succes_log = self.remove_guard_bits(guard_mask, pin_succes_log)
pin_entry_log = self.remove_guard_bits(guard_mask, pin_entry_log)
# divide by two because bits are doubled after remove_guard_bits()
return bin(pin_succes_log - pin_entry_log).count("1") // 2
def remove_guard_bits(self, guard_mask: int, log: int) -> int:
"""
Removes all guard bits and replaces each guard bit
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 | (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
if self._check_guard_key(r):
return r
def _check_guard_key(self, guard_key: int) -> bool:
"""
Checks if guard_key is congruent to 15 modulo 6311 and
some other conditions, see the docs.
"""
count = (guard_key & 0x22222222) + ((guard_key >> 2) & 0x22222222)
count = count + (count >> 4)
zero_runs = ~guard_key & 0xFFFFFFFF
zero_runs = zero_runs & (zero_runs >> 2)
zero_runs = zero_runs & (zero_runs >> 1)
zero_runs = zero_runs & (zero_runs >> 1)
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)
return (
((count & 0x0E0E0E0E) == 0x04040404)
& (one_runs == 0)
& (zero_runs == 0)
& (guard_key % consts.GUARD_KEY_MODULUS == consts.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 = 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 = 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_success_log = helpers.to_int_by_words(pin_success_log)
return guard_key, pin_success_log, pin_entry_log
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)
)
try:
self.norcow.replace(consts.PIN_LOG_KEY, pin_log)
except RuntimeError:
self.norcow.set(consts.PIN_LOG_KEY, pin_log)