Ejemplo n.º 1
0
async def clear_seed(*a):
    # Erase the seed words, and private key from this wallet!
    # This is super dangerous for the customer's money.
    import seed
    from main import pa

    if pa.has_duress_pin():
        await ux_show_story(
            '''Please empty the duress wallet, and clear the duress PIN before clearing main seed.'''
        )
        return

    if not await ux_confirm(
            '''Wipe seed words and reset wallet. All funds will be lost. You better have a backup of the seed words.'''
    ):
        return await ux_aborted()

    ch = await ux_show_story('''Are you REALLY sure though???\n\n\
This action will certainly cause you to lose all funds associated with this wallet, \
unless you have a backup of the seed words and know how to import them into a \
new wallet.\n\nPress 4 to prove you read to the end of this message and accept all \
consequences.''',
                             escape='4')
    if ch != '4':
        return await ux_aborted()

    seed.clear_seed()
Ejemplo n.º 2
0
def render_backup_contents():
    # simple text format:
    #   key = value
    # or #comments
    # but value is JSON
    from main import settings, pa

    rv = StringIO()

    def COMMENT(val=None):
        if val:
            rv.write('\n# %s\n' % val)
        else:
            rv.write('\n')

    def ADD(key, val):
        rv.write('%s = %s\n' % (key, ujson.dumps(val)))

    rv.write('# Coldcard backup file! DO NOT CHANGE.\n')

    chain = chains.current_chain()

    COMMENT('Private key details: ' + chain.name)

    with stash.SensitiveValues(for_backup=True) as sv:

        if sv.mode == 'words':
            ADD('mnemonic', tcc.bip39.from_data(sv.raw))

        if sv.mode == 'master':
            ADD('bip32_master_key', b2a_hex(sv.raw))

        ADD('chain', chain.ctype)
        ADD('xprv', chain.serialize_private(sv.node))
        ADD('xpub', chain.serialize_public(sv.node))

        # BTW: everything is really a duplicate of this value
        ADD('raw_secret', b2a_hex(sv.secret).rstrip(b'0'))

        if pa.has_duress_pin():
            COMMENT('Duress Wallet (informational)')
            dpk = sv.duress_root()
            ADD('duress_xprv', chain.serialize_private(dpk))
            ADD('duress_xpub', chain.serialize_public(dpk))

        if version.has_608:
            # save the so-called long-secret
            ADD('long_secret', b2a_hex(pa.ls_fetch()))

    COMMENT('Firmware version (informational)')
    date, vers, timestamp = version.get_mpy_version()[0:3]
    ADD('fw_date', date)
    ADD('fw_version', vers)
    ADD('fw_timestamp', timestamp)
    ADD('serial', version.serial_number())

    COMMENT('User preferences')

    # user preferences
    for k, v in settings.current.items():
        if k[0] == '_': continue  # debug stuff in simulator
        if k == 'xpub': continue  # redundant, and wrong if bip39pw
        if k == 'xfp': continue  # redundant, and wrong if bip39pw
        ADD('setting.' + k, v)

    if version.has_fatram:
        import hsm
        if hsm.hsm_policy_available():
            ADD('hsm_policy', hsm.capture_backup())

    rv.write('\n# EOF\n')

    return rv.getvalue()
Ejemplo n.º 3
0
async def pin_changer(_1, _2, item):
    # Help them to change pins with appropriate warnings.
    # - forcing them to drill-down to get warning about secondary is on purpose
    # - the bootloader maybe lying to us about weather we are main vs. duress
    # - there is a duress wallet for both main/sec pins, and you need to know main pin for that
    # - what may look like just policy here, is in fact enforced by the bootrom code
    #
    from main import pa, dis
    from login import LoginUX
    from pincodes import BootloaderError, EPIN_OLD_AUTH_FAIL

    mode = item.arg

    warn = {
        'main':
        ('Main PIN',
         'You will be changing the main PIN used to unlock your Coldcard. '
         "It's the one you just used a moment ago to get in here."),
        'duress': ('Duress PIN',
                   'This PIN leads to a bogus wallet. Funds are recoverable '
                   'from main seed backup, but not as easily.'),
        'secondary':
        ('Second PIN',
         'This PIN protects the "secondary" wallet that can be used to '
         'segregate funds or other banking purposes. This other wallet is '
         'completely independant of the primary.'),
        'brickme':
        ('Brickme PIN',
         'Use of this special PIN code at any prompt will destroy the '
         'Coldcard completely. It cannot be reused or salvaged, and '
         'the secrets it held are destroyed forever.\n\nDO NOT TEST THIS!'),
    }

    if pa.is_secondary:
        # secondary wallet user can only change their own password, and the secondary
        # duress pin...
        # - now excluded from menu, but keep for Mark1/2 hardware!
        if mode == 'main' or mode == 'brickme':
            await needs_primary()
            return

    if mode == 'duress' and pa.is_secret_blank():
        await ux_show_story(
            "Please set wallet seed before creating duress wallet.")
        return

    # are we changing the pin used to login?
    is_login_pin = (mode == 'main') or (mode == 'secondary'
                                        and pa.is_secondary)

    lll = LoginUX()
    lll.offer_second = False
    title, msg = warn[mode]

    async def incorrect_pin():
        await ux_show_story(
            'You provided an incorrect value for the existing %s.' % title,
            title='Wrong PIN')
        return

    # standard threats for all PIN's
    msg += '''\n\n\
THERE IS ABSOLUTELY NO WAY TO RECOVER A FORGOTTEN PIN! Write it down.

We strongly recommend all PIN codes used be unique between each other.
'''
    if not is_login_pin:
        msg += '''\nUse 999999-999999 to clear existing PIN.'''

    ch = await ux_show_story(msg, title=title)
    if ch != 'y': return

    args = {}

    need_old_pin = True

    if is_login_pin:
        # Challenge them for old password; they probably have it, and we have it
        # in memory already, because we wouldn't be here otherwise... but
        # challenge them anyway as a policy choice.
        need_old_pin = True
    else:
        # There may be no existing PIN, and we need to learn that

        if mode == 'secondary':
            args['is_secondary'] = True

        elif mode == 'duress':

            args['is_duress'] = True

            need_old_pin = bool(pa.has_duress_pin())

        elif mode == 'brickme':
            args['is_brickme'] = True

            need_old_pin = bool(pa.has_brickme_pin())

        if need_old_pin and not version.has_608:
            # Do an expensive check (mostly for secondary pin case?)
            try:
                dis.fullscreen("Check...")
                pa.change(old_pin=b'', new_pin=b'', **args)
                need_old_pin = False
            except BootloaderError as exc:
                # not an error: old pin in non-blank
                need_old_pin = True

    if not need_old_pin:
        # It is blank
        old_pin = ''
    else:
        # We need the existing pin, so prompt for that.
        lll.subtitle = 'Old ' + title

        old_pin = await lll.prompt_pin()
        if old_pin is None:
            return await ux_aborted()

    args['old_pin'] = old_pin.encode()

    # we can verify the main pin right away here. Be nice.
    if is_login_pin and args['old_pin'] != pa.pin:
        return await incorrect_pin()

    while 1:
        lll.reset()
        lll.subtitle = "New " + title
        pin = await lll.get_new_pin(title, allow_clear=True)

        if pin is None:
            return await ux_aborted()

        is_clear = (pin == '999999-999999')

        args['new_pin'] = pin.encode() if not is_clear else b''

        if args['new_pin'] == pa.pin and not is_login_pin:
            await ux_show_story(
                "Your new PIN matches the existing PIN used to get here. "
                "It would be a bad idea to use it for another purpose.",
                title="Try Again")
            continue

        break

    # install it.
    try:
        dis.fullscreen("Clearing..." if is_clear else "Saving...")
        dis.busy_bar(True)

        pa.change(**args)
        dis.busy_bar(False)
    except Exception as exc:
        dis.busy_bar(False)

        code = exc.args[1]

        if code == EPIN_OLD_AUTH_FAIL:
            # likely: wrong old pin, on anything but main PIN
            return await incorrect_pin()
        else:
            return await ux_show_story("Unexpected low-level error: %s" %
                                       exc.args[0],
                                       title='Error')

    # Main pin is changed, and we use it lots, so update pa
    # - also we need pa.has_duress_pin() and has_brickme_pin() to be correct
    # - this step can be super slow with 608, unfortunately
    try:
        dis.fullscreen("Verify...")
        dis.busy_bar(True)

        pa.setup(args['new_pin'] if is_login_pin else pa.pin, pa.is_secondary)

        if not pa.is_successful():
            # typical: do need login, but if we just cleared the main PIN,
            # we cannot/need not login again
            pa.login()

        if mode == 'duress':
            # program the duress secret now... it's derived from real wallet contents
            from stash import SensitiveValues, SecretStash, AE_SECRET_LEN

            if is_clear:
                # clear secret, using the new pin, which is empty string
                pa.change(is_duress=True,
                          new_secret=b'\0' * AE_SECRET_LEN,
                          old_pin=b'')
            else:
                with SensitiveValues() as sv:
                    # derive required key
                    node = sv.duress_root()
                    d_secret = SecretStash.encode(xprv=node)
                    sv.register(d_secret)

                    # write it out.
                    pa.change(is_duress=True,
                              new_secret=d_secret,
                              old_pin=args['new_pin'])

    finally:
        dis.busy_bar(False)