def test_min_users_parse(dev, start_hsm, tweak_rule, load_hsm_users, auth_user, sim_exec, readback_rule): policy = DICT(rules=[dict(users=USERS)]) load_hsm_users() start_hsm(policy) r = readback_rule(0) assert sorted(r.users) == sorted(USERS) assert r.min_users == len(USERS) for n in range(1, len(USERS) - 1): policy = DICT(rules=[dict(users=USERS, min_users=n)]) tweak_rule(0, policy.rules[0]) r = readback_rule(0) assert sorted(r.users) == sorted(USERS) assert r.min_users == n if n else r.min_users == len(USERS) policy = DICT(rules=[dict(users=USERS, min_users=0)]) with pytest.raises(RuntimeError) as ee: tweak_rule(0, policy.rules[0]) assert 'must be in range' in str(ee) policy = DICT(rules=[dict(users=USERS, min_users=7)]) with pytest.raises(RuntimeError) as ee: tweak_rule(0, policy.rules[0]) assert 'must be in range' in str(ee) policy = DICT(rules=[dict(users=USERS + USERS + USERS, min_users=7)]) with pytest.raises(RuntimeError) as ee: tweak_rule(0, policy.rules[0]) assert 'dup users' in str(ee)
def test_sign_msg_good(quick_start_hsm, change_hsm, attempt_msg_sign, addr_fmt=AF_CLASSIC): # message signing, but only at certain derivations permit = ['m/73', "m/*'", 'm/1p/3h/4/5/6/7'] block = ['m', 'm/72', permit[-1][:-2]] msg = b'testing 123' policy = DICT(msg_paths=permit) quick_start_hsm(policy) if 1: for addr_fmt in [AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH]: for p in permit: p = p.replace('*', '75333') attempt_msg_sign(None, msg, p, addr_fmt=addr_fmt) for p in block: attempt_msg_sign('not enabled for that path', msg, p, addr_fmt=addr_fmt) policy = DICT(msg_paths=['any']) change_hsm(policy) for p in block + permit: p = p.replace('*', '75333') attempt_msg_sign(None, msg, p, addr_fmt=addr_fmt)
def test_show_p2sh_addr(dev, hsm_reset, start_hsm, change_hsm, make_myself_wallet, addr_vs_path): # MULTISIG addrs from test_multisig import HARD, make_redeem M = 4 pm = lambda i: [HARD(45), i, 0,0] # can't amke ms wallets inside HSM mode hsm_reset() keys, _ = make_myself_wallet(M) # slow AF permit = ['p2sh', 'm/73'] start_hsm(DICT(share_addrs=permit)) scr, pubkeys, xfp_paths = make_redeem(M, keys, path_mapper=pm) assert len(scr) <= 520, "script too long for standard!" got_addr = dev.send_recv(CCProtocolPacker.show_p2sh_address( M, xfp_paths, scr, addr_fmt=AF_P2WSH)) addr_vs_path(got_addr, addr_fmt=AF_P2WSH, script=scr) # turn it off; p2sh must be explicitly allowed for allow in ['m', 'any']: change_hsm(DICT(share_addrs=[allow])) dev.send_recv(CCProtocolPacker.show_address('m', AF_CLASSIC)) with pytest.raises(CCProtoError) as ee: got_addr = dev.send_recv(CCProtocolPacker.show_p2sh_address( M, xfp_paths, scr, addr_fmt=AF_P2WSH)) assert 'Not allowed in HSM mode' in str(ee)
def test_show_addr(dev, quick_start_hsm, change_hsm): # test we can do address "showing" with no UX # which can also be disabled, etc. path = 'm/4' addr_fmt = AF_P2WPKH policy = DICT(share_addrs=[path]) def doit(path, addr_fmt): return dev.send_recv(CCProtocolPacker.show_address(path, addr_fmt), timeout=5000) quick_start_hsm(policy) addr = doit(path, addr_fmt) change_hsm(DICT(share_addrs=['m'])) with pytest.raises(CCProtoError) as ee: addr = doit(path, addr_fmt) assert 'Not allowed in HSM mode' in str(ee) addr = doit('m', addr_fmt) change_hsm(DICT(share_addrs=['any'])) addr = doit('m', addr_fmt) addr = doit('m/1/2/3', addr_fmt) addr = doit('m/3', addr_fmt) permit = ['m/73', 'm/1p/3h/4/5/6/7', 'm/1/2/3', "m/999'/*'"] change_hsm(DICT(share_addrs=permit)) for path in permit: path = path.replace('*', '73') addr = doit(path, addr_fmt)
def test_invalid_psbt(quick_start_hsm, attempt_psbt): policy = DICT(warnings_ok=True, rules=[{}]) quick_start_hsm(policy) garb = b'psbt\xff' * 20 attempt_psbt(garb, remote_error='PSBT parse failed') # even w/o any signing rights, invalid is invalid policy = DICT() quick_start_hsm(policy) attempt_psbt(garb, remote_error='PSBT parse failed')
def test_user_subset(dev, start_hsm, tweak_rule, load_hsm_users, fake_txn, attempt_psbt, auth_user): psbt = fake_txn(1, 1, dev.master_xpub) auth_user.psbt_hash = sha256(psbt).digest() policy = DICT(rules=[dict(users=['totp'])]) load_hsm_users() start_hsm(policy) for name in USERS: tweak_rule(0, dict(users=[name])) # should fail auth_user(name, garbage=True) msg = attempt_psbt(psbt, ': mismatch') assert name in msg assert 'wrong auth' in msg # should work auth_user(name) attempt_psbt(psbt) # auth should be cleared attempt_psbt(psbt, 'need user(s) confirmation') # fail as "replay" # - except PW thing is linked to PSBT, not the counter # - except HOTP doesn't see it as replay because it doesn't even check old counter value if name != 'pw': auth_user(name, do_replay=True) attempt_psbt(psbt, 'replay' if name == 'totp' else 'mismatch')
def test_named_wallets(dev, start_hsm, tweak_rule, make_myself_wallet, hsm_status, attempt_psbt, fake_txn, fake_ms_txn, amount=5E6, incl_xpubs=False): wname = 'Myself-4' M = 4 stat = hsm_status() assert not stat.active for retry in range(3): keys, _ = make_myself_wallet(4) # slow AF stat = hsm_status() if wname in stat.wallets: break # policy: only allow multisig w/ that name policy = DICT(rules=[dict(wallet=wname)]) stat = start_hsm(policy) assert 'Any amount from multisig wallet' in stat.summary assert wname in stat.summary assert 'wallets' not in stat # simple p2pkh should fail psbt = fake_txn(1, 2, dev.master_xpub, outvals=[amount, 1E8-amount], change_outputs=[1], fee=0) attempt_psbt(psbt, "not multisig") # but txn w/ multisig wallet should work psbt = fake_ms_txn(1, 2, M, keys, fee=0, outvals=[amount, 1E8-amount], outstyles=['p2wsh'], change_outputs=[1], incl_xpubs=incl_xpubs) attempt_psbt(psbt) # check ms txn not accepted when rule spec's a single signer tweak_rule(0, dict(wallet='1')) attempt_psbt(psbt, 'wrong wallet')
def worst_case_policy(): MAX_NUMBER_USERS = 30 # from shared/users.py from helpers import prandom from base64 import b32encode users = { f'user{i:02d}': [1, b32encode(prandom(10)).decode('ascii'), 0] for i in range(MAX_NUMBER_USERS) } paths = [f'm/{i}p/{i+3}' for i in range(10)] addrs = [render_address(b'\x00\x14' + prandom(20)) for i in range(5)] p = DICT(period=30, share_xpubs=paths, share_addrs=paths + ['p2sh'], msg_paths=paths, warnings_ok=False, must_log=True) p.rules = [ dict(local_conf=True, whitelist=addrs, users=list(users.keys()), min_users=rn + 3, max_amount=int(1E10), per_period=int(1E10), wallet='1') for rn in range(3) ] return users, p
def test_simple_limit(dev, amount, over, start_hsm, fake_txn, attempt_psbt, tweak_rule): # a policy which sets a hard limit policy = DICT(rules=[dict(max_amount=amount)]) stat = start_hsm(policy) assert ('Up to %g XTN per txn will be approved' % (amount / 1E8)) in stat.summary assert 'Rule #1' in stat.summary assert 'Rule #2' not in stat.summary # create a transaction psbt = fake_txn(2, 2, dev.master_xpub, outvals=[amount, 2E8 - amount], change_outputs=[1], fee=0) attempt_psbt(psbt) psbt = fake_txn(2, 2, dev.master_xpub, outvals=[amount + over, 2E8 - amount - over], change_outputs=[1], fee=0) attempt_psbt(psbt, "amount exceeded") if tweak_rule: tweak_rule(0, dict(max_amount=int(amount + over))) attempt_psbt(psbt)
def test_whitelist_single(dev, start_hsm, tweak_rule, attempt_psbt, fake_txn, amount=5E6): junk = EXAMPLE_ADDRS[0] policy = DICT(rules=[dict(whitelist=[junk])]) started = False start_hsm(policy) # try all addr types for style in ['p2wpkh', 'p2wsh', 'p2sh', 'p2pkh', 'p2wsh-p2sh', 'p2wpkh-p2sh']: dests = [] psbt = fake_txn(1, 2, dev.master_xpub, outstyles=[style, 'p2wpkh'], outvals=[amount, 1E8-amount], change_outputs=[1], fee=0, capture_scripts=dests) dest = render_address(dests[0]) tweak_rule(0, dict(whitelist=[dest])) attempt_psbt(psbt) tweak_rule(0, dict(whitelist=[junk])) attempt_psbt(psbt, "non-whitelisted") tweak_rule(0, dict(whitelist=[dest, junk])) attempt_psbt(psbt)
def test_local_conf(dev, quick_start_hsm, tweak_rule, load_hsm_users, fake_txn, enter_local_code, hsm_status, attempt_psbt, auth_user, sim_exec, readback_rule): psbt = fake_txn(1, 1, dev.master_xpub) auth_user.psbt_hash = sha256(psbt).digest() # self test vectors assert calc_local_pincode(b'b' * 32, 'YWFhYWFhYWFhYWFhYWFh') == '998170' assert calc_local_pincode(bytes(32), 'YWFhYWFhYWFhYWFaYWFh') == '816912' load_hsm_users() policy = DICT(rules=[dict(users=USERS, local_conf=True)]) s = quick_start_hsm(policy) for u in USERS: auth_user(u) lcode = calc_local_pincode(sha256(psbt).digest(), s.next_local_code) enter_local_code(lcode) attempt_psbt(psbt) for u in USERS: auth_user(u) attempt_psbt(psbt, 'local operator didn\'t confirm') tweak_rule(0, dict(local_conf=True)) attempt_psbt(psbt, 'local operator didn\'t confirm') s = hsm_status() lcode = calc_local_pincode(sha256(psbt).digest(), s.next_local_code) enter_local_code(lcode) attempt_psbt(psbt)
def test_sign_msg_any(quick_start_hsm, attempt_msg_sign, addr_fmt=AF_CLASSIC): permit = ['m/73', 'm/1p/3h/4/5/6/7'] block = ['m', 'm/72', permit[-1][:-2]] msg = b'whatever' policy = DICT(msg_paths=['any']) quick_start_hsm(policy) for p in permit + block: attempt_msg_sign(None, msg, p, addr_fmt=addr_fmt)
def test_never_log(dev, start_hsm, attempt_msg_sign, fake_txn, attempt_psbt, sim_card_ejected): # never try to log anything policy = DICT(never_log=True, msg_paths=['m'], rules=[{}]) start_hsm(policy) sim_card_ejected(True) # WEAK test attempt_msg_sign(None, b'hello', 'm', addr_fmt=AF_CLASSIC)
def test_psbt_warnings(dev, quick_start_hsm, tweak_hsm_attr, attempt_psbt, fake_txn, amount=5E6): # txn w/ warnings policy = DICT(warnings_ok=True, rules=[{}]) stat = quick_start_hsm(policy) assert 'warnings' in stat.summary psbt = fake_txn(1, 1, dev.master_xpub, fee=0.05E8) attempt_psbt(psbt) tweak_hsm_attr('warnings_ok', False) attempt_psbt(psbt, 'has 1 warning(s)')
def test_velocity(dev, start_hsm, fake_txn, attempt_psbt, fast_forward, hsm_status): # stop everything if can't log level = int(1E8) policy = DICT(period=2, rules=[dict(per_period=level)]) start_hsm(policy) psbt = fake_txn(2, 1, dev.master_xpub) attempt_psbt(psbt, 'would exceed period spending') psbt = fake_txn(2, 2, dev.master_xpub) attempt_psbt(psbt, 'would exceed period spending') psbt = fake_txn(2, 10, dev.master_xpub) attempt_psbt(psbt, 'would exceed period spending') psbt = fake_txn(2, 2, dev.master_xpub, outvals=[level, 2E8 - level], change_outputs=[1]) attempt_psbt(psbt) # exactly the limit s = hsm_status() assert 90 <= s.period_ends <= 120 assert s.has_spent == [level] attempt_psbt(psbt, 'would exceed period spending') psbt = fake_txn(1, 1, dev.master_xpub) attempt_psbt(psbt, 'would exceed period spending') # skip ahead fast_forward(120) s = hsm_status() assert 'period_ends' not in s assert 'has_spend' not in s amt = 0.30E8 psbt = fake_txn(1, 2, dev.master_xpub, outvals=[amt, 1E8 - amt], change_outputs=[1]) attempt_psbt(psbt) # 1/3rd of limit attempt_psbt(psbt) # 1/3rd of limit attempt_psbt(psbt) # 1/3rd of limit attempt_psbt(psbt, 'would exceed period spending') s = hsm_status() assert 90 <= s.period_ends <= 120 assert s.has_spent == [int(amt * 3)]
def test_xpub_sharing(dev, start_hsm, change_hsm, addr_fmt=AF_CLASSIC): # xpub sharing, but only at certain derivations # - note 'm' is always shared permit = ['m', 'm/73', "m/43/44/*'", 'm/1p/3h/4/5/6/7'] block = ['m/72', 'm/43/44/99', permit[-1][:-2]] policy = DICT(share_xpubs=permit) start_hsm(policy) for p in permit: p = p.replace('*', '99') xpub = dev.send_recv(CCProtocolPacker.get_xpub(p), timeout=5000) for p in block: with pytest.raises(CCProtoError) as ee: xpub = dev.send_recv(CCProtocolPacker.get_xpub(p), timeout=5000) assert 'Not allowed in HSM mode' in str(ee) policy = DICT(share_xpubs=['any']) change_hsm(policy) for p in block + permit: p = p.replace('*', '99') xpub = dev.send_recv(CCProtocolPacker.get_xpub(p), timeout=5000) # default is block all but 'm' policy = DICT() change_hsm(policy) for p in block + permit: if p == 'm': continue p = p.replace('*', '99') with pytest.raises(CCProtoError) as ee: xpub = dev.send_recv(CCProtocolPacker.get_xpub(p), timeout=5000) assert 'Not allowed in HSM mode' in str(ee) # 'm' always works xpub = dev.send_recv(CCProtocolPacker.get_xpub('m'), timeout=5000) assert xpub[0:4] == 'tpub'
def test_big_txn(num_in, num_out, dev, quick_start_hsm, hsm_status, is_simulator, tweak_hsm_attr, attempt_psbt, fake_txn, amount=5E6): if not is_simulator(): # It does work, I've done it, but let's never do it again... raise pytest.skip("life is too short") # do something slow policy = DICT(warnings_ok=True, rules=[{}]) quick_start_hsm(policy) for count in range(20): psbt = fake_txn(num_in, num_out, dev.master_xpub) attempt_psbt(psbt)
def test_storage_locker(package, count, start_hsm, dev): # read and write (limited) of storage locker. policy = DICT(set_sl=package, allow_sl=count) start_hsm(policy) for t in range(count+3): if t < count: got = dev.send_recv(CCProtocolPacker.get_storage_locker(), timeout=None) assert got == package.encode('ascii') else: with pytest.raises(CCProtoError) as ee: got = dev.send_recv(CCProtocolPacker.get_storage_locker(), timeout=None) assert 'consumed' in str(ee)
def test_must_log(dev, start_hsm, sim_card_ejected, attempt_msg_sign, fake_txn, attempt_psbt, is_simulator): # stop everything if can't log policy = DICT(must_log=True, msg_paths=['m'], rules=[{}]) start_hsm(policy) psbt = fake_txn(1, 1, dev.master_xpub) sim_card_ejected(True) attempt_msg_sign('Could not log details', b'hello', 'm', addr_fmt=AF_CLASSIC) attempt_psbt(psbt, 'Could not log details') if is_simulator(): sim_card_ejected(False) attempt_msg_sign(None, b'hello', 'm', addr_fmt=AF_CLASSIC) attempt_psbt(psbt)
def test_whitelist_multi(dev, start_hsm, tweak_rule, attempt_psbt, fake_txn, amount=5E6): # sending to one whitelisted, and one non, etc. junk = EXAMPLE_ADDRS[0] policy = DICT(rules=[dict(whitelist=[junk])]) stat = start_hsm(policy) # make a txn that sends to every type of output styles = ['p2wpkh', 'p2wsh', 'p2sh', 'p2pkh', 'p2wsh-p2sh', 'p2wpkh-p2sh'] dests = [] psbt = fake_txn(1, len(styles), dev.master_xpub, outstyles=styles, capture_scripts=dests) dests = [render_address(s) for s in dests] # simple: sending to all tweak_rule(0, dict(whitelist=dests)) attempt_psbt(psbt) # whitelist only one of those (expect fail) for dest in dests: tweak_rule(0, dict(whitelist=[dest])) msg = attempt_psbt(psbt, 'non-whitelisted') nwl = msg.rsplit(': ', 1)[1] # random addr is put in err msg assert nwl != dest assert nwl in dests # whitelist all but one of them for dest in dests: others = [d for d in dests if d != dest] tweak_rule(0, dict(whitelist=others)) msg = attempt_psbt(psbt, 'non-whitelisted') # sing addr is put in err msg nwl = msg.rsplit(': ', 1)[1] assert nwl == dest assert nwl in dests
def test_min_users_perms(dev, quick_start_hsm, load_hsm_users, fake_txn, attempt_psbt, auth_user, sim_exec, readback_rule): psbt = fake_txn(1, 1, dev.master_xpub) auth_user.psbt_hash = sha256(psbt).digest() load_hsm_users() # all subsets of users for n in range(1, len(USERS)): policy = DICT(rules=[dict(users=USERS, min_users=n)]) quick_start_hsm(policy) for au in itertools.permutations(USERS, n): #print("Auth with: " + '+'.join(au)) for u in au: auth_user(u) attempt_psbt(psbt) # auth should be cleared attempt_psbt(psbt, 'need user(s) confirmation')
sim_exec(cmd) time.sleep(.1) yield doit try: cmd = 'import uos, hsm; uos.unlink(hsm.POLICY_FNAME)' sim_exec(cmd) except: pass @pytest.mark.parametrize( 'policy,contains', [ (DICT(), 'No transaction will be signed'), (DICT(must_log=1), 'MicroSD card MUST '), (DICT(must_log=0), 'MicroSD card will '), (DICT(never_log=1), 'No logging'), (DICT(warnings_ok=1), 'PSBT warnings'), (DICT(priv_over_ux=1), 'optimized for privacy'), # boot-to-hsm (DICT(boot_to_hsm='any'), 'Boot to HSM enabled'), (DICT(boot_to_hsm='123123'), 'Boot to HSM enabled'), # msg signing (DICT(msg_paths=["m/1'/2p/3H"]), "m/1'/2'/3'"), (DICT(msg_paths=["m/1", "m/2"]), "m/1 OR m/2"), (DICT(msg_paths=["any"]), "(any path)"),