Beispiel #1
0
async def remember_bip39_passphrase():
    # Compute current xprv and switch to using that as root secret.
    import stash
    from main import dis, pa

    dis.fullscreen('Check...')

    with stash.SensitiveValues() as sv:
        if sv.mode != 'words':
            # not a BIP39 derived secret, so cannot work.
            await ux_show_story('''The wallet secret was not based on a seed phrase, so we cannot add a BIP39 passphrase at this time.''', title='Failed')
            return

        nv = SecretStash.encode(xprv=sv.node)

    # Important: won't write new XFP to nvram if pw still set
    stash.bip39_passphrase = ''

    dis.fullscreen('Saving...')
    pa.change(new_secret=nv)

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)

    # check and reload secret
    pa.reset()
    pa.login()
Beispiel #2
0
async def remember_bip39_passphrase():
    # Compute current xprv and switch to using that as root secret.
    import stash
    from main import dis, pa

    if not stash.bip39_passphrase:
        if not await ux_confirm(
                '''You do not have a BIP39 passphrase set right now, so this command does little except forget the seed words. It does not enhance security.'''
        ):
            return

    dis.fullscreen('Check...')

    with stash.SensitiveValues() as sv:
        if sv.mode != 'words':
            # not a BIP39 derived secret, so cannot work.
            await ux_show_story(
                '''The wallet secret was not based on a seed phrase, so we cannot add a BIP39 passphrase at this time.''',
                title='Failed')
            return

        nv = SecretStash.encode(xprv=sv.node)

    dis.fullscreen('Saving...')
    pa.change(new_secret=nv)

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)

    # check and reload secret
    pa.reset()
    pa.login()
Beispiel #3
0
    async def try_login(self, retry=True):
        from main import pa, numpad

        while retry:
            self.reset()

            pin = await self.interact()

            if pin is None:
                # Perhaps they are having trouble with touch pad?
                numpad.sensitivity = 2
                continue
            
            pa.setup(pin, self.is_secondary)

            if pa.is_delay_needed() or pa.num_fails:
                await self.do_delay(pa)

            # do the actual login attempt now
            dis.fullscreen("Wait...")
            try:
                ok = pa.login()
                if ok: break
            except RuntimeError as e:
                # I'm a brick and other stuff can happen here
                print("pa.login: %r" % e)

            await ux_show_story('''\
That's not the right PIN!\n
Please check all digits carefully, and that prefix verus suffix break point is correct.
Your next attempt will take even longer, so please keep that in mind.
''', title='Wrong PIN')
Beispiel #4
0
async def initial_pin_setup(*a):
    # First time they select a PIN of any type.
    from login import LoginUX
    lll = LoginUX()
    pin = await lll.get_new_pin(
        'Choose PIN', '''\
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.
''')
    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:
        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)

    # 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)
Beispiel #5
0
def set_genuine():
    # PIN must be blank for this to work
    # - or logged in already as main
    from main import pa

    if pa.is_secondary:
        return

    if not pa.is_successful():
        # assume blank pin during factory selftest
        pa.setup(b'')
        assert not pa.is_delay_needed()     # "PIN failures?"

        if not pa.is_successful():
            pa.login()
            assert pa.is_successful()       # "PIN not blank?"

    # do verify step
    pa.greenlight_firmware()

    dis.show()
Beispiel #6
0
def set_seed_value(words):
    # Save the seed words into secure element, and reboot. BIP39 password
    # is not set at this point (empty string)
    ok = tcc.bip39.check(' '.join(words))
    assert ok, "seed check: %r" % words

    # map words to bip39 wordlist indices
    data = [tcc.bip39.lookup_word(w) for w in words]

    # map to packed binary representation.
    val = 0
    for v in data:
        val <<= 11
        val |= v

    # remove the checksum part
    vlen = (len(words) * 4) // 3
    val >>= (len(words) // 3)

    # convert to bytes
    seed = val.to_bytes(vlen, 'big')
    assert len(seed) == vlen
    
    from main import dis, pa, settings

    # encode it for our limited secret space
    nv = SecretStash.encode(seed_phrase=seed)

    dis.fullscreen('Applying...')
    pa.change(new_secret=nv)

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)

    # check and reload secret
    pa.reset()
    pa.login()
Beispiel #7
0
async def remember_bip39_passphrase():
    # Compute current xprv and switch to using that as root secret.
    import stash
    from main import dis, pa

    dis.fullscreen('Check...')

    with stash.SensitiveValues() as sv:
        nv = SecretStash.encode(xprv=sv.node)

    # Important: won't write new XFP to nvram if pw still set
    stash.bip39_passphrase = ''

    dis.fullscreen('Saving...')
    pa.change(new_secret=nv)

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)

    # check and reload secret
    pa.reset()
    pa.login()
Beispiel #8
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')
Beispiel #9
0
async def start_login_sequence():
    # Boot up login sequence here.
    #
    from main import pa, settings, dis, loop, numpad
    import version

    if pa.is_blank():
        # Blank devices, with no PIN set all, can continue w/o login

        # Do green-light set immediately after firmware upgrade
        if version.is_fresh_version():
            pa.greenlight_firmware()
            dis.show()

        goto_top_menu()
        return

    # Allow impatient devs and crazy people to skip the PIN
    guess = settings.get('_skip_pin', None)
    if guess is not None:
        try:
            dis.fullscreen("(Skip PIN)")
            pa.setup(guess)
            pa.login()
        except:
            pass

    # if that didn't work, or no skip defined, force
    # them to login succefully.
    while not pa.is_successful():
        # always get a PIN and login first
        await block_until_login()

    # Must read settings after login
    settings.set_key()
    settings.load()

    # Restore a login preference or two
    numpad.sensitivity = settings.get('sens', numpad.sensitivity)

    # Do green-light set immediately after firmware upgrade
    if not pa.is_secondary:
        if version.is_fresh_version():
            pa.greenlight_firmware()
            dis.show()

    # Populate xfp/xpub values, if missing.
    # - can happen for first-time login of duress wallet
    # - may indicate lost settings, which we can easily recover from
    # - these values are important to USB protocol
    if not (settings.get('xfp', 0)
            and settings.get('xpub', 0)) and not pa.is_secret_blank():
        try:
            import stash

            # Recalculate xfp/xpub values (depends both on secret and chain)
            with stash.SensitiveValues() as sv:
                sv.capture_xpub()
        except Exception as exc:
            # just in case, keep going; we're not useless and this
            # is early in boot process
            print("XFP save failed: %s" % exc)

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

    goto_top_menu()
Beispiel #10
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...
        # NOTE: now excluded from menu, but keep
        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; certainly had it, because
        # we wouldn't be here otherwise.
        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
        elif mode == 'brickme':
            args['is_brickme'] = True

        old_pin = None
        try:
            dis.fullscreen("Check...")
            pa.change(old_pin=b'', new_pin=b'', **args)
            need_old_pin = False
            old_pin = ''  # converts to bytes below
        except BootloaderError as exc:
            #print("(not-error) old pin was NOT blank: %s" % exc)
            need_old_pin = True

    was_blank = not need_old_pin

    if need_old_pin:
        # 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.
    dis.fullscreen("Saving...")

    try:
        pa.change(**args)
    except Exception as exc:
        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 to get pa.has_duress_pin() and has_brickme_pin() to be correct, need this
    dis.fullscreen("Verify...")
    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
        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'',
                      new_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'])
Beispiel #11
0
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# quickly main wipe seed; don't install anything new
from main import pa, settings, numpad, dis
from pincodes import AE_SECRET_LEN, PA_IS_BLANK

if not pa.is_secret_blank():
    # clear settings associated with this key, since it will be no more
    settings.blank()

    # save a blank secret (all zeros is a special case, detected by bootloader)
    dis.fullscreen('Wipe Seed!')
    nv = bytes(AE_SECRET_LEN)
    pa.change(new_secret=nv)

    rv = pa.setup(pa.pin)
    pa.login()

    assert pa.is_secret_blank()

# reset top menu and go there
from actions import goto_top_menu
goto_top_menu()

numpad.abort_ux()
Beispiel #12
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)
Beispiel #13
0
async def start_login_sequence():
    # Boot up login sequence here.
    #
    from main import pa, settings, dis, loop, numpad
    from ux import idle_logout

    if pa.is_blank():
        # Blank devices, with no PIN set all, can continue w/o login

        # Do green-light set immediately after firmware upgrade
        if version.is_fresh_version():
            pa.greenlight_firmware()
            dis.show()

        goto_top_menu()
        return

    # maybe show a nickname before we do anything
    nickname = settings.get('nick', None)
    if nickname:
        try:
            await show_nickname(nickname)
        except: pass

    # Allow impatient devs and crazy people to skip the PIN
    guess = settings.get('_skip_pin', None)
    if guess is not None:
        try:
            dis.fullscreen("(Skip PIN)")
            pa.setup(guess)
            pa.login()
        except: pass

    # if that didn't work, or no skip defined, force
    # them to login succefully.
    while not pa.is_successful():
        # always get a PIN and login first
        await block_until_login()

    # Must re-read settings after login
    settings.set_key()
    settings.load()

    # implement "login countdown" feature
    delay = settings.get('lgto', 0)
    if delay:
        pa.reset()
        await login_countdown(delay)
        await block_until_login()

    # implement idle timeout now that we are logged-in
    loop.create_task(idle_logout())

    # Do green-light set immediately after firmware upgrade
    if not pa.is_secondary:
        if version.is_fresh_version():
            pa.greenlight_firmware()
            dis.show()

    # Populate xfp/xpub values, if missing.
    # - can happen for first-time login of duress wallet
    # - may indicate lost settings, which we can easily recover from
    # - these values are important to USB protocol
    if not (settings.get('xfp', 0) and settings.get('xpub', 0)) and not pa.is_secret_blank():
        try:
            import stash

            # Recalculate xfp/xpub values (depends both on secret and chain)
            with stash.SensitiveValues() as sv:
                sv.capture_xpub()
        except Exception as exc:
            # just in case, keep going; we're not useless and this
            # is early in boot process
            print("XFP save failed: %s" % exc)

    # If HSM policy file is available, offer to start that,
    # **before** the USB is even enabled.
    if version.has_fatram:
        try:
            import hsm, hsm_ux

            if hsm.hsm_policy_available():
                ar = await hsm_ux.start_hsm_approval(usb_mode=False, startup_mode=True)
                if ar:
                    await ar.interact()
        except: pass

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

    goto_top_menu()