diff --git a/storage/tests/c/storage.py b/storage/tests/c/storage.py index 51ba59498..ee652ff99 100644 --- a/storage/tests/c/storage.py +++ b/storage/tests/c/storage.py @@ -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: diff --git a/storage/tests/c0/libtrezor-storage0.so b/storage/tests/c0/libtrezor-storage0.so deleted file mode 100755 index 3504b9d60..000000000 Binary files a/storage/tests/c0/libtrezor-storage0.so and /dev/null differ diff --git a/storage/tests/c0/storage.py b/storage/tests/c0/storage.py index 235695c63..d7981fb1e 100644 --- a/storage/tests/c0/storage.py +++ b/storage/tests/c0/storage.py @@ -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: diff --git a/storage/tests/python/src/consts.py b/storage/tests/python/src/consts.py index c70a1c97a..349794dee 100644 --- a/storage/tests/python/src/consts.py +++ b/storage/tests/python/src/consts.py @@ -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( diff --git a/storage/tests/python/src/crypto.py b/storage/tests/python/src/crypto.py index 037d82a0e..30b1f851d 100644 --- a/storage/tests/python/src/crypto.py +++ b/storage/tests/python/src/crypto.py @@ -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. diff --git a/storage/tests/python/src/storage.py b/storage/tests/python/src/storage.py index 65267244d..538fa2be9 100644 --- a/storage/tests/python/src/storage.py +++ b/storage/tests/python/src/storage.py @@ -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 diff --git a/storage/tests/python/tests/test_pin.py b/storage/tests/python/tests/test_pin.py index a6f517922..23527a8e0 100644 --- a/storage/tests/python/tests/test_pin.py +++ b/storage/tests/python/tests/test_pin.py @@ -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") diff --git a/storage/tests/test.py b/storage/tests/test.py index 0ee5f4a0e..97d365119 100755 --- a/storage/tests/test.py +++ b/storage/tests/test.py @@ -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") diff --git a/storage/tests/tests/common.py b/storage/tests/tests/common.py index c66ea8091..9a88a9c14 100644 --- a/storage/tests/tests/common.py +++ b/storage/tests/tests/common.py @@ -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 diff --git a/storage/tests/tests/storage_model.py b/storage/tests/tests/storage_model.py index 06eedc460..f11f9b3e1 100644 --- a/storage/tests/tests/storage_model.py +++ b/storage/tests/tests/storage_model.py @@ -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 diff --git a/storage/tests/tests/test_pin.py b/storage/tests/tests/test_pin.py index efda1a330..8e75de1c9 100644 --- a/storage/tests/tests/test_pin.py +++ b/storage/tests/tests/test_pin.py @@ -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" diff --git a/storage/tests/tests/test_random.py b/storage/tests/tests/test_random.py index 01f4f71c6..ad8c8169a 100644 --- a/storage/tests/tests/test_random.py +++ b/storage/tests/tests/test_random.py @@ -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): diff --git a/storage/tests/tests/test_random_upgrade.py b/storage/tests/tests/test_random_upgrade.py index 4aaa1a507..0b573705d 100644 --- a/storage/tests/tests/test_random_upgrade.py +++ b/storage/tests/tests/test_random_upgrade.py @@ -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): diff --git a/storage/tests/tests/test_set_get.py b/storage/tests/tests/test_set_get.py index cb313cf84..d46c8b12c 100644 --- a/storage/tests/tests/test_set_get.py +++ b/storage/tests/tests/test_set_get.py @@ -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") diff --git a/storage/tests/tests/test_upgrade.py b/storage/tests/tests/test_upgrade.py index a089438ca..4d8c5805d 100644 --- a/storage/tests/tests/test_upgrade.py +++ b/storage/tests/tests/test_upgrade.py @@ -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()