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.
244 lines
6.7 KiB
244 lines
6.7 KiB
from micropython import const
|
|
from ubinascii import hexlify
|
|
|
|
from trezor import config
|
|
from trezor.crypto import random
|
|
|
|
from apps.common import cache
|
|
|
|
HOMESCREEN_MAXSIZE = 16384
|
|
|
|
_STORAGE_VERSION = b"\x02"
|
|
_FALSE_BYTE = b"\x00"
|
|
_TRUE_BYTE = b"\x01"
|
|
_COUNTER_HEAD_LEN = 4
|
|
_COUNTER_TAIL_LEN = 8
|
|
|
|
# fmt: off
|
|
_APP = const(0x01) # app namespace
|
|
_DEVICE_ID = const(0x00) # bytes
|
|
_VERSION = const(0x01) # int
|
|
_MNEMONIC = const(0x02) # str
|
|
_LANGUAGE = const(0x03) # str
|
|
_LABEL = const(0x04) # str
|
|
_USE_PASSPHRASE = const(0x05) # bool (0x01 or empty)
|
|
_HOMESCREEN = const(0x06) # bytes
|
|
_NEEDS_BACKUP = const(0x07) # bool (0x01 or empty)
|
|
_FLAGS = const(0x08) # int
|
|
_U2F_COUNTER = const(0x09) # int
|
|
_PASSPHRASE_SOURCE = const(0x0A) # int
|
|
_UNFINISHED_BACKUP = const(0x0B) # bool (0x01 or empty)
|
|
_AUTOLOCK_DELAY_MS = const(0x0C) # int
|
|
_NO_BACKUP = const(0x0D) # bool (0x01 or empty)
|
|
# fmt: on
|
|
|
|
|
|
def _set_bool(app: int, key: int, value: bool, public: bool = False) -> None:
|
|
if value:
|
|
config.set(app, key, _TRUE_BYTE, public)
|
|
else:
|
|
config.set(app, key, _FALSE_BYTE, public)
|
|
|
|
|
|
def _get_bool(app: int, key: int, public: bool = False) -> bool:
|
|
return config.get(app, key, public) == _TRUE_BYTE
|
|
|
|
|
|
def _set_counter(app: int, key: int, count: int, public: bool = False) -> None:
|
|
if count is None:
|
|
config.delete(app, key, public)
|
|
return
|
|
|
|
value = count.to_bytes(_COUNTER_HEAD_LEN, "big")
|
|
if public:
|
|
value += _COUNTER_TAIL_LEN * b"\xff"
|
|
config.set(app, key, value, public)
|
|
|
|
|
|
def _next_counter(app: int, key: int, public: bool = False) -> int:
|
|
# If the counter value is public, then it is stored as a four byte integer in
|
|
# big endian byte order, called the "head", followed an eight byte "tail". The
|
|
# counter value is equal to the integer value of the head plus the number of
|
|
# zero bits in the tail. The counter value 0 is stored as 00000000FFFFFFFFFFFFFFFF.
|
|
# With each increment the tail is shifted to the right by one bit. Thus after
|
|
# three increments the stored value is 000000001FFFFFFFFFFFFFFF. Once all the
|
|
# bits in the tail are set to zero, the next counter value is stored as
|
|
# 00000021FFFFFFFFFFFFFFFF.
|
|
|
|
value = config.get(app, key, public)
|
|
if value is None:
|
|
_set_counter(app, key, 0, public)
|
|
return 0
|
|
|
|
head = value[:_COUNTER_HEAD_LEN]
|
|
tail = value[_COUNTER_HEAD_LEN:]
|
|
i = tail.rfind(b"\x00") + 1
|
|
count = int.from_bytes(head, "big") + 1 + 8 * i
|
|
if i == len(tail):
|
|
_set_counter(app, key, count, public)
|
|
return count
|
|
|
|
zero_count = 0
|
|
while (tail[i] << zero_count) < 128:
|
|
zero_count += 1
|
|
count += zero_count
|
|
|
|
tail = tail[:i] + bytes([tail[i] >> 1]) + tail[i + 1 :]
|
|
config.set(app, key, head + tail, public)
|
|
return count
|
|
|
|
|
|
def _new_device_id() -> str:
|
|
return hexlify(random.bytes(12)).decode().upper()
|
|
|
|
|
|
def get_device_id() -> str:
|
|
dev_id = config.get(_APP, _DEVICE_ID, True) # public
|
|
if not dev_id:
|
|
dev_id = _new_device_id().encode()
|
|
config.set(_APP, _DEVICE_ID, dev_id, True) # public
|
|
return dev_id.decode()
|
|
|
|
|
|
def is_initialized() -> bool:
|
|
return bool(config.get(_APP, _VERSION))
|
|
|
|
|
|
def get_label() -> str:
|
|
label = config.get(_APP, _LABEL, True) # public
|
|
if label is None:
|
|
return None
|
|
return label.decode()
|
|
|
|
|
|
def get_mnemonic() -> str:
|
|
mnemonic = config.get(_APP, _MNEMONIC)
|
|
if mnemonic is None:
|
|
return None
|
|
return mnemonic.decode()
|
|
|
|
|
|
def has_passphrase() -> bool:
|
|
return _get_bool(_APP, _USE_PASSPHRASE)
|
|
|
|
|
|
def get_homescreen() -> bytes:
|
|
return config.get(_APP, _HOMESCREEN, True) # public
|
|
|
|
|
|
def load_mnemonic(mnemonic: str, needs_backup: bool, no_backup: bool) -> None:
|
|
config.set(_APP, _MNEMONIC, mnemonic.encode())
|
|
config.set(_APP, _VERSION, _STORAGE_VERSION)
|
|
_set_bool(_APP, _NO_BACKUP, no_backup)
|
|
if not no_backup:
|
|
_set_bool(_APP, _NEEDS_BACKUP, needs_backup)
|
|
|
|
|
|
def needs_backup() -> bool:
|
|
return _get_bool(_APP, _NEEDS_BACKUP)
|
|
|
|
|
|
def set_backed_up() -> None:
|
|
config.set(_APP, _NEEDS_BACKUP, b"")
|
|
|
|
|
|
def unfinished_backup() -> bool:
|
|
return _get_bool(_APP, _UNFINISHED_BACKUP)
|
|
|
|
|
|
def set_unfinished_backup(state: bool) -> None:
|
|
_set_bool(_APP, _UNFINISHED_BACKUP, state)
|
|
|
|
|
|
def no_backup() -> bool:
|
|
return _get_bool(_APP, _NO_BACKUP)
|
|
|
|
|
|
def get_passphrase_source() -> int:
|
|
b = config.get(_APP, _PASSPHRASE_SOURCE)
|
|
if b == b"\x01":
|
|
return 1
|
|
elif b == b"\x02":
|
|
return 2
|
|
else:
|
|
return 0
|
|
|
|
|
|
def load_settings(
|
|
label: str = None,
|
|
use_passphrase: bool = None,
|
|
homescreen: bytes = None,
|
|
passphrase_source: int = None,
|
|
) -> None:
|
|
if label is not None:
|
|
config.set(_APP, _LABEL, label.encode(), True) # public
|
|
if use_passphrase is not None:
|
|
_set_bool(_APP, _USE_PASSPHRASE, use_passphrase)
|
|
if homescreen is not None:
|
|
if homescreen[:8] == b"TOIf\x90\x00\x90\x00":
|
|
if len(homescreen) <= HOMESCREEN_MAXSIZE:
|
|
config.set(_APP, _HOMESCREEN, homescreen, True) # public
|
|
else:
|
|
config.set(_APP, _HOMESCREEN, b"", True) # public
|
|
if passphrase_source is not None:
|
|
if passphrase_source in [0, 1, 2]:
|
|
config.set(_APP, _PASSPHRASE_SOURCE, bytes([passphrase_source]))
|
|
|
|
|
|
def get_flags() -> int:
|
|
b = config.get(_APP, _FLAGS)
|
|
if b is None:
|
|
return 0
|
|
else:
|
|
return int.from_bytes(b, "big")
|
|
|
|
|
|
def set_flags(flags: int) -> None:
|
|
b = config.get(_APP, _FLAGS)
|
|
if b is None:
|
|
b = 0
|
|
else:
|
|
b = int.from_bytes(b, "big")
|
|
flags = (flags | b) & 0xFFFFFFFF
|
|
if flags != b:
|
|
config.set(_APP, _FLAGS, flags.to_bytes(4, "big"))
|
|
|
|
|
|
def get_autolock_delay_ms() -> int:
|
|
b = config.get(_APP, _AUTOLOCK_DELAY_MS)
|
|
if b is None:
|
|
return 10 * 60 * 1000
|
|
else:
|
|
return int.from_bytes(b, "big")
|
|
|
|
|
|
def set_autolock_delay_ms(delay_ms: int) -> None:
|
|
if delay_ms < 60 * 1000:
|
|
delay_ms = 60 * 1000
|
|
config.set(_APP, _AUTOLOCK_DELAY_MS, delay_ms.to_bytes(4, "big"))
|
|
|
|
|
|
def next_u2f_counter() -> int:
|
|
return _next_counter(_APP, _U2F_COUNTER, True) # public
|
|
|
|
|
|
def set_u2f_counter(cntr: int) -> None:
|
|
_set_counter(_APP, _U2F_COUNTER, cntr, True) # public
|
|
|
|
|
|
def wipe():
|
|
config.wipe()
|
|
cache.clear()
|
|
|
|
|
|
def init_unlocked():
|
|
# Check for storage version upgrade.
|
|
version = config.get(_APP, _VERSION)
|
|
if version == b"\x01":
|
|
# Make the U2F counter public.
|
|
counter = config.get(_APP, _U2F_COUNTER)
|
|
if counter is not None:
|
|
_set_counter(_APP, _U2F_COUNTER, counter, True) # public
|
|
config.delete(_APP, _U2F_COUNTER)
|
|
config.set(_APP, _VERSION, _STORAGE_VERSION)
|