From 3eee67764849d992fea4c9cf78e732181874b70d Mon Sep 17 00:00:00 2001 From: scgbckbone Date: Wed, 10 Apr 2024 21:03:35 +0200 Subject: [PATCH] remove ability to use Passphrase if passphrase already in use; remove ability to choose to which seed apply passphrase - always apply to tmp seed if active and possible --- releases/ChangeLog.md | 4 +++ releases/QChangeLog.md | 4 +++ shared/auth.py | 36 +++++++++---------------- shared/flow.py | 13 ++++----- shared/seed.py | 60 ++++++----------------------------------- shared/usb.py | 3 +-- testing/test_bip39pw.py | 45 +++++++------------------------ testing/test_ux.py | 2 ++ 8 files changed, 47 insertions(+), 120 deletions(-) diff --git a/releases/ChangeLog.md b/releases/ChangeLog.md index 8cf1c864..07230ed8 100644 --- a/releases/ChangeLog.md +++ b/releases/ChangeLog.md @@ -1,5 +1,9 @@ ## 5.2.3 - 2024-XX-XX +- Change: `Passphrase` menu item is no longer offered if BIP39 passphrase + already in use. Use `Restore Master` with ability to keep or purge current + passphrase wallet settings. +- Change: Removed ability to add passphrase to master seed if active temporary seed. - Bugfix: Saving passphrase on SD Card caused a freeze that required reboot - Bugfix: Properly handle and finalize framing error response - Bugfix: `Brick Me` option for `If Wrong` PIN caused yikes diff --git a/releases/QChangeLog.md b/releases/QChangeLog.md index 2e001258..0c2aeb79 100644 --- a/releases/QChangeLog.md +++ b/releases/QChangeLog.md @@ -25,6 +25,10 @@ ## 1.1.1Q - 2024-04-XX +- Change: `Passphrase` menu item is no longer offered if BIP39 passphrase + already in use. Use `Restore Master` with ability to keep or purge current + passphrase wallet settings. +- Change: Removed ability to add passphrase to master seed if active temporary seed. - Bugfix: Handle ZeroSecretException for BIP39 passphrase calculation when on temporary seed without master secret. - Bugfix: Battery idle timeout also considers last progress bar update diff --git a/shared/auth.py b/shared/auth.py index 017fe758..18f1027c 100644 --- a/shared/auth.py +++ b/shared/auth.py @@ -1207,35 +1207,27 @@ class NewPassphrase(UserAuthorizedAction): from pincodes import pa title = "Passphrase" - bypass_tmp = True - escape = "x2" + KEY_CANCEL + escape = "yx2" + KEY_CANCEL + KEY_ENTER while 1: msg = ('BIP-39 passphrase (%d chars long) has been provided over ' - 'USB connection. Should we switch to that wallet now?\n\n') - if pa.tmp_value and settings.get("words", True): - escape += "1" - msg += "Press (1) to add passphrase to currently active temporary seed. " + 'USB connection. Should we switch to that wallet now?\n\n' + 'Press OK to add passphrase ' % len(self._pw)) + if pa.tmp_value: + msg += "to current active temporary seed. " + else: + msg += "to master seed. " - if not pa.is_secret_blank() and settings.master_get("words", True): - escape += "y" + KEY_ENTER - msg += "Press OK to add passphrase to master seed. " + msg += 'Press (2) to view the provided passphrase. X to cancel.' - msg += ('Press (2) to view the provided passphrase.\n\n' - 'X to cancel.') - - ch = await ux_show_story(msg=msg % len(self._pw), title=title, - escape=escape, strict_escape=True) + ch = await ux_show_story(msg=msg, title=title, escape=escape, + strict_escape=True) if ch == '2': await ux_show_story('Provided:\n\n%s\n\n' % self._pw, title=title) continue - else: - if ch == '1': - bypass_tmp = False - - break + else: break try: - if ch not in 'y1'+ KEY_ENTER: + if ch not in ('y'+ KEY_ENTER): # they don't want to! self.refused = True await ux_dramatic_pause("Refused.", 1) @@ -1243,9 +1235,7 @@ class NewPassphrase(UserAuthorizedAction): from seed import set_bip39_passphrase # full screen message shown: "Working..." - await set_bip39_passphrase(self._pw, bypass_tmp=bypass_tmp, - summarize_ux=False) - + await set_bip39_passphrase(self._pw, summarize_ux=False) self.result = settings.get('xpub') except BaseException as exc: diff --git a/shared/flow.py b/shared/flow.py index d247e777..fd97b1bf 100644 --- a/shared/flow.py +++ b/shared/flow.py @@ -85,10 +85,8 @@ def se2_and_real_secret(): from pincodes import pa return (not pa.is_secret_blank()) and (not pa.tmp_value) -def bip39_passphrase_active(): - import stash - return settings.get('words', True) \ - or (settings.master_get('words', True) and stash.bip39_passphrase) +def word_based_seed(): + return settings.get("words", True) HWTogglesMenu = [ @@ -243,7 +241,7 @@ DebugFunctionsMenu = [ SeedXORMenu = [ # xxxxxxxxxxxxxxxx - MenuItem("Split Existing", f=xor_split_start, predicate=lambda: settings.get('words', True)), + MenuItem("Split Existing", f=xor_split_start, predicate=word_based_seed), MenuItem("Restore Seed XOR", f=xor_restore_start), ] @@ -252,8 +250,7 @@ SeedFunctionsMenu = [ MenuItem('Seed XOR', menu=SeedXORMenu), MenuItem("Destroy Seed", f=clear_seed, predicate=se2_and_real_secret), MenuItem('Lock Down Seed', f=convert_ephemeral_to_master), - MenuItem('Export SeedQR', f=export_seedqr, - predicate=lambda: settings.get('words', True)), + MenuItem('Export SeedQR', f=export_seedqr, predicate=word_based_seed), ] DangerZoneMenu = [ @@ -382,7 +379,7 @@ EmptyWallet = [ NormalSystem = [ # xxxxxxxxxxxxxxxx MenuItem('Ready To Sign', f=ready2sign, shortcut='r'), - MenuItem('Passphrase', f=start_b39_pw, predicate=bip39_passphrase_active, shortcut='p'), + MenuItem('Passphrase', f=start_b39_pw, predicate=word_based_seed, shortcut='p'), MenuItem('Scan Any QR Code', predicate=lambda: version.has_qr, shortcut=KEY_QR, f=scan_any_qr, arg=(False, True)), MenuItem('Start HSM Mode', f=start_hsm_menu_item, predicate=hsm_policy_available), diff --git a/shared/seed.py b/shared/seed.py index 56f8c615..f8aad991 100644 --- a/shared/seed.py +++ b/shared/seed.py @@ -662,12 +662,7 @@ async def calc_bip39_passphrase(pw, bypass_tmp=False): dis.fullscreen("Working...") - # get xfp of parent reliably - cannot go to settings for this if in ephemeral - current_xfp = settings.get("xfp", 0) if not pa.tmp_value else 0 - if not current_xfp: - with stash.SensitiveValues(bypass_tmp=bypass_tmp) as sv: - assert sv.mode == 'words', sv.mode - current_xfp = swab32(sv.node.my_fp()) + current_xfp = settings.get("xfp", 0) with stash.SensitiveValues(bip39pw=pw, bypass_tmp=bypass_tmp) as sv: # can't do it without original seed words (late, but caller has checked) @@ -1266,53 +1261,14 @@ class PassphraseMenu(MenuSystem): cls.pp_sofar = '' async def apply_pass_value(new_pp): - # Apply provided BIP-39 passphrase to master seed, and go to top menu. - mdata = None - tdata = None + # Apply provided BIP-39 passphrase to master or current active tmp seed + # and go to top menu. + nv, xfp, parent_xfp = await calc_bip39_passphrase(new_pp) + xfp_str = xfp2str(xfp) + parent_xfp_str = xfp2str(parent_xfp) - try: - m_nv, m_xfp, m_parent_xfp = await calc_bip39_passphrase(new_pp, - bypass_tmp=True) - m_parent_xfp_str = xfp2str(m_parent_xfp) - m_xfp_str = xfp2str(m_xfp) - mdata = ( - m_nv, m_xfp, m_xfp_str, m_parent_xfp_str, - "master seed [%s]" % m_parent_xfp_str, - "(1) master+pass:\n%s→%s\n\n" % (m_parent_xfp_str, m_xfp_str), - ) - except (AssertionError, ZeroSecretException): pass - - if pa.tmp_value and settings.get("words", True): - # we have ephemeral seed - can add passphrase to it as it is word based - t_nv, t_xfp, t_parent_xfp = await calc_bip39_passphrase(new_pp, - bypass_tmp=False) - t_parent_xfp_str = xfp2str(t_parent_xfp) - t_xfp_str = xfp2str(t_xfp) - tdata = ( - t_nv, t_xfp, t_xfp_str, t_parent_xfp_str, - "current active temporary seed [%s]" % t_parent_xfp_str, - "(2) tmp+pass:\n%s→%s\n\n" % (t_parent_xfp_str, t_xfp_str), - ) - - if tdata is None and mdata is None: - # if master is not word based, temporary has to be, otherwise "Passphrase" - # not offered in menu - # should never be seen by user because flow.py::bip39_passphrase_active - await ux_show_story(title="FAILED", msg="Need word based secret") - return - - tmp = False - if tdata and mdata: - ch = await ux_show_story(mdata[-1] + tdata[-1], escape='12x', - strict_escape=True, scrollbar=False) - if ch == "x": return # exit - if ch == "2": - tmp = True - elif tdata: - tmp = True - - data = tdata if tmp else mdata - nv, xfp, xfp_str, parent_xfp_str, msg, _ = data + msg = "current active temporary seed [%s]" if pa.tmp_value else "master seed [%s]" + msg = msg % parent_xfp_str msg = ('Above is the master key fingerprint of the new wallet' ' created by adding passphrase to %s.' diff --git a/shared/usb.py b/shared/usb.py index 2f5a793e..5a809825 100644 --- a/shared/usb.py +++ b/shared/usb.py @@ -552,11 +552,10 @@ class USBHandler: if cmd == 'pass': # bip39 passphrase provided, maybe use it if authorized assert self.encrypted_req, 'must encrypt' - from flow import bip39_passphrase_active from auth import start_bip39_passphrase from glob import settings - assert bip39_passphrase_active(), 'no seed' + assert settings.get("words", True), 'no seed' assert len(args) < 400, 'too long' pw = str(args, 'utf8') assert len(pw) < 100, 'too long' diff --git a/testing/test_bip39pw.py b/testing/test_bip39pw.py index 97a9ccaf..f6ccd613 100644 --- a/testing/test_bip39pw.py +++ b/testing/test_bip39pw.py @@ -93,10 +93,11 @@ def set_bip39_pw(dev, need_keypress, reset_seed_words, cap_story, time.sleep(.1) title, body = cap_story() if on_tmp: - assert "Press (1)" in body - need_keypress("1") + assert "to current active temporary seed" in body else: - press_select() + assert "to master seed" in body + + press_select() time.sleep(.3) title, story = cap_story() @@ -232,12 +233,11 @@ def test_lockdown(stype, pick_menu_item, set_bip39_pw, goto_home, cap_story, @pytest.mark.parametrize("stype", ["words", "xprv"]) -@pytest.mark.parametrize("on_eph", [True, False]) @pytest.mark.parametrize("seed_vault", [True, False]) def test_bip39pass_on_ephemeral_seed(generate_ephemeral_words, import_ephemeral_xprv, need_keypress, pick_menu_item, goto_home, reset_seed_words, goto_eph_seed_menu, stype, - enter_complex, cap_story, cap_menu, on_eph, + enter_complex, cap_story, cap_menu, settings_set, seed_vault, press_select): passphrase = "@coinkite rulez!!" reset_seed_words() @@ -246,8 +246,6 @@ def test_bip39pass_on_ephemeral_seed(generate_ephemeral_words, import_ephemeral_ goto_eph_seed_menu() - sim_fp = xfp2str(simulator_fixed_xfp) - if stype == "words": # words sec = generate_ephemeral_words(24, from_main=True, seed_vault=seed_vault) @@ -268,38 +266,18 @@ def test_bip39pass_on_ephemeral_seed(generate_ephemeral_words, import_ephemeral_ pick_menu_item("Passphrase") press_select() enter_complex(passphrase, apply=True) - time.sleep(.1) - title, choice_story = cap_story() tmp_seed = Mnemonic.to_seed(" ".join(sec), passphrase=passphrase) tmp_node = BIP32Node.from_master_secret(tmp_seed) tmp_fp = tmp_node.fingerprint().hex().upper() - master_seed = Mnemonic.to_seed(simulator_fixed_words, passphrase=passphrase) - master_node = BIP32Node.from_master_secret(master_seed) - master_fp = master_node.fingerprint().hex().upper() - - choice_msg = "(1) master+pass:\n%s→%s\n\n" % (sim_fp, master_fp) - choice_msg += "(2) tmp+pass:\n%s→%s\n\n" % (parent_fp, tmp_fp) - - assert choice_story == choice_msg - - if on_eph: - need_keypress("2") - else: - need_keypress("1") - time.sleep(.2) title, story = cap_story() title_xfp = title[1:-1] assert "created by adding passphrase to" in story - if on_eph: - assert tmp_fp == title_xfp - assert f"current active temporary seed [{parent_fp}]" in story - else: - assert master_fp == title_xfp - assert f"master seed [{sim_fp}]" in story + assert tmp_fp == title_xfp + assert f"current active temporary seed [{parent_fp}]" in story press_select() @@ -333,10 +311,7 @@ def test_bip39pass_on_ephemeral_seed(generate_ephemeral_words, import_ephemeral_ time.sleep(.1) _, story = cap_story() assert title_xfp in story - if on_eph: - assert ("BIP-39 Passphrase on [%s]" % parent_fp) in story - else: - assert "BIP-39 Passphrase on [0F056943]" in story + assert ("BIP-39 Passphrase on [%s]" % parent_fp) in story @pytest.mark.parametrize("stype", ["words", "xprv", "b39pw"]) @@ -345,9 +320,9 @@ def test_bip39pass_on_ephemeral_seed_usb(generate_ephemeral_words, import_epheme reset_seed_words, goto_eph_seed_menu, stype, cap_story, cap_menu, set_bip39_pw, get_identity_story, settings_set): - settings_set("seedvault", 0) passphrase = "@coinkite rulez!!" reset_seed_words() + settings_set("seedvault", 0) goto_eph_seed_menu() @@ -364,7 +339,7 @@ def test_bip39pass_on_ephemeral_seed_usb(generate_ephemeral_words, import_epheme import_ephemeral_xprv("sd", from_main=True, seed_vault=False) goto_home() - if stype == "xprv": + if stype in ("xprv", "b39pw"): with pytest.raises(Exception) as e: set_bip39_pw(passphrase, reset=False) assert "no seed" in e.value.args[0] diff --git a/testing/test_ux.py b/testing/test_ux.py index d0237003..0cae7bb5 100644 --- a/testing/test_ux.py +++ b/testing/test_ux.py @@ -627,6 +627,8 @@ def test_bip39_complex(target, goto_home, pick_menu_item, cap_story, enter_complex(target, apply=True) press_select() verify_ephemeral_secret_ui(xpub=expect.hwif(), is_b39pw=True) + pick_menu_item("Restore Master") + press_select() @pytest.mark.qrcode