# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC. # # tests for ../shared/pwsave.py # import pytest, time, os, shutil from binascii import a2b_hex from constants import simulator_fixed_tprv @pytest.fixture def simulator_db_file(sim_root_dir): def doit(): return sim_root_dir + "/MicroSD/.tmp.tmp" return doit @pytest.fixture def set_pw_phrase(pick_menu_item, word_menu_entry): def doit(words): for w in words.split(): pick_menu_item('Add Word') word_menu_entry([w.lower()]) pick_menu_item(w) return doit @pytest.mark.parametrize('pws', [ 'abc abc def def 123', '1 2 3', '1 2 3 11 22 33', '1aa1 2aa2 1aa2 2aa1', 'aBc1 aBc2 aBc3', 'abcd defg', '1aaa 2aaa', '1aaa2aaa', 'ab'*25, ]) def test_first_time(pws, need_keypress, cap_story, pick_menu_item, enter_complex, cap_menu, go_to_passphrase, reset_seed_words, press_select, simulator_db_file): try: os.unlink(simulator_db_file()) except: pass pws = pws.split() xfps = {} uniq = [] for pw in pws: if pw not in uniq: uniq.append(pw) go_to_passphrase() enter_complex(pw, apply=True) time.sleep(.01) title, story = cap_story() xfp = title[1:-1] assert '(1) to apply and save to MicroSD' in story need_keypress('1') xfps[pw] = xfp reset_seed_words() for n, pw in enumerate(uniq): go_to_passphrase() pick_menu_item('Restore Saved') m = cap_menu() assert len(m) == len(uniq) if len(pw): if m[0][0] != '*': assert set(i[0] for i in m) == set(j[0] if j else '(' for j in pws) else: assert set(i[-1] for i in m) == set(j[-1] if j else ')' for j in pws) pick_menu_item(m[n]) time.sleep(.1) sub_menu = cap_menu() assert len(sub_menu) == 3 # xfp label, restore, delete assert xfps[uniq[n]] in sub_menu[0] pick_menu_item("Restore") time.sleep(.01) title, story = cap_story() xfp = title[1:-1] assert xfp == xfps[uniq[n]] press_select() reset_seed_words() def test_crypto_unittest(sim_exec, simulator, simulator_db_file): # unit test for AES key generation from SDCard and master secret card = sim_exec('import files; from h import b2a_hex; cs = files.CardSlot().__enter__(); RV.write(b2a_hex(cs.get_id_hash())); cs.__exit__()') # known value for simulator, generally unknown on random SD cards assert card == '95a60b9ff0c944ec2c23a28e599f794e95bb376a451b6037b054f8230b405fb0' salt = a2b_hex(card) # read key simulator calculates key = sim_exec('''\ import files; from h import b2a_hex; \ from pwsave import PassphraseSaver; \ cs = files.CardSlot().__enter__(); \ p=PassphraseSaver(); p._calc_key(cs); RV.write(b2a_hex(p.key)); cs.__exit__()''') assert len(key) == 64 # recalc what it should be from bip32 import BIP32Node from hashlib import sha256 mk = BIP32Node.from_wallet_key(simulator_fixed_tprv) sk = mk.subkey_for_path('2147431408h/0h') md = sha256() md.update(salt) md.update(bytes(sk.node.private_key)) md.update(salt) expect = sha256(md.digest()).hexdigest() assert expect == key # check that key works for decrypt and that the file was actually encrypted with open(simulator_db_file(), 'rb') as fd: raw = fd.read() import pyaes d = pyaes.AESModeOfOperationCTR(a2b_hex(expect), pyaes.Counter(0)).decrypt txt = str(bytearray(d(raw)), 'utf8') print(txt) assert txt[0] == '[' and txt[-1] == ']' import json j = json.loads(txt) assert isinstance(j, list) assert j[0]['pw'] assert j[0]['xfp'] def test_delete_one_by_one(go_to_passphrase, pick_menu_item, cap_menu, cap_story, press_select, src_root_dir, sim_root_dir): # delete it one by one # when all deleted - we must be back in Passphrase # menu without Restore Saved option visible go_to_passphrase() time.sleep(.1) m = cap_menu() if 'Restore Saved' not in m: shutil.copy2(f'{src_root_dir}/testing/data/pwsave.tmp', f'{sim_root_dir}/MicroSD/.tmp.tmp') go_to_passphrase() pick_menu_item('Restore Saved') m = cap_menu() len_m = len(m) for i, mi in enumerate(m): pick_menu_item(mi) pick_menu_item("Delete") time.sleep(.1) _, story = cap_story() assert "Delete saved passphrase?" in story press_select() mm = cap_menu() if i == (len_m - 1): # last item - back to passphrase menu assert "Edit Phrase" in mm assert "Restore Saved" not in mm else: assert mi not in mm assert "Edit Phrase" not in mm # EOF