From 542dcd32c7788dd7c268b8749c3381c986e5b6e5 Mon Sep 17 00:00:00 2001 From: scgbckbone Date: Thu, 25 Jun 2026 10:26:32 +0200 Subject: [PATCH] revert SSSP bypass PIN login --- releases/Next-ChangeLog.md | 1 - shared/actions.py | 13 +- testing/login_settings_tests.py | 213 ++++++++++++++++++++++++++++++-- 3 files changed, 211 insertions(+), 16 deletions(-) diff --git a/releases/Next-ChangeLog.md b/releases/Next-ChangeLog.md index df7dd7db..4142101b 100644 --- a/releases/Next-ChangeLog.md +++ b/releases/Next-ChangeLog.md @@ -33,7 +33,6 @@ This lists the new changes that have not yet been published in a normal release. - Bugfix: Non-standard OP_RETURN outputs shown as "null-data", hiding part of the script - Bugfix: Over-limit CCC address-whitelist import was rejected but still modified the policy - Bugfix: Deleting a file right after renaming it (List Files) blanked the old name, leaving the renamed file -- Bugfix: SSSP bypass PIN alone could complete login into a no-secret session. Second prompt now requires a PIN that loads secrets. - Bugfix: Reordered `multi(...)` multisig with same keys was misreported as name-only change. Now blocked as duplicate. - Bugfix: Max WIF store capacity limit was ignored if saving via QR WIF visualization - Bugfix: Force Seed XOR restore from Temporary Seed menu to remain temporary even when master seed is blank diff --git a/shared/actions.py b/shared/actions.py index 259294e0..ffff63d0 100644 --- a/shared/actions.py +++ b/shared/actions.py @@ -820,15 +820,12 @@ async def start_login_sequence(): sp_unlock = tp.was_sp_unlock() if sp_unlock: # Trying to unlock spending policy: ask for main PIN next. - while True: - await ux_show_story("Spending Policy Unlock: Please provide Main PIN next.") - pa.reset() - await block_until_login() - if pa.has_secrets(): - break + await ux_show_story("Spending Policy Unlock: Please provide Main PIN next.") + pa.reset() + await block_until_login() - # Main or duress wallet PINs are acceptable here, but zero-secret - # trick PINs are not enough to disable spending policy. + # we don't really know if that was the Main PIN (could easily be the bypass + # PIN again) and if it's a duress wallet, that's cool... # Do we need to do countdown delay? (real or otherwise) # - wiping has already occurred if that was selected by trick details diff --git a/testing/login_settings_tests.py b/testing/login_settings_tests.py index 3652370a..2f635bab 100644 --- a/testing/login_settings_tests.py +++ b/testing/login_settings_tests.py @@ -501,7 +501,7 @@ def test_login_integration(request, nick, randomize, login_ctdwn, kill_btn, kill def test_calc_login(request): is_Q = request.config.getoption('--Q') if not is_Q: raise pytest.skip("Q only") - + clean_sim_data() # remove all from previous sim = ColdcardSimulator(args=["--q1"]) sim.start(start_wait=6) @@ -734,13 +734,10 @@ def test_sssp_bypass_pin_alone_no_login(request): _login(device, is_Q, bypass_pin) # bypass PIN a 2nd time, instead of main PIN time.sleep(1.0) - # With the bug the device lands on the EmptyWallet menu (no-secret session). - # With the fix the zero-secret PIN is rejected and login does not complete. + # device lands on the EmptyWallet menu (no-secret session). scr = _cap_screen(device) - assert "New Seed Words" not in scr - assert "Import Existing" not in scr - - assert "provide Main PIN" in scr + assert "New Seed Words" in scr + assert "Import Existing" in scr sim.stop() device.close() @@ -895,4 +892,206 @@ def test_sssp_trick_pins(request): sim.stop() device.close() + +def test_trick_countdown_twice(request): + # countdown TP used again after countdown does not land in empty seed menu + ct_pin = "89-89" + is_Q = request.config.getoption('--Q') + headless = request.config.getoption('--headless') + clean_sim_data() # remove all from previous + sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"], + headless=headless) + sim.start(start_wait=6) + device = ColdcardDevice(is_simulator=True) + _login(device, is_Q, "22-22") + + _pick_menu_item(device, is_Q, "Settings") + _pick_menu_item(device, is_Q, "Login Settings") + _pick_menu_item(device, is_Q, "Trick PINs") + + _pick_menu_item(device, is_Q, "Add New Trick") + time.sleep(.1) + + for ch in ct_pin[:2]: + _need_keypress(device, ch) + time.sleep(.1) + _press_select(device, is_Q) + + if not is_Q: + # anti-phishing words + _press_select(device, is_Q) + + for ch in ct_pin[-2:]: + _need_keypress(device, ch) + time.sleep(.1) + _press_select(device, is_Q) + + _pick_menu_item(device, is_Q, "Login Countdown") + _press_select(device, is_Q) + time.sleep(.1) + + _pick_menu_item(device, is_Q, "Just Countdown") + for _ in range(2): + _press_select(device, is_Q) + time.sleep(.1) + + # adjust countdown to lowest possible value + _pick_menu_item(device, is_Q, f'↳{ct_pin}') + _pick_menu_item(device, is_Q, '↳Countdown') + _need_keypress(device, "4") + _pick_menu_item(device, is_Q, " 5 minutes") + + time.sleep(2) + sim.stop() + device.close() + + sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"], + headless=headless) + sim.start(start_wait=6) + device = ColdcardDevice(is_simulator=True) + + _login(device, is_Q, ct_pin) + time.sleep(.15) + scr = " ".join(_cap_screen(device).split("\n")) + assert "Login countdown in effect" in scr + assert "Must wait:" in scr + assert "5s" in scr + time.sleep(6) + + _login(device, is_Q, ct_pin) + time.sleep(.15) + scr = _cap_screen(device) + assert "New Seed Words" in scr + assert "Import Existing" in scr + + sim.stop() + device.close() + + +def test_wipe_countdown_trick_pin_finishes_blank(request): + ct_pin = "89-90" + is_Q = request.config.getoption('--Q') + headless = request.config.getoption('--headless') + clean_sim_data() # remove all from previous + sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"], + headless=headless) + sim.start(start_wait=6) + device = ColdcardDevice(is_simulator=True) + _login(device, is_Q, "22-22") + + _pick_menu_item(device, is_Q, "Settings") + _pick_menu_item(device, is_Q, "Login Settings") + _pick_menu_item(device, is_Q, "Trick PINs") + + _pick_menu_item(device, is_Q, "Add New Trick") + time.sleep(.1) + + for ch in ct_pin[:2]: + _need_keypress(device, ch) + time.sleep(.1) + _press_select(device, is_Q) + + if not is_Q: + # anti-phishing words + _press_select(device, is_Q) + + for ch in ct_pin[-2:]: + _need_keypress(device, ch) + time.sleep(.1) + _press_select(device, is_Q) + + _pick_menu_item(device, is_Q, "Login Countdown") + _press_select(device, is_Q) + time.sleep(.1) + + _pick_menu_item(device, is_Q, "Wipe & Countdown") + for _ in range(2): + _press_select(device, is_Q) + time.sleep(.1) + + # adjust countdown to lowest possible value + _pick_menu_item(device, is_Q, f'↳{ct_pin}') + _pick_menu_item(device, is_Q, '↳Countdown') + _need_keypress(device, "4") + _pick_menu_item(device, is_Q, " 5 minutes") + + time.sleep(2) + sim.stop() + device.close() + + sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"], + headless=headless) + sim.start(start_wait=6) + device = ColdcardDevice(is_simulator=True) + + _login(device, is_Q, ct_pin) + time.sleep(.15) + scr = " ".join(_cap_screen(device).split("\n")) + assert "Login countdown in effect" in scr + assert "Must wait:" in scr + assert "5s" in scr + time.sleep(6) + + _login(device, is_Q, ct_pin) + time.sleep(3) + m = _cap_menu(device) + assert "New Seed Words" in m + assert "Import Existing" in m + + sim.stop() + device.close() + + +def test_look_blank_trick_pin_empty_menu(request): + blank_pin = "55-55" + is_Q = request.config.getoption('--Q') + clean_sim_data() # remove all from previous + sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"]) + sim.start(start_wait=6) + device = ColdcardDevice(is_simulator=True) + _login(device, is_Q, "22-22") + + _pick_menu_item(device, is_Q, "Settings") + _pick_menu_item(device, is_Q, "Login Settings") + _pick_menu_item(device, is_Q, "Trick PINs") + + _pick_menu_item(device, is_Q, "Add New Trick") + time.sleep(.1) + + for ch in blank_pin[:2]: + _need_keypress(device, ch) + time.sleep(.1) + _press_select(device, is_Q) + + if not is_Q: + # anti-phishing words + _press_select(device, is_Q) + + for ch in blank_pin[-2:]: + _need_keypress(device, ch) + time.sleep(.1) + _press_select(device, is_Q) + + _pick_menu_item(device, is_Q, "Look Blank") + for _ in range(2): + _press_select(device, is_Q) + time.sleep(.1) + + time.sleep(2) + sim.stop() + device.close() + + sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"]) + sim.start(start_wait=6) + device = ColdcardDevice(is_simulator=True) + + _login(device, is_Q, blank_pin) + time.sleep(3) + m = _cap_menu(device) + assert "New Seed Words" in m + assert "Import Existing" in m + + sim.stop() + device.close() + # EOF