Ejemplo n.º 1
0
async def initial_pin_setup(*a):
    # First time they select a PIN of any type.
    from login import LoginUX
    lll = LoginUX()
    title = 'Choose PIN'

    ch = await ux_show_story('''\
Pick the main wallet's PIN code now. Be more clever, but an example:

123-4567

It has two parts: prefix (123-) and suffix (-4567). \
Each part must between 2 to 6 digits long. Total length \
can be as long as 12 digits.

The prefix part determines the anti-phishing words you will \
see each time you login.

Your new PIN protects access to \
this Coldcard device and is not a factor in the wallet's \
seed words or private keys.

THERE IS ABSOLUTELY NO WAY TO RECOVER A FORGOTTEN PIN! Write it down.
''',
                             title=title)
    if ch != 'y': return

    while 1:
        ch = await ux_show_story('''\
There is ABSOLUTELY NO WAY to 'reset the PIN' or 'factory reset' the Coldcard if you forget the PIN.

DO NOT FORGET THE PIN CODE.
 
Press 6 to prove you read to the end of this message.''',
                                 title='WARNING',
                                 escape='6')

        if ch == 'x': return
        if ch == '6': break

    # do the actual picking
    pin = await lll.get_new_pin(title)
    del lll

    if pin is None: return

    # A new pin is to be set!
    from main import pa, dis, settings, loop
    dis.fullscreen("Saving...")

    try:
        dis.busy_bar(True)
        assert pa.is_blank()

        pa.change(new_pin=pin)

        # check it? kinda, but also get object into normal "logged in" state
        pa.setup(pin)
        ok = pa.login()
        assert ok

        # must re-read settings after login, because they are encrypted
        # with a key derived from the main secret.
        settings.set_key()
        settings.load()
    except Exception as e:
        print("Exception: %s" % e)
    finally:
        dis.busy_bar(False)

    # Allow USB protocol, now that we are auth'ed
    from usb import enable_usb
    enable_usb(loop, False)

    from menu import MenuSystem
    from flow import EmptyWallet
    return MenuSystem(EmptyWallet)
Ejemplo n.º 2
0
    async def try_login(self, retry=True):
        from main import pa
        while retry:

            if version.has_608 and not pa.attempts_left:
                # tell them it's futile
                await self.we_are_ewaste(pa.num_fails)

            self.reset()

            if pa.num_fails:
                self.footer = '%d failures' % pa.num_fails
                if version.has_608:
                    self.footer += ', %d tries left' % pa.attempts_left

            pin = await self.interact()

            if pin is None:
                # pressed X on empty screen ... RFU
                continue
            
            dis.fullscreen("Wait...")
            pa.setup(pin, self.is_secondary)

            if version.has_608 and pa.num_fails > 3:
                # they are approaching brickage, so warn them each attempt
                await self.confirm_attempt(pa.attempts_left, pa.num_fails, pin)
            elif pa.is_delay_needed():
                # mark 1/2 might come here, never mark3
                await self.do_delay(pa)

            # do the actual login attempt now
            try:
                dis.busy_bar(True)
                ok = pa.login()
                if ok: break        # success, leave
            except RuntimeError as exc:
                # I'm a brick and other stuff can happen here
                # - especially AUTH_FAIL when pin is just wrong.
                ok = False
                if exc.args[0] == pincodes.EPIN_I_AM_BRICK:
                    await self.we_are_ewaste(pa.num_fails)
                    continue
            finally:
                dis.busy_bar(False)

            pa.num_fails += 1
            if version.has_608:
                pa.attempts_left -= 1

            msg = ""
            nf = '1 failure' if pa.num_fails <= 1 else ('%d failures' % pa.num_fails)
            if version.has_608:
                if not pa.attempts_left:
                    await self.we_are_ewaste(pa.num_fails)
                    continue

                msg += '%d attempts left' % (pa.attempts_left)
            else:
                msg += '%s' % nf

            msg += '''\n\nPlease check all digits carefully, and that prefix versus \
suffix break point is correct.'''
            if version.has_608:
                msg += '\n\n' + nf

            await ux_show_story(msg, title='WRONG PIN')
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)