def goto_top_menu(): # Start/restart menu system import version from menu import MenuSystem from flow import VirginSystem, NormalSystem, EmptyWallet, FactoryMenu from main import pa if version.is_factory_mode(): m = MenuSystem(FactoryMenu) elif pa.is_blank(): # let them play a little before picking a PIN first time m = MenuSystem(VirginSystem, should_cont=lambda: pa.is_blank()) else: assert pa.is_successful(), "nonblank but wrong pin" m = MenuSystem(EmptyWallet if pa.is_secret_blank() else NormalSystem) the_ux.reset(m) return m
def set_key(self, new_secret=None): # System settings (not secrets) are stored in SPI Flash, encrypted with this # key that is derived from main wallet secret. Call this method when the secret # is first loaded, or changes for some reason. from main import pa from stash import blank_object key = None mine = False if not new_secret: if not pa.is_successful() or pa.is_secret_blank(): # simple fixed key allows us to store a few things when logged out key = b'\0' * 32 else: # read secret and use it. new_secret = pa.fetch() mine = True if new_secret: # hash up the secret... without decoding it or similar assert len(new_secret) >= 32 s = tcc.sha256(new_secret) for round in range(5): s.update('pad') s = tcc.sha256(s.digest()) key = s.digest() if mine: blank_object(new_secret) # for restore from backup case, or when changing (created) the seed self.nvram_key = key
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()
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'])
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()