Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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')
Ejemplo n.º 6
0
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')
Ejemplo n.º 7
0
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')
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
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)
Ejemplo n.º 12
0
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)
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
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)')
Ejemplo n.º 15
0
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)]
Ejemplo n.º 16
0
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'
Ejemplo n.º 17
0
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)
Ejemplo n.º 18
0
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)
Ejemplo n.º 19
0
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)
Ejemplo n.º 20
0
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
Ejemplo n.º 21
0
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')
Ejemplo n.º 22
0
            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)"),