bugfix: incorrect xfp shown in meta for passphrase on top of eph secret
(cherry picked from commit 8d304f408a)
This commit is contained in:
parent
fdbfc85fd4
commit
ccc0ac039c
@ -223,7 +223,7 @@ DangerZoneMenu = [
|
||||
on_change=change_seed_vault,
|
||||
story=("Enable Seed Vault? Adds prompt to store ephemeral secrets "
|
||||
"into Seed Vault, where they can easily be reused later.\n\n"
|
||||
"WARNING: Seed Vault is encrypted (AES-256-CTR) by master seed,"
|
||||
"WARNING: Seed Vault is encrypted (AES-256-CTR) by ephemeral master seed,"
|
||||
" but not held directly inside secure elements. Backups are required"
|
||||
" after any change to vault! Recommended for experiments or temporary use."),
|
||||
predicate=has_secrets),
|
||||
|
||||
@ -230,8 +230,7 @@ class SettingsObject:
|
||||
def _used_slots(self):
|
||||
# mk4: faster list of slots in use; doesn't open them
|
||||
files = os.listdir(MK4_WORKDIR)
|
||||
x = [int(fn[0:-4], 16) for fn in files if fn.endswith('.aes')]
|
||||
return x
|
||||
return [int(fn[0:-4], 16) for fn in files if fn.endswith('.aes')]
|
||||
|
||||
def _nonempty_slots(self, dis=None):
|
||||
# generate slots that are non-empty
|
||||
|
||||
@ -428,18 +428,22 @@ async def add_seed_to_vault(encoded, meta=None):
|
||||
new_xfp_str = xfp2str(new_xfp)
|
||||
tmp_val = None
|
||||
|
||||
# do not offer to store main seed
|
||||
if new_xfp == settings.get("xfp", 0):
|
||||
return
|
||||
|
||||
# do not offer to store secrets that are already in vault
|
||||
if await in_seed_vault(new_xfp_str, settings.get("seeds", [])):
|
||||
return
|
||||
|
||||
if pa.tmp_value:
|
||||
dis.fullscreen("Wait...")
|
||||
tmp_val = pa.tmp_value[:]
|
||||
await restore_to_main_secret(preserve_settings=True)
|
||||
|
||||
# do not offer to store main seed
|
||||
if new_xfp == settings.get("xfp", 0):
|
||||
if tmp_val:
|
||||
pa.tmp_secret(tmp_val)
|
||||
|
||||
return
|
||||
|
||||
seeds = settings.get("seeds", [])
|
||||
|
||||
xfp_ui = "[%s]" % new_xfp_str
|
||||
@ -459,6 +463,7 @@ async def add_seed_to_vault(encoded, meta=None):
|
||||
await ux_show_story(xfp_ui + "\nSaved to Seed Vault")
|
||||
|
||||
if tmp_val:
|
||||
dis.fullscreen("Wait...")
|
||||
pa.tmp_secret(tmp_val)
|
||||
settings.set("seeds", seeds)
|
||||
settings.save()
|
||||
@ -649,15 +654,23 @@ def set_seed_value(words=None, encoded=None, chain=None):
|
||||
|
||||
async def calc_bip39_passphrase(pw, bypass_tmp=False):
|
||||
from glob import dis, settings
|
||||
from pincodes import pa
|
||||
|
||||
dis.fullscreen("Working...")
|
||||
current_xfp = settings.get("xfp", 0)
|
||||
# generate secret
|
||||
# get xfp of parent reliably - cannot go to settings for this if in ephemeral
|
||||
if pa.tmp_value:
|
||||
with stash.SensitiveValues(bypass_tmp=bypass_tmp) as sv:
|
||||
assert sv.mode == 'words'
|
||||
current_xfp = swab32(sv.node.my_fp())
|
||||
else:
|
||||
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)
|
||||
assert sv.mode == 'words'
|
||||
nv = SecretStash.encode(xprv=sv.node)
|
||||
xfp = swab32(sv.node.my_fp())
|
||||
|
||||
return nv, xfp, current_xfp
|
||||
|
||||
async def set_bip39_passphrase(pw, bypass_tmp=False, summarize_ux=True):
|
||||
@ -801,7 +814,7 @@ class SeedVaultMenu(MenuSystem):
|
||||
"only remove from seed vault and keep "
|
||||
"encrypted settings for later use.\n\n"
|
||||
"WARNING: Funds will be lost if wallet is"
|
||||
"not backed up elsewhere.")
|
||||
" not backed up elsewhere.")
|
||||
|
||||
ch = await ux_show_story(title="[" + xfp_str + "]",
|
||||
msg=msg, escape="1")
|
||||
@ -842,10 +855,12 @@ class SeedVaultMenu(MenuSystem):
|
||||
finally:
|
||||
if tmp_val and (not wipe_slot):
|
||||
# we were in ephemeral mode before and have not
|
||||
# wiped seed - return back to ephemral
|
||||
# wiped seed - return back to ephemeral
|
||||
pa.tmp_secret(tmp_val)
|
||||
settings.set("seeds", seeds)
|
||||
settings.save()
|
||||
elif tmp_val and wipe_slot:
|
||||
goto_top_menu()
|
||||
|
||||
# pop menu stack
|
||||
the_ux.pop()
|
||||
@ -989,18 +1004,12 @@ class EphemeralSeedMenu(MenuSystem):
|
||||
MenuItem("Import XPRV", f=import_xprv, arg=True), # ephemeral=True
|
||||
MenuItem("Tapsigner Backup", f=import_tapsigner_backup_file, arg=True), # ephemeral=True
|
||||
]
|
||||
if pa.tmp_value:
|
||||
xfp = settings.get("xfp", "")
|
||||
if xfp:
|
||||
rv.insert(0, MenuItem("[%s]" % xfp2str(xfp)))
|
||||
else:
|
||||
rv.insert(0, MenuItem("[Active]"))
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
async def make_ephemeral_seed_menu(*a):
|
||||
if not (pa.tmp_value or settings.get("seedvault", False)):
|
||||
if (not pa.tmp_value) and (not settings.get("seedvault", False)):
|
||||
# force a warning on them, unless they are already doing it.
|
||||
ch = await ux_show_story(
|
||||
"Ephemeral seed is a temporary secret completely separate "
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
#
|
||||
# BIP-39 seed word encryption
|
||||
#
|
||||
import pdb
|
||||
|
||||
import pytest, time, struct
|
||||
from pycoin.key.BIP32Node import BIP32Node
|
||||
from binascii import a2b_hex
|
||||
@ -202,19 +204,29 @@ def test_lockdown(stype, pick_menu_item, set_bip39_pw, goto_home, cap_story,
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stype", ["words", "xprv"])
|
||||
@pytest.mark.parametrize("passphrase", ["@coinkite rulez!!", "!@#!@", "AAAAAAAAAAA"])
|
||||
@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, passphrase):
|
||||
enter_complex, cap_story, cap_menu, on_eph,
|
||||
settings_set, seed_vault):
|
||||
passphrase = "@coinkite rulez!!"
|
||||
reset_seed_words()
|
||||
settings_set("seedvault", 1)
|
||||
settings_set("seeds", [])
|
||||
|
||||
goto_eph_seed_menu()
|
||||
|
||||
if stype == "words":
|
||||
# words
|
||||
sec = generate_ephemeral_words(24, from_main=True)
|
||||
sec = generate_ephemeral_words(24, from_main=True, seed_vault=seed_vault)
|
||||
parent = Mnemonic.to_seed(" ".join(sec))
|
||||
parent_node = BIP32Node.from_master_secret(parent)
|
||||
parent_fp = parent_node.fingerprint().hex().upper()
|
||||
else:
|
||||
# node
|
||||
sec = import_ephemeral_xprv("sd", from_main=True)
|
||||
sec = import_ephemeral_xprv("sd", from_main=True, seed_vault=seed_vault)
|
||||
|
||||
goto_home()
|
||||
if stype == "xprv":
|
||||
@ -235,13 +247,58 @@ def test_bip39pass_on_ephemeral_seed(generate_ephemeral_words, import_ephemeral_
|
||||
expect0 = BIP32Node.from_master_secret(seed0)
|
||||
assert expect0.fingerprint().hex().upper() == xfp0
|
||||
assert "press (2) to add passphrase to the current active ephemeral seed" in story
|
||||
need_keypress("2")
|
||||
time.sleep(.5)
|
||||
|
||||
if on_eph:
|
||||
need_keypress("2")
|
||||
time.sleep(.5)
|
||||
title, story = cap_story()
|
||||
xfp1 = title[1:-1]
|
||||
seed1 = Mnemonic.to_seed(" ".join(sec), passphrase=passphrase)
|
||||
expect1 = BIP32Node.from_master_secret(seed1)
|
||||
assert expect1.fingerprint().hex().upper() == xfp1
|
||||
assert "press (2)" not in story
|
||||
need_keypress("y")
|
||||
else:
|
||||
need_keypress("y")
|
||||
|
||||
time.sleep(.3)
|
||||
title, story = cap_story()
|
||||
xfp1 = title[1:-1]
|
||||
seed1 = Mnemonic.to_seed(" ".join(sec), passphrase=passphrase)
|
||||
expect1 = BIP32Node.from_master_secret(seed1)
|
||||
assert expect1.fingerprint().hex().upper() == xfp1
|
||||
assert "press (2)" not in story
|
||||
to_check = None
|
||||
if seed_vault:
|
||||
assert "Press (1) to store ephemeral secret into Seed Vault" in story
|
||||
need_keypress("1")
|
||||
time.sleep(.2)
|
||||
title, story = cap_story()
|
||||
assert "Saved to Seed Vault" in story
|
||||
if on_eph:
|
||||
assert xfp1 in story
|
||||
to_check = xfp1
|
||||
else:
|
||||
assert xfp0 in story
|
||||
to_check = xfp0
|
||||
need_keypress("y")
|
||||
else:
|
||||
assert "Press (1)" not in story
|
||||
|
||||
if seed_vault:
|
||||
# check correct meta in seed vault
|
||||
pick_menu_item("Seed Vault")
|
||||
m = cap_menu()
|
||||
for i in m:
|
||||
if to_check in i:
|
||||
pick_menu_item(i)
|
||||
break
|
||||
else:
|
||||
pytest.fail("not in menu")
|
||||
|
||||
# choose first info item in submenu
|
||||
need_keypress("y")
|
||||
time.sleep(.1)
|
||||
_, story = cap_story()
|
||||
assert to_check in story
|
||||
if on_eph:
|
||||
assert ("BIP-39 Passphrase on [%s]" % parent_fp) in story
|
||||
else:
|
||||
assert "BIP-39 Passphrase on [0F056943]" in story
|
||||
|
||||
# EOF
|
||||
|
||||
@ -109,7 +109,10 @@ def goto_eph_seed_menu(goto_home, pick_menu_item, cap_story, need_keypress):
|
||||
|
||||
title, story = cap_story()
|
||||
if title == "WARNING":
|
||||
assert "temporary secret stored solely in device RAM" in story
|
||||
assert "Ephemeral seed is a temporary secret completely separate from the master seed" in story
|
||||
assert "typically held in device RAM" in story
|
||||
assert "not persisted between reboots in the Secure Element." in story
|
||||
assert "Enable the Seed Vault feature to store these secrets longer-term." in story
|
||||
assert "Press (4) to prove you read to the end of this message and accept all consequences." in story
|
||||
need_keypress("4") # understand consequences
|
||||
|
||||
@ -185,7 +188,11 @@ def verify_ephemeral_secret_ui(cap_story, need_keypress, cap_menu, dev, fake_txn
|
||||
|
||||
menu = cap_menu()
|
||||
|
||||
assert expected_xfp in menu[0] if expected_xfp else True
|
||||
if expected_xfp:
|
||||
assert expected_xfp in menu[0]
|
||||
else:
|
||||
assert menu[0].startswith("[") # ephemeral xfp
|
||||
|
||||
assert menu[1] == "Ready To Sign" # returned to main menu
|
||||
assert menu[-1] == "Restore Master" # restore main from ephemeral
|
||||
|
||||
@ -193,13 +200,13 @@ def verify_ephemeral_secret_ui(cap_story, need_keypress, cap_menu, dev, fake_txn
|
||||
pick_menu_item("Seed Vault")
|
||||
time.sleep(.1)
|
||||
sc_menu = cap_menu()
|
||||
assert "Restore Master" in sc_menu
|
||||
item = [i for i in sc_menu if in_effect_xfp in i][0]
|
||||
pick_menu_item(item)
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert "Delete" in m
|
||||
assert "Rename" in m
|
||||
assert "Restore Master" in m
|
||||
assert len(m) == 4 # xfp is top item (works same as "Use this seed")
|
||||
if "Use This Seed" in m:
|
||||
pick_menu_item("Use This Seed")
|
||||
@ -230,7 +237,6 @@ def verify_ephemeral_secret_ui(cap_story, need_keypress, cap_menu, dev, fake_txn
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert item not in m
|
||||
assert "Restore Master" not in m
|
||||
else:
|
||||
# Seed Vault disabled
|
||||
m = cap_menu()
|
||||
@ -264,12 +270,6 @@ def verify_ephemeral_secret_ui(cap_story, need_keypress, cap_menu, dev, fake_txn
|
||||
try_sign(psbt, accept=True, finalize=True) # MUST NOT raise
|
||||
need_keypress("y")
|
||||
|
||||
goto_eph_seed_menu()
|
||||
time.sleep(0.1)
|
||||
menu = cap_menu()
|
||||
# ephemeral seed chosen -> [xfp] will be visible
|
||||
assert menu[0] == f"[{ident_xfp}]"
|
||||
|
||||
restore_main_seed(preserve_settings)
|
||||
|
||||
goto_eph_seed_menu()
|
||||
@ -712,6 +712,7 @@ def test_activate_current_tmp_secret(reset_seed_words, goto_eph_seed_menu,
|
||||
time.sleep(0.2)
|
||||
title, story = cap_story()
|
||||
assert 'ephemeral master key is in effect now' in story
|
||||
|
||||
in_effect_xfp = title[1:-1]
|
||||
need_keypress("y")
|
||||
goto_eph_seed_menu()
|
||||
@ -723,6 +724,13 @@ def test_activate_current_tmp_secret(reset_seed_words, goto_eph_seed_menu,
|
||||
word_menu_entry(words.split())
|
||||
time.sleep(0.2)
|
||||
title, story = cap_story()
|
||||
if seed_vault:
|
||||
assert "Press (1) to store ephemeral secret into Seed Vault" in story
|
||||
# do not save
|
||||
need_keypress("y")
|
||||
time.sleep(0.2)
|
||||
title, story = cap_story()
|
||||
|
||||
assert "Ephemeral master key already in use" in story
|
||||
already_used_xfp = title[1:-1]
|
||||
assert already_used_xfp == in_effect_xfp == expected_xfp
|
||||
@ -936,7 +944,7 @@ def test_seed_vault_captures(request, dev, settings_set, settings_get, pick_menu
|
||||
|
||||
title, story = cap_story()
|
||||
assert 'New ephemeral master key' in story
|
||||
assert 'power down' in story
|
||||
assert 'power down' not in story
|
||||
assert xfp in title
|
||||
need_keypress("y") # confirm activation of ephemeral secret
|
||||
|
||||
@ -954,4 +962,147 @@ def test_seed_vault_captures(request, dev, settings_set, settings_get, pick_menu
|
||||
settings_set("seedvault", None)
|
||||
settings_set("seeds", [])
|
||||
|
||||
|
||||
def test_seed_vault_modifications(settings_set, reset_seed_words, pick_menu_item,
|
||||
generate_ephemeral_words, import_ephemeral_xprv,
|
||||
goto_home, cap_story, cap_menu, restore_main_seed,
|
||||
need_keypress):
|
||||
reset_seed_words()
|
||||
settings_set("seedvault", 1)
|
||||
settings_set("seeds", [])
|
||||
|
||||
generate_ephemeral_words(num_words=24, seed_vault=True)
|
||||
generate_ephemeral_words(num_words=12, seed_vault=True)
|
||||
import_ephemeral_xprv("sd", seed_vault=True)
|
||||
import_ephemeral_xprv("sd", seed_vault=True)
|
||||
|
||||
goto_home()
|
||||
pick_menu_item("Seed Vault")
|
||||
m = cap_menu()
|
||||
# 4 entries + Restore Master (as we are in ephemeral)
|
||||
assert len(m) == 5
|
||||
|
||||
# restore to main seed
|
||||
restore_main_seed(seed_vault=True, preserve_settings=True)
|
||||
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
# no ephemeral xfp at the top
|
||||
assert m[0] == "Ready To Sign"
|
||||
pick_menu_item("Seed Vault")
|
||||
# we are no longer in ephemral
|
||||
assert "Restore Master" not in m
|
||||
# first entry in menu
|
||||
need_keypress("y")
|
||||
m = cap_menu()
|
||||
assert "Rename" in m
|
||||
assert "Use This Seed" in m # we are in master - so this must be there
|
||||
assert "Delete" in m
|
||||
|
||||
# delete entry 0
|
||||
pick_menu_item("Delete")
|
||||
need_keypress("y")
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert len(m) == 3
|
||||
|
||||
# first entry again
|
||||
need_keypress("y")
|
||||
pick_menu_item("Rename")
|
||||
for _ in range(9):
|
||||
need_keypress("x")
|
||||
need_keypress("1") # big letters
|
||||
need_keypress("9")
|
||||
need_keypress("1")
|
||||
# name changed to AA
|
||||
need_keypress("y")
|
||||
|
||||
m = cap_menu()
|
||||
assert m[0] == "AA"
|
||||
assert "Rename" in m
|
||||
assert "Use This Seed" in m # we are in master - so this must be there
|
||||
assert "Delete" in m
|
||||
|
||||
# go back
|
||||
need_keypress("x")
|
||||
# second item
|
||||
need_keypress("8")
|
||||
need_keypress("y")
|
||||
time.sleep(.1)
|
||||
pick_menu_item("Use This Seed")
|
||||
title, _ = cap_story()
|
||||
need_keypress("y") # confirm new eph
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert m[0] == title
|
||||
pick_menu_item("Seed Vault")
|
||||
need_keypress("8")
|
||||
need_keypress("y")
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert "Rename" in m
|
||||
assert "In Use" in m
|
||||
assert "Delete" in m
|
||||
|
||||
pick_menu_item("Rename")
|
||||
for _ in range(9):
|
||||
need_keypress("x")
|
||||
need_keypress("1") # big letters
|
||||
need_keypress("9")
|
||||
need_keypress("1")
|
||||
need_keypress("9")
|
||||
need_keypress("1")
|
||||
# name changed to AAA
|
||||
need_keypress("y")
|
||||
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert m[0] == "AAA"
|
||||
pick_menu_item("Delete")
|
||||
need_keypress("y")
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
# after we delete from seed vault together with its settings
|
||||
# we're back to master secret
|
||||
assert m[0] == "Ready To Sign"
|
||||
pick_menu_item("Seed Vault")
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert len(m) == 2
|
||||
|
||||
need_keypress("8")
|
||||
need_keypress("y")
|
||||
pick_menu_item("Use This Seed")
|
||||
title, _ = cap_story()
|
||||
need_keypress("y") # confirm new eph
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert m[0] == title
|
||||
pick_menu_item("Seed Vault")
|
||||
need_keypress("8")
|
||||
need_keypress("y")
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert "Rename" in m
|
||||
assert "In Use" in m
|
||||
assert "Delete" in m
|
||||
|
||||
pick_menu_item("Delete")
|
||||
need_keypress("1") # only delete from seed vault
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert len(m) == 2
|
||||
need_keypress("y")
|
||||
# this is now different eph - modification not allowed
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
assert "Rename" not in m
|
||||
assert "Delete" not in m
|
||||
assert "Use This Seed" in m
|
||||
goto_home()
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
# still in ephemeral
|
||||
assert title == m[0]
|
||||
|
||||
# EOF
|
||||
|
||||
@ -127,7 +127,7 @@ def restore_seed_xor(set_seed_words, goto_home, pick_menu_item, cap_story,
|
||||
time.sleep(0.01)
|
||||
title, body = cap_story()
|
||||
|
||||
assert 'New ephemeral master key in effect' in body
|
||||
assert 'New ephemeral master key is in effect now' in body
|
||||
need_keypress("y")
|
||||
assert get_secrets()['mnemonic'] == expect
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user