mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-18 04:18:10 +00:00
test(storage): Update tests to comply with new storage version and interface.
This commit is contained in:
parent
66823e2893
commit
8f5f5daaab
@ -21,10 +21,10 @@ class Storage:
|
||||
def wipe(self) -> None:
|
||||
self.lib.storage_wipe()
|
||||
|
||||
def unlock(self, pin: int, ext_salt: bytes = None) -> bool:
|
||||
def unlock(self, pin: str, ext_salt: bytes = None) -> bool:
|
||||
if ext_salt is not None and len(ext_salt) != EXTERNAL_SALT_LEN:
|
||||
raise ValueError
|
||||
return sectrue == self.lib.storage_unlock(c.c_uint32(pin), ext_salt)
|
||||
return sectrue == self.lib.storage_unlock(pin.encode(), len(pin), ext_salt)
|
||||
|
||||
def lock(self) -> None:
|
||||
self.lib.storage_lock()
|
||||
@ -37,8 +37,8 @@ class Storage:
|
||||
|
||||
def change_pin(
|
||||
self,
|
||||
oldpin: int,
|
||||
newpin: int,
|
||||
oldpin: str,
|
||||
newpin: str,
|
||||
old_ext_salt: bytes = None,
|
||||
new_ext_salt: bytes = None,
|
||||
) -> bool:
|
||||
@ -47,7 +47,12 @@ class Storage:
|
||||
if new_ext_salt is not None and len(new_ext_salt) != EXTERNAL_SALT_LEN:
|
||||
raise ValueError
|
||||
return sectrue == self.lib.storage_change_pin(
|
||||
c.c_uint32(oldpin), c.c_uint32(newpin), old_ext_salt, new_ext_salt
|
||||
oldpin.encode(),
|
||||
len(oldpin),
|
||||
newpin.encode(),
|
||||
len(newpin),
|
||||
old_ext_salt,
|
||||
new_ext_salt,
|
||||
)
|
||||
|
||||
def get(self, key: int) -> bytes:
|
||||
|
Binary file not shown.
@ -20,18 +20,18 @@ class Storage:
|
||||
def wipe(self) -> None:
|
||||
self.lib.storage_wipe()
|
||||
|
||||
def check_pin(self, pin: int) -> bool:
|
||||
return sectrue == self.lib.storage_check_pin(c.c_uint32(pin))
|
||||
def check_pin(self, pin: str) -> bool:
|
||||
return sectrue == self.lib.storage_check_pin(c.c_uint32(int("1" + pin)))
|
||||
|
||||
def unlock(self, pin: int) -> bool:
|
||||
return sectrue == self.lib.storage_unlock(c.c_uint32(pin))
|
||||
def unlock(self, pin: str) -> bool:
|
||||
return sectrue == self.lib.storage_unlock(c.c_uint32(int("1" + pin)))
|
||||
|
||||
def has_pin(self) -> bool:
|
||||
return sectrue == self.lib.storage_has_pin()
|
||||
|
||||
def change_pin(self, oldpin: int, newpin: int) -> bool:
|
||||
def change_pin(self, oldpin: str, newpin: str) -> bool:
|
||||
return sectrue == self.lib.storage_change_pin(
|
||||
c.c_uint32(oldpin), c.c_uint32(newpin)
|
||||
c.c_uint32(int("1" + oldpin)), c.c_uint32(int("1" + newpin))
|
||||
)
|
||||
|
||||
def get(self, key: int) -> bytes:
|
||||
|
@ -21,11 +21,11 @@ WIPE_CODE_DATA_KEY = (PIN_APP_ID << 8) | 0x06
|
||||
# Norcow storage key of the storage upgrade flag.
|
||||
STORAGE_UPGRADED_KEY = (PIN_APP_ID << 8) | 0x07
|
||||
|
||||
# The PIN value corresponding to an invalid PIN.
|
||||
PIN_INVALID = 0
|
||||
# Norcow storage key of the unauthenticated storage version.
|
||||
UNAUTH_VERSION_KEY = (PIN_APP_ID << 8) | 0x08
|
||||
|
||||
# The PIN value corresponding to an empty PIN.
|
||||
PIN_EMPTY = 1
|
||||
PIN_EMPTY = ""
|
||||
|
||||
# Maximum number of failed unlock attempts.
|
||||
PIN_MAX_TRIES = 16
|
||||
@ -64,7 +64,7 @@ WIPE_CODE_TAG_SIZE = 8
|
||||
# The value corresponding to an unconfigured wipe code.
|
||||
# NOTE: This is intentionally different from PIN_EMPTY so that we don't need
|
||||
# special handling when both the PIN and wipe code are not set.
|
||||
WIPE_CODE_EMPTY = 0
|
||||
WIPE_CODE_EMPTY = "\0\0\0\0"
|
||||
|
||||
# Size of counter. 4B integer and 8B tail.
|
||||
COUNTER_TAIL = 12
|
||||
@ -128,7 +128,7 @@ NORCOW_SECTOR_SIZE = 64 * 1024
|
||||
NORCOW_MAGIC = b"NRC2"
|
||||
|
||||
# Norcow version, set in the storage header, but also as an encrypted item.
|
||||
NORCOW_VERSION = b"\x02\x00\x00\x00"
|
||||
NORCOW_VERSION = b"\x03\x00\x00\x00"
|
||||
|
||||
# Norcow magic combined with the version, which is stored as its negation.
|
||||
NORCOW_MAGIC_AND_VERSION = NORCOW_MAGIC + bytes(
|
||||
|
@ -7,7 +7,7 @@ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from . import consts, prng
|
||||
|
||||
|
||||
def derive_kek_keiv(salt: bytes, pin: int) -> (bytes, bytes):
|
||||
def derive_kek_keiv(salt: bytes, pin: str) -> (bytes, bytes):
|
||||
kdf = PBKDF2HMAC(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=consts.KEK_SIZE + consts.KEIV_SIZE,
|
||||
@ -15,7 +15,7 @@ def derive_kek_keiv(salt: bytes, pin: int) -> (bytes, bytes):
|
||||
iterations=10000,
|
||||
backend=default_backend(),
|
||||
)
|
||||
pbkdf_output = kdf.derive(pin.to_bytes(4, "little"))
|
||||
pbkdf_output = kdf.derive(pin.encode())
|
||||
# the first 256b is Key Encryption Key
|
||||
kek = pbkdf_output[: consts.KEK_SIZE]
|
||||
# following with 96b of Initialization Vector
|
||||
@ -42,7 +42,7 @@ def chacha_poly_decrypt(
|
||||
|
||||
|
||||
def decrypt_edek_esak(
|
||||
pin: int, salt: bytes, edek_esak: bytes, pvc: bytes
|
||||
pin: str, salt: bytes, edek_esak: bytes, pvc: bytes
|
||||
) -> (bytes, bytes):
|
||||
"""
|
||||
Decrypts EDEK, ESAK to DEK, SAK and checks PIN in the process.
|
||||
|
@ -38,14 +38,15 @@ class Storage:
|
||||
self.sak = prng.random_buffer(consts.SAK_SIZE)
|
||||
|
||||
self.nc.set(consts.SAT_KEY, crypto.init_hmacs(self.sak))
|
||||
self._set_encrypt(consts.VERSION_KEY, b"\x02\x00\x00\x00")
|
||||
self._set_encrypt(consts.VERSION_KEY, consts.NORCOW_VERSION)
|
||||
self.nc.set(consts.UNAUTH_VERSION_KEY, consts.NORCOW_VERSION)
|
||||
self.nc.set(consts.STORAGE_UPGRADED_KEY, consts.FALSE_WORD)
|
||||
self.pin_log.init()
|
||||
self._set_wipe_code(consts.WIPE_CODE_EMPTY)
|
||||
self._set_pin(consts.PIN_EMPTY)
|
||||
self.unlocked = False
|
||||
|
||||
def _set_pin(self, pin: int):
|
||||
def _set_pin(self, pin: str):
|
||||
random_salt = prng.random_buffer(consts.PIN_SALT_SIZE)
|
||||
salt = self.hw_salt_hash + random_salt
|
||||
kek, keiv = crypto.derive_kek_keiv(salt, pin)
|
||||
@ -61,10 +62,10 @@ class Storage:
|
||||
else:
|
||||
self._set_bool(consts.PIN_NOT_SET_KEY, False)
|
||||
|
||||
def _set_wipe_code(self, wipe_code: int):
|
||||
def _set_wipe_code(self, wipe_code: str):
|
||||
if wipe_code == consts.PIN_EMPTY:
|
||||
wipe_code = consts.WIPE_CODE_EMPTY
|
||||
wipe_code_bytes = wipe_code.to_bytes(4, "little")
|
||||
wipe_code_bytes = wipe_code.encode()
|
||||
salt = prng.random_buffer(consts.WIPE_CODE_SALT_SIZE)
|
||||
tag = crypto._hmac(salt, wipe_code_bytes)[: consts.WIPE_CODE_TAG_SIZE]
|
||||
self.nc.set(consts.WIPE_CODE_DATA_KEY, wipe_code_bytes + salt + tag)
|
||||
@ -73,10 +74,7 @@ class Storage:
|
||||
self.nc.wipe()
|
||||
self._init_pin()
|
||||
|
||||
def check_pin(self, pin: int) -> bool:
|
||||
if pin == 0:
|
||||
return False
|
||||
|
||||
def check_pin(self, pin: str) -> bool:
|
||||
self.pin_log.write_attempt()
|
||||
|
||||
data = self.nc.get(consts.EDEK_ESEK_PVC_KEY)
|
||||
@ -99,7 +97,7 @@ class Storage:
|
||||
def lock(self) -> None:
|
||||
self.unlocked = False
|
||||
|
||||
def unlock(self, pin: int) -> bool:
|
||||
def unlock(self, pin: str) -> bool:
|
||||
if not self.initialized or not self.check_pin(pin):
|
||||
return False
|
||||
|
||||
@ -117,13 +115,8 @@ class Storage:
|
||||
def get_pin_rem(self) -> int:
|
||||
return consts.PIN_MAX_TRIES - self.pin_log.get_failures_count()
|
||||
|
||||
def change_pin(self, oldpin: int, newpin: int) -> bool:
|
||||
if (
|
||||
not self.initialized
|
||||
or not self.unlocked
|
||||
or oldpin == consts.PIN_INVALID
|
||||
or newpin == consts.PIN_INVALID
|
||||
):
|
||||
def change_pin(self, oldpin: str, newpin: str) -> bool:
|
||||
if not self.initialized or not self.unlocked:
|
||||
return False
|
||||
if not self.check_pin(oldpin):
|
||||
return False
|
||||
|
@ -5,24 +5,24 @@ def test_set_pin_success():
|
||||
s = Storage()
|
||||
hw_salt = b"\x00\x00\x00\x00\x00\x00"
|
||||
s.init(hw_salt)
|
||||
s._set_pin(1)
|
||||
assert s.unlock(1)
|
||||
s._set_pin("")
|
||||
assert s.unlock("")
|
||||
|
||||
s = Storage()
|
||||
s.init(hw_salt)
|
||||
s._set_pin(229922)
|
||||
assert s.unlock(229922)
|
||||
s._set_pin("229922")
|
||||
assert s.unlock("229922")
|
||||
|
||||
|
||||
def test_set_pin_failure():
|
||||
s = Storage()
|
||||
hw_salt = b"\x00\x00\x00\x00\x00\x00"
|
||||
s.init(hw_salt)
|
||||
s._set_pin(1)
|
||||
assert s.unlock(1)
|
||||
assert not s.unlock(1234)
|
||||
s._set_pin("")
|
||||
assert s.unlock("")
|
||||
assert not s.unlock("1234")
|
||||
|
||||
s = Storage()
|
||||
s.init(hw_salt)
|
||||
s._set_pin(229922)
|
||||
assert not s.unlock(1122992211)
|
||||
s._set_pin("229922")
|
||||
assert not s.unlock("1122992211")
|
||||
|
@ -28,8 +28,8 @@ a = []
|
||||
for s in [sc, sp]:
|
||||
print(s.__class__)
|
||||
s.init(uid)
|
||||
assert s.unlock(3) is False
|
||||
assert s.unlock(1) is True
|
||||
assert s.unlock("3") is False
|
||||
assert s.unlock("") is True
|
||||
s.set(0xBEEF, b"hello")
|
||||
s.set(0x03FE, b"world!")
|
||||
s.set(0xBEEF, b"satoshi")
|
||||
|
@ -15,7 +15,7 @@ def init(
|
||||
for s in (sc, sp):
|
||||
s.init(uid)
|
||||
if unlock:
|
||||
assert s.unlock(1)
|
||||
assert s.unlock("")
|
||||
return sc, sp
|
||||
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
|
||||
class StorageModel:
|
||||
_EMPTY_PIN = 1
|
||||
_EMPTY_PIN = ""
|
||||
_PIN_MAX_TRIES = 16
|
||||
|
||||
def __init__(self) -> None:
|
||||
@ -13,14 +13,14 @@ class StorageModel:
|
||||
|
||||
def wipe(self) -> None:
|
||||
self.unlocked = False
|
||||
self.pin = 1
|
||||
self.pin = self._EMPTY_PIN
|
||||
self.pin_rem = self._PIN_MAX_TRIES
|
||||
self.dict = {}
|
||||
|
||||
def lock(self) -> None:
|
||||
self.unlocked = False
|
||||
|
||||
def unlock(self, pin: int) -> bool:
|
||||
def unlock(self, pin: str) -> bool:
|
||||
if pin == self.pin:
|
||||
self.pin_rem = self._PIN_MAX_TRIES
|
||||
self.unlocked = True
|
||||
@ -37,7 +37,7 @@ class StorageModel:
|
||||
def get_pin_rem(self) -> int:
|
||||
return self.pin_rem
|
||||
|
||||
def change_pin(self, oldpin: int, newpin: int) -> bool:
|
||||
def change_pin(self, oldpin: str, newpin: str) -> bool:
|
||||
if self.unlocked and self.unlock(oldpin):
|
||||
self.pin = newpin
|
||||
return True
|
||||
|
@ -16,15 +16,14 @@ def test_init_pin():
|
||||
def test_change_pin():
|
||||
sc, sp = common.init(unlock=True)
|
||||
for s in (sc, sp):
|
||||
assert s.change_pin(1, 2221)
|
||||
assert not s.change_pin(99991, 1) # invalid old PIN
|
||||
assert not s.unlock(0) # invalid PIN
|
||||
assert s.unlock(2221)
|
||||
assert not s.change_pin(2221, 0) # invalid new PIN
|
||||
assert s.change_pin(2221, 999991)
|
||||
assert s.change_pin(999991, 991)
|
||||
assert s.unlock(991)
|
||||
assert not s.unlock(99991) # invalid PIN
|
||||
assert s.change_pin("", "222")
|
||||
assert not s.change_pin("9999", "") # invalid PIN
|
||||
assert s.unlock("222")
|
||||
assert s.change_pin("222", "99999")
|
||||
assert s.change_pin("99999", "Trezor")
|
||||
assert s.unlock("Trezor")
|
||||
assert not s.unlock("9999") # invalid PIN
|
||||
assert not s.unlock("99999") # invalid old PIN
|
||||
|
||||
assert common.memory_equals(sc, sp)
|
||||
|
||||
@ -33,34 +32,34 @@ def test_has_pin():
|
||||
sc, sp = common.init()
|
||||
for s in (sc, sp):
|
||||
assert not s.has_pin()
|
||||
assert s.unlock(1)
|
||||
assert s.unlock("")
|
||||
assert not s.has_pin()
|
||||
assert s.change_pin(1, 221)
|
||||
assert s.change_pin("", "22")
|
||||
assert s.has_pin()
|
||||
assert s.change_pin(221, 1)
|
||||
assert s.change_pin("22", "")
|
||||
assert not s.has_pin()
|
||||
|
||||
|
||||
def test_wipe_after_max_pin():
|
||||
sc, sp = common.init(unlock=True)
|
||||
for s in (sc, sp):
|
||||
assert s.change_pin(1, 2221)
|
||||
assert s.unlock(2221)
|
||||
assert s.change_pin("", "222")
|
||||
assert s.unlock("222")
|
||||
s.set(0x0202, b"Hello")
|
||||
|
||||
# try an invalid PIN MAX - 1 times
|
||||
for i in range(consts.PIN_MAX_TRIES - 1):
|
||||
assert not s.unlock(99991)
|
||||
assert not s.unlock("9999")
|
||||
# this should pass
|
||||
assert s.unlock(2221)
|
||||
assert s.unlock("222")
|
||||
assert s.get(0x0202) == b"Hello"
|
||||
|
||||
# try an invalid PIN MAX times, the storage should get wiped
|
||||
for i in range(consts.PIN_MAX_TRIES):
|
||||
assert not s.unlock(99991)
|
||||
assert not s.unlock("9999")
|
||||
assert i == consts.PIN_MAX_TRIES - 1
|
||||
# this should return False and raise an exception, the storage is wiped
|
||||
assert not s.unlock(2221)
|
||||
assert not s.unlock("222")
|
||||
with pytest.raises(RuntimeError):
|
||||
assert s.get(0x0202) == b"Hello"
|
||||
|
||||
|
@ -12,7 +12,7 @@ class StorageComparison(RuleBasedStateMachine):
|
||||
self.sc, self.sp = common.init(unlock=True)
|
||||
self.sm = StorageModel()
|
||||
self.sm.init(b"")
|
||||
self.sm.unlock(1)
|
||||
self.sm.unlock("")
|
||||
self.storages = (self.sc, self.sp, self.sm)
|
||||
|
||||
keys = Bundle("keys")
|
||||
@ -29,7 +29,10 @@ class StorageComparison(RuleBasedStateMachine):
|
||||
|
||||
@rule(target=pins, p=st.integers(1, 3))
|
||||
def p(self, p):
|
||||
return p
|
||||
if p == 1:
|
||||
return ""
|
||||
else:
|
||||
return str(p)
|
||||
|
||||
@rule(k=keys, v=values)
|
||||
def set(self, k, v):
|
||||
|
@ -33,7 +33,10 @@ class StorageUpgrade(RuleBasedStateMachine):
|
||||
|
||||
@rule(target=pins, p=st.integers(1, 3))
|
||||
def p(self, p):
|
||||
return p
|
||||
if p == 1:
|
||||
return ""
|
||||
else:
|
||||
return str(p)
|
||||
|
||||
@rule(k=keys, v=values)
|
||||
def set(self, k, v):
|
||||
|
@ -27,8 +27,8 @@ def test_set_get():
|
||||
assert common.memory_equals(sc, sp)
|
||||
|
||||
for s in (sc, sp):
|
||||
s.change_pin(1, 2221)
|
||||
s.change_pin(2221, 991)
|
||||
s.change_pin("", "222")
|
||||
s.change_pin("222", "99")
|
||||
s.set(0xAAAA, b"something else")
|
||||
assert common.memory_equals(sc, sp)
|
||||
|
||||
@ -60,7 +60,7 @@ def test_set_get():
|
||||
|
||||
# check that storage functions after unlock
|
||||
for s in (sc, sp):
|
||||
s.unlock(991)
|
||||
s.unlock("99")
|
||||
s.set(0xAAAA, b"public")
|
||||
s.set(0x0902, b"protected")
|
||||
assert s.get(0xAAAA) == b"public"
|
||||
@ -131,14 +131,14 @@ def test_set_similar():
|
||||
|
||||
for s in (sc, sp):
|
||||
s.wipe()
|
||||
s.unlock(1)
|
||||
s.unlock("")
|
||||
s.set(0xBEEF, b"satoshi")
|
||||
s.set(0xBEEF, b"Satoshi")
|
||||
assert common.memory_equals(sc, sp)
|
||||
|
||||
for s in (sc, sp):
|
||||
s.wipe()
|
||||
s.unlock(1)
|
||||
s.unlock("")
|
||||
s.set(0xBEEF, b"satoshi")
|
||||
s.set(0xBEEF, b"Satoshi")
|
||||
s.set(0xBEEF, b"Satoshi")
|
||||
|
@ -17,12 +17,12 @@ def set_values(s):
|
||||
s.set(0xBEEF, b"Hello")
|
||||
s.set(0xCAFE, b"world! ")
|
||||
s.set(0xDEAD, b"How\n")
|
||||
s.change_pin(1, 1222)
|
||||
s.change_pin("", "222")
|
||||
s.set(0xAAAA, b"are")
|
||||
s.set(0x0901, b"you?")
|
||||
s.set(0x0902, b"Lorem")
|
||||
s.set(0x0903, b"ipsum")
|
||||
s.change_pin(1222, 199)
|
||||
s.change_pin("222", "99")
|
||||
s.set(0xDEAD, b"A\n")
|
||||
s.set(0xDEAD, b"AAAAAAAAAAA")
|
||||
s.set(0x2200, b"BBBB")
|
||||
@ -31,7 +31,7 @@ def set_values(s):
|
||||
|
||||
|
||||
def check_values(s):
|
||||
assert s.unlock(199)
|
||||
assert s.unlock("99")
|
||||
assert s.get(0xAAAA) == b"are"
|
||||
assert s.get(0x0901) == b"you?"
|
||||
assert s.get(0x0902) == b"Lorem"
|
||||
@ -45,10 +45,10 @@ def check_values(s):
|
||||
def test_upgrade():
|
||||
sc0 = StorageC0()
|
||||
sc0.init()
|
||||
assert sc0.unlock(1)
|
||||
assert sc0.unlock("")
|
||||
set_values(sc0)
|
||||
for _ in range(10):
|
||||
assert not sc0.unlock(3)
|
||||
assert not sc0.unlock("3")
|
||||
|
||||
sc1 = StorageC()
|
||||
sc1._set_flash_buffer(sc0._get_flash_buffer())
|
||||
@ -60,10 +60,10 @@ def test_upgrade():
|
||||
def test_python_set_sectors():
|
||||
sp0 = StoragePy()
|
||||
sp0.init(common.test_uid)
|
||||
assert sp0.unlock(1)
|
||||
assert sp0.unlock("")
|
||||
set_values(sp0)
|
||||
for _ in range(10):
|
||||
assert not sp0.unlock(3)
|
||||
assert not sp0.unlock("3")
|
||||
assert sp0.get_pin_rem() == 6
|
||||
|
||||
sp1 = StoragePy()
|
||||
|
Loading…
Reference in New Issue
Block a user