Never store 'seeds' in ephemeral settings

(cherry picked from commit e1ac204e04)
This commit is contained in:
scgbckbone 2023-10-05 19:07:51 +02:00 committed by doc-hex
parent cfe86f5363
commit 73fa603200
5 changed files with 120 additions and 62 deletions

View File

@ -70,12 +70,10 @@ from version import mk_num, is_devmode
# settings linked to seed
# LINKED_SETTINGS = ["multisig", "tp", "ovc", "xfp", "xpub", "words"]
# settings that does not make sense to copy to ephemeral secret
# settings that does not make sense to copy to temporary secret
# LINKED_SETTINGS += ["sd2fa", "usr", "axi", "hsmcmd"]
# prelogin settings - do not need to be part of other saved settings
# PRELOGIN_SETTINGS = ["_skip_pin", "nick", "rngk", "lgto", "kbtn", "terms_ok"]
# seed vault is singleton
SINGLE_SETTINGS = ["seedvault", "seeds"]
# keep these settings only if unspecified on the other end
KEEP_IF_BLANK_SETTINGS = ["bkpw", "wa", "sighshchk", "emu", "rz",
"axskip", "del", "pms", "idle_to", "b39skip"]
@ -91,6 +89,31 @@ def MK4_FILENAME(slot):
return MK4_WORKDIR + ('%03x.aes' % slot)
# master current dict
master_sv_data = None
master_nvram_key = None
def extract_master_sv_data(settings_dict):
# allows us to specify what we want to keep
# in global 'master_sv_data' variable
# and save RAM
if settings_dict is None:
return
seeds = settings_dict.get("seeds", [])
seedvault = settings_dict.get("seedvault", 0)
if (not seedvault) and (not seeds):
return
return {"seeds": seeds, "seedvault": seedvault,
"xfp": settings_dict.get("xfp", 0)}
def set_master_sv_data(data, key):
global master_sv_data, master_nvram_key
master_sv_data = extract_master_sv_data(data)
master_nvram_key = key
class SettingsObject:
def __init__(self, dis=None):
@ -307,6 +330,9 @@ class SettingsObject:
self.current['chain'] = 'XTN'
def get(self, kn, default=None):
if master_sv_data and kn in ["seeds", "seedvault"]:
# we are in temporary mode as global 'master_sv_data' is populated
return master_sv_data.get(kn, default)
return self.current.get(kn, default)
def changed(self):
@ -331,12 +357,6 @@ class SettingsObject:
def merge_previous_active(self, previous):
if previous:
for k in SINGLE_SETTINGS:
if k not in previous:
self.current.pop(k, None)
else:
self.current[k] = previous[k]
for k in KEEP_IF_BLANK_SETTINGS:
if previous.get(k, None) and not self.current.get(k, None):
self.current[k] = previous[k]

View File

@ -416,6 +416,7 @@ class PinAttempt:
# Main secret has changed: reset the settings+their key,
# and capture xfp/xpub
# if None is provided as raw_secret -> restore to main seed
import nvstore
from glob import settings
stash.SensitiveValues.clear_cache()
@ -447,9 +448,10 @@ class PinAttempt:
sv.chain = chain
if raw_secret is None:
# restore to main wallet
nv = sv.encoded_secret()
settings.set_key(nv)
settings.nvram_key = nvstore.master_nvram_key
settings.load()
# blank global as we return to main seed
nvstore.set_master_sv_data(None, None)
else:
sv.capture_xpub()
except stash.ZeroSecretException:
@ -461,15 +463,22 @@ class PinAttempt:
self.state_flags |= (1 << 4)
return
settings.merge_previous_active(old_values)
def tmp_secret(self, encoded, chain=None, bip39pw=''):
# Use indicated secret and stop using the SE; operate like this until reboot
val = bytes(encoded + bytes(AE_SECRET_LEN - len(encoded)))
if self.tmp_value == val:
# noop - already enabled
return False
if not self.tmp_value:
# going from master seed
import nvstore
from glob import settings
nvstore.set_master_sv_data(settings.current,
settings.nvram_key)
self.tmp_value = val
# We're no longer blank. hard to say about duress secret and stuff tho

View File

@ -416,6 +416,8 @@ async def in_seed_vault(xfp=None, seeds=None):
return False
async def add_seed_to_vault(encoded, meta=None):
import nvstore
if not settings.get("seedvault", False):
# seed vault disabled
return
@ -426,24 +428,21 @@ async def add_seed_to_vault(encoded, meta=None):
_,_,node = SecretStash.decode(encoded)
new_xfp = swab32(node.my_fp())
new_xfp_str = xfp2str(new_xfp)
tmp_val = None
# 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)
if nvstore.master_sv_data:
main_xfp = nvstore.master_sv_data.get("xfp", 0)
else:
main_xfp = settings.get("xfp", 0)
if new_xfp == main_xfp:
return
# if in ephemeral 'seeds' are grabbed from master_sv_data global var
seeds = settings.get("seeds", [])
xfp_ui = "[%s]" % new_xfp_str
@ -454,26 +453,34 @@ async def add_seed_to_vault(encoded, meta=None):
ch = await ux_show_story(story, escape="1")
if ch == "1":
eph_nvram_key = None
seeds.append((new_xfp_str,
stash.SecretStash.storage_encode(encoded),
xfp_ui,
meta))
if nvstore.master_sv_data:
# in ephemeral
nvstore.master_sv_data["seeds"] = seeds
eph_nvram_key = settings.nvram_key[:]
settings.nvram_key = nvstore.master_nvram_key
settings.load()
settings.set("seeds", seeds)
settings.save()
if eph_nvram_key:
# restore ephemerlal settings after save to main settings
settings.nvram_key = eph_nvram_key
settings.load()
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()
return True
async def set_ephemeral_seed(encoded, chain=None, summarize_ux=True, bip39pw='',
is_restore=False, meta=None):
if not is_restore:
await add_seed_to_vault(encoded, meta=meta)
dis.fullscreen("Wait...")
applied = pa.tmp_secret(encoded, chain=chain, bip39pw=bip39pw)
dis.progress_bar_show(1)
@ -551,6 +558,7 @@ async def set_seed_extended_key(extended_key):
async def set_ephemeral_seed_extended_key(extended_key, meta=None):
encoded, chain = xprv_to_encoded_secret(extended_key)
dis.fullscreen("Applying...")
await set_ephemeral_seed(encoded=encoded, chain=chain, meta=meta)
goto_top_menu()
@ -805,6 +813,7 @@ class SeedVaultMenu(MenuSystem):
@staticmethod
async def _clear(menu, label, item):
import nvstore
from glob import dis, settings
idx, xfp_str, encoded = item.arg
@ -822,13 +831,11 @@ class SeedVaultMenu(MenuSystem):
dis.fullscreen("Saving...")
tmp_val = None
wipe_slot = (ch != "1")
tmp_val = False
if pa.tmp_value:
# active ephemeral seed
assert (encoded == stash.SecretStash.storage_encode(pa.tmp_value))
tmp_val = pa.tmp_value[:]
tmp_val = True
if wipe_slot:
# are we deleting current active ephemeral wallet
@ -836,31 +843,47 @@ class SeedVaultMenu(MenuSystem):
# slot wiping
if tmp_val:
# wipe current settings
master_nvram_key = nvstore.master_nvram_key
settings.blank()
else:
# in main settings
settings.save()
pa.tmp_secret(pad_raw_secret(encoded))
master_nvram_key = settings.nvram_key[:]
settings.set_key(pad_raw_secret(encoded))
settings.load()
settings.blank()
if pa.tmp_value:
await restore_to_main_secret(preserve_settings=True)
# back to master settings
pa.tmp_value = False
settings.nvram_key = master_nvram_key
settings.load()
nvstore.set_master_sv_data(None, None)
eph_nvram_key = None
# will get global master_sv_data from nvstore if in ephemeral
seeds = settings.get("seeds", [])
try:
del seeds[idx]
settings.set("seeds", seeds)
settings.save()
except IndexError: pass
finally:
if tmp_val and (not wipe_slot):
# we were in ephemeral mode before and have not
# 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()
except IndexError:
pass
if nvstore.master_sv_data:
# in ephemeral
nvstore.master_sv_data["seeds"] = seeds
eph_nvram_key = settings.nvram_key[:]
settings.nvram_key = nvstore.master_nvram_key
settings.load()
settings.set("seeds", seeds)
settings.save()
if tmp_val and (not wipe_slot):
# we were in ephemeral mode before and have not
# wiped seed - return back to ephemeral
settings.nvram_key = eph_nvram_key
settings.load()
elif tmp_val and wipe_slot:
goto_top_menu()
# pop menu stack
the_ux.pop()
@ -881,17 +904,12 @@ class SeedVaultMenu(MenuSystem):
@staticmethod
async def _rename(menu, label, item):
# let them edit the name
import nvstore
from glob import dis
idx, xfp_str = item.arg
tmp_val = None
if pa.tmp_value:
tmp_val = pa.tmp_value[:]
dis.fullscreen("Wait...")
settings.save()
await restore_to_main_secret(preserve_settings=True)
# will get global master_sv_data from nvstore if in ephemeral
seeds = settings.get("seeds", [])
chk_xfp, encoded, old_name, meta = seeds[idx]
assert chk_xfp == xfp_str
@ -900,18 +918,26 @@ class SeedVaultMenu(MenuSystem):
new_name = await ux_input_text(old_name, confirm_exit=False, max_len=40)
if not new_name:
new_name = old_name
return
dis.fullscreen("Saving...")
# save it
eph_nvram_key = None
seeds[idx] = (chk_xfp, encoded, new_name, meta)
if nvstore.master_sv_data:
# in ephemeral
nvstore.master_sv_data["seeds"] = seeds
eph_nvram_key = settings.nvram_key[:]
settings.nvram_key = nvstore.master_nvram_key
settings.load()
settings.set("seeds", seeds)
settings.save()
if tmp_val:
pa.tmp_secret(tmp_val)
settings.set("seeds", seeds)
settings.save()
if eph_nvram_key:
# restore ephemerlal settings after save to main settings
settings.nvram_key = eph_nvram_key
settings.load()
# update label in sub-menu
menu.items[0].label = new_name

View File

@ -9,6 +9,7 @@ import stash
from seed import set_seed_value
from utils import xfp2str
from actions import goto_top_menu
import nvstore
tn = chains.BitcoinTestnet
@ -21,6 +22,8 @@ settings.set('idle_to', 0)
import main
pa.tmp_value = None
nvstore.master_sv_data = None
nvstore.master_nvram_key = None
set_seed_value(main.WORDS)
print("New key in effect: %s" % settings.get('xpub', 'MISSING'))

View File

@ -750,7 +750,7 @@ def test_activate_current_tmp_secret(reset_seed_words, goto_eph_seed_menu,
])
def test_seed_vault_menus(dev, data, settings_set, settings_get, pick_menu_item, need_keypress, cap_story,
cap_menu, reset_seed_words, get_identity_story, get_seed_value_ux, fake_txn,
try_sign, sim_exec, goto_home, goto_eph_seed_menu):
try_sign, sim_exec, goto_home):
# Verify "seed vault" feature works as intended
reset_seed_words()
xfp, entropy, mnemonic = data
@ -775,8 +775,8 @@ def test_seed_vault_menus(dev, data, settings_set, settings_get, pick_menu_item,
pick_menu_item("Seed Vault")
time.sleep(.1)
_, story = cap_story()
assert "Enable Seed Vault?" in story
need_keypress("y")
if "Enable Seed Vault?" in story:
need_keypress("y")
time.sleep(.1)
pick_menu_item("Enable")
time.sleep(.5)