test(storage): Update tests to comply with new storage version and interface.

pull/1546/head
Andrew Kozlik 4 years ago committed by Andrew Kozlik
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:

@ -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…
Cancel
Save