From a9c402cba3f4643e478de9a723be2cf134d47b83 Mon Sep 17 00:00:00 2001 From: scgbckbone Date: Sat, 25 Nov 2023 17:11:24 +0100 Subject: [PATCH] allow passphrase via USB if passphrase already set (work on master seed in that case); show password over USB UX change (cherry picked from commit 4a39fc82f1686089854976d3537562c5e3e59c78) --- releases/ChangeLog.md | 1 + shared/auth.py | 32 +++++++++----------- shared/drv_entro.py | 2 +- shared/usb.py | 3 +- testing/test_bip39pw.py | 65 ++++++++++++++++++++++++++++++++++++----- 5 files changed, 74 insertions(+), 29 deletions(-) diff --git a/releases/ChangeLog.md b/releases/ChangeLog.md index d9ce84c5..157613f4 100644 --- a/releases/ChangeLog.md +++ b/releases/ChangeLog.md @@ -10,6 +10,7 @@ - Enhancement: `12 Words` menu option preferred on the top of the menu in all the seed menus (rather than 24 words). - Enhancement: One instant retry on SE1 comm failures +- Enhancement: Allow passphrase via USB if passphrase already set - operates on master seed. - Bugfix: Handle any failures in slot reading when loading settings - Bugfix: Add missing First Time UX for extended key import as master seed - Bugfix: Hide `Upgrade Firmware` menu item if temporary seed is active diff --git a/shared/auth.py b/shared/auth.py index 2255536b..1cd68caf 100644 --- a/shared/auth.py +++ b/shared/auth.py @@ -1199,29 +1199,24 @@ class NewPassphrase(UserAuthorizedAction): title = "Passphrase" bypass_tmp = True escape = "2" - showit = False while 1: - if showit: - ch = await ux_show_story('Given:\n\n%s\n\nShould we switch to that wallet now?' - '\n\nOK to continue, X to cancel.' % self._pw, title=title) - else: - 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. " - - msg += ('Press (2) to view the provided passphrase.\n\n' - 'OK to continue, X to cancel.') - ch = await ux_show_story(msg=msg % len(self._pw), title=title, escape=escape) + 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. " + msg += ('Press (2) to view the provided passphrase.\n\n' + 'OK to continue, X to cancel.') + ch = await ux_show_story(msg=msg % len(self._pw), title=title, escape=escape) if ch == '2': - showit = True + await ux_show_story('Provided:\n\n%s\n\n' % self._pw, title=title) continue - elif ch == '1': - bypass_tmp = False + else: + if ch == '1': + bypass_tmp = False - break + break try: if ch not in 'y1': @@ -1237,7 +1232,6 @@ class NewPassphrase(UserAuthorizedAction): self.result = settings.get('xpub') - except BaseException as exc: self.failed = "Exception" sys.print_exception(exc) diff --git a/shared/drv_entro.py b/shared/drv_entro.py index 4d453427..190c8acc 100644 --- a/shared/drv_entro.py +++ b/shared/drv_entro.py @@ -257,7 +257,7 @@ async def drv_entro_step2(_1, picked, _2): dis.fullscreen("Applying...") from actions import goto_top_menu from glob import settings - xfp_str = xfp2str(settings.get("xfp")) + xfp_str = xfp2str(settings.get("xfp", 0)) await seed.set_ephemeral_seed( encoded, meta='BIP85 Derived from [%s], index=%d' % (xfp_str, index) diff --git a/shared/usb.py b/shared/usb.py index 67695dd3..867ac3a9 100644 --- a/shared/usb.py +++ b/shared/usb.py @@ -619,10 +619,11 @@ class USBHandler: if cmd == 'pass': # bip39 passphrase provided, maybe use it if authorized assert self.encrypted_req, 'must encrypt' + import stash from auth import start_bip39_passphrase from glob import settings - assert settings.get('words', True), 'no seed' + assert settings.get('words', True) or stash.bip39_passphrase, '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 4d717a49..b0d268f0 100644 --- a/testing/test_bip39pw.py +++ b/testing/test_bip39pw.py @@ -54,17 +54,19 @@ def test_b9p_basic(pw, set_bip39_pw): def set_bip39_pw(dev, need_keypress, reset_seed_words, cap_story, sim_execfile): - def doit(pw, reset=True, seed_vault=False): + def doit(pw, reset=True, seed_vault=False, on_tmp=False): # reset from previous runs if reset: words = reset_seed_words() else: conts = sim_execfile('devtest/get-secrets.py') - assert 'mnemonic' in conts - for l in conts.split("\n"): - if l.startswith("mnemonic ="): - words = l.split("=")[-1].strip().replace('"', '') - break + if 'mnemonic' in conts: + for l in conts.split("\n"): + if l.startswith("mnemonic ="): + words = l.split("=")[-1].strip().replace('"', '') + break + else: + words = simulator_fixed_words # optimization if pw == '': @@ -84,8 +86,15 @@ def set_bip39_pw(dev, need_keypress, reset_seed_words, cap_story, time.sleep(0.050) title, body = cap_story() assert pw in body + need_keypress("y") # go back - need_keypress('y') + time.sleep(.1) + title, body = cap_story() + if on_tmp: + assert "Press (1)" in body + need_keypress("1") + else: + need_keypress("y") time.sleep(.3) title, story = cap_story() @@ -308,6 +317,46 @@ def test_bip39pass_on_ephemeral_seed(generate_ephemeral_words, import_ephemeral_ 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 [0F056943]" in story + + +@pytest.mark.parametrize("stype", ["words", "xprv", "b39pw"]) +def test_bip39pass_on_ephemeral_seed_usb(generate_ephemeral_words, import_ephemeral_xprv, + need_keypress, pick_menu_item, goto_home, + 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() + + goto_eph_seed_menu() + + if stype == "words": + # words + sec = generate_ephemeral_words(24, from_main=True, seed_vault=False) + parent_words = " ".join(sec) + elif stype == "b39pw": + base_pw = "random_pw" + parent_words = simulator_fixed_words + set_bip39_pw(base_pw, reset=False, on_tmp=False) + else: + # node + import_ephemeral_xprv("sd", from_main=True, seed_vault=False) + + goto_home() + if stype == "xprv": + with pytest.raises(Exception) as e: + set_bip39_pw(passphrase, reset=False) + assert "no seed" in e.value.args[0] + return + + parent = Mnemonic.to_seed(parent_words, passphrase=passphrase) + parent_node = BIP32Node.from_master_secret(parent, netcode="XTN") + xpub = parent_node.hwif() + set_bip39_pw(passphrase, reset=False, on_tmp=True if stype == "words" else False) + ident_story = get_identity_story() + assert xpub in ident_story + # EOF