Skip to content

Commit

Permalink
improve flash and sd keystore, remove legacy export to SD
Browse files Browse the repository at this point in the history
  • Loading branch information
stepansnigirev committed Nov 15, 2022
1 parent e5bc8b7 commit 5b8ca01
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 155 deletions.
180 changes: 128 additions & 52 deletions src/keystore/flash.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from rng import get_random_bytes
from embit import ec, bip39, bip32
from helpers import tagged_hash
from gui.screens import Alert, PinScreen, Menu, MnemonicScreen
from gui.screens import Alert, PinScreen, Menu, MnemonicScreen, InputScreen


class FlashKeyStore(RAMKeyStore):
Expand All @@ -30,7 +30,7 @@ class FlashKeyStore(RAMKeyStore):
NOTE = "Uses internal memory of the microcontroller for all keys."
# Button to go to storage menu
# Menu is implemented in async storage_menu function
storage_button = "Reckless"
storage_button = "Flash storage"
load_button = "Load key from internal memory"

def __init__(self):
Expand Down Expand Up @@ -187,64 +187,144 @@ def _set_pin(self, pin):
# call unlock now
self._unlock(pin)

@property
def flashpath(self):
"""Path to store bitcoin key"""
return self.path

async def init(self, show_fn, show_loader):
"""
Waits for keystore media
and loads internal secret and PIN state
"""
self.show = show_fn
self.show_loader = show_loader
platform.maybe_mkdir(self.path)
self.load_secret(self.path)
self.load_state()
# the rest we can get from parent
await super().init(show_fn, show_loader)

def fileprefix(self, path):
if path is self.flashpath:
return 'reckless'

hexid = hexlify(tagged_hash("sdid", self.secret)[:4]).decode()
return "specterdiy%s" % hexid


async def save_mnemonic(self):
if self.is_locked:
raise KeyStoreError("Keystore is locked")
if self.mnemonic is None:
raise KeyStoreError("Recovery phrase is not loaded")
self.save_aead(
self.flashpath, plaintext=self.mnemonic.encode(), key=self.enc_secret
)
# check it's ok
await self.load_mnemonic()

@property
def flashpath(self):
"""Path to store bitcoin key"""
return self.path
path = self.flashpath
filename = await self.get_input(suggestion=self.mnemonic.split()[0])
if filename is None:
return

fullpath = "%s/%s.%s" % (path, self.fileprefix(path), filename)

if platform.file_exists(fullpath):
scr = Prompt(
"\n\nFile already exists: %s\n" % filename,
"Would you like to overwrite this file?",
)
res = await self.show(scr)
if res is False:
return

self.save_aead(fullpath, plaintext=self.mnemonic.encode(),
key=self.enc_secret)
# check it's ok
await self.load_mnemonic(fullpath)
# return the full file name incl. prefix if saved to SD card, just the name if on flash
return filename

@property
def is_key_saved(self):
return platform.file_exists(self.flashpath)
flash_files = [
f[0] for f in os.ilistdir(self.flashpath)
if f[0].lower().startswith(self.fileprefix(self.flashpath))
]
flash_exists = (len(flash_files) > 0)
return flash_exists

async def load_mnemonic(self):
async def load_mnemonic(self, file=None):
if self.is_locked:
raise KeyStoreError("Keystore is locked")
if not platform.file_exists(self.flashpath):

if file is None:
file = await self.select_file()
if file is None:
return False

if not platform.file_exists(file):
raise KeyStoreError("Key is not saved")
_, data = self.load_aead(self.flashpath, self.enc_secret)
_, data = self.load_aead(file, self.enc_secret)

self.set_mnemonic(data.decode(), "")
return True

async def select_file(self):

buttons = [(None, 'Internal storage')]
buttons += self.load_files(self.flashpath)

return await self.show(Menu(buttons, title="Select a file", last=(None, "Cancel")))

def load_files(self, path):
buttons = []
files = [f[0] for f in os.ilistdir(path) if f[0].startswith(self.fileprefix(path))]

if len(files) == 0:
buttons += [(None, 'No files found')]
else:
files.sort()
for file in files:
displayname = file.replace(self.fileprefix(path), "")
if displayname is "":
displayname = "Default"
else:
displayname = displayname[1:] # strip first character
buttons += [("%s/%s" % (path, file), displayname)]
return buttons

async def delete_mnemonic(self):
if not platform.file_exists(self.flashpath):
raise KeyStoreError("Secret is not saved. No need to delete anything.")
file = await self.select_file()
if file is None:
return False
if not platform.file_exists(file):
raise KeyStoreError("File not found.")
try:
os.remove(self.flashpath)
except:
raise KeyStoreError("Failed to delete from memory")
os.remove(file)
except Exception as e:
print(e)
raise KeyStoreError("Failed to delete file '%s'" % file)
finally:
return True

async def get_input(
self,
title="Enter a name for this seed",
note="Naming your seeds allows you to store multiple.\n"
"Give each seed a unique name!",
suggestion="",
):
scr = InputScreen(title, note, suggestion, min_length=1, strip=True)
await self.show(scr)
return scr.get_value()

async def init(self, show_fn, show_loader):
"""
Waits for keystore media
and loads internal secret and PIN state
"""
self.show = show_fn
self.show_loader = show_loader
platform.maybe_mkdir(self.path)
self.load_secret(self.path)
self.load_state()
# the rest we can get from parent
await super().init(show_fn, show_loader)

async def storage_menu(self):
async def storage_menu(self, title="Manage keys on internal flash"):
"""Manage storage, return True if new key was loaded"""
buttons = [
# id, text
(None, "Key management"),
(0, "Save key to flash"),
(1, "Load key from flash"),
(2, "Delete key from flash"),
(None, title),
(0, "Save key"),
(1, "Load key"),
(2, "Delete key"),
]

# we stay in this menu until back is pressed
Expand All @@ -256,23 +336,19 @@ async def storage_menu(self):
if menuitem == 255:
return False
elif menuitem == 0:
await self.save_mnemonic()
await self.show(
Alert(
"Success!", "Your key is stored in flash now.", button_text="OK"
filename = await self.save_mnemonic()
if filename:
await self.show(
Alert("Success!", "Your key is stored now.\n\nName: %s" % filename, button_text="OK")
)
)
elif menuitem == 1:
await self.load_mnemonic()
await self.show(
Alert("Success!", "Your key is loaded.", button_text="OK")
)
if await self.load_mnemonic():
await self.show(
Alert("Success!", "Your key is loaded.", button_text="OK")
)
return True
elif menuitem == 2:
await self.delete_mnemonic()
await self.show(
Alert(
"Success!", "Your key is deleted from flash.", button_text="OK"
if await self.delete_mnemonic():
await self.show(
Alert("Success!", "Your key is deleted.", button_text="OK")
)
)

107 changes: 4 additions & 103 deletions src/keystore/sdcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import platform
from rng import get_random_bytes
from embit import bip39
from gui.screens import Alert, Progress, Menu, MnemonicScreen, InputScreen, Prompt
from gui.screens import Alert, Progress, Menu, MnemonicScreen, Prompt
import asyncio
from io import BytesIO
from helpers import tagged_hash
Expand Down Expand Up @@ -102,11 +102,8 @@ async def save_mnemonic(self):

@property
def is_key_saved(self):
flash_files = [
f[0] for f in os.ilistdir(self.flashpath)
if f[0].lower().startswith(self.fileprefix(self.flashpath))
]
flash_exists = (len(flash_files) > 0)
flash_exists = super().is_key_saved

if not platform.is_sd_present():
return flash_exists

Expand Down Expand Up @@ -157,23 +154,6 @@ async def select_file(self):

return await self.show(Menu(buttons, title="Select a file", last=(None, "Cancel")))

def load_files(self, path):
buttons = []
files = [f[0] for f in os.ilistdir(path) if f[0].startswith(self.fileprefix(path))]

if len(files) == 0:
buttons += [(None, 'No files found')]
else:
files.sort()
for file in files:
displayname = file.replace(self.fileprefix(path), "")
if displayname is "":
displayname = "Default"
else:
displayname = displayname[1:] # strip first character
buttons += [("%s/%s" % (path, file), displayname)]
return buttons

async def delete_mnemonic(self):

file = await self.select_file()
Expand All @@ -194,85 +174,6 @@ async def delete_mnemonic(self):
platform.unmount_sdcard()
return True

async def get_input(
self,
title="Enter a name for this seed",
note="Naming your seeds allows you to store multiple.\n"
"Give each seed a unique name!",
suggestion="",
):
scr = InputScreen(title, note, suggestion, min_length=1, strip=True)
await self.show(scr)
return scr.get_value()

async def export_mnemonic(self):
if await self.show(Prompt("Warning",
"You need to confirm your PIN code "
"to export your recovery phrase.\n\n"
"Your recovery phrase will be saved "
"to the SD card as plain text.\n\n"
"Anybody who has access to this SD card "
"will be able to read your recovery phrase!\n\n"
"Continue?")):
self.lock()
await self.unlock()

filename = "seed-export-%s.txt" % self.mnemonic.split()[0]
filepath = "%s/%s" % (self.sdpath, filename)

if not platform.is_sd_present():
raise KeyStoreError("SD card is not present")

platform.mount_sdcard()

with open(filepath, "w") as f:
f.write("bip39: ")
f.write(self.mnemonic)

platform.unmount_sdcard()

await self.show(
Alert("Success!", "Your seed is exported.\n\nName: %s" % filename, button_text="OK")
)


async def storage_menu(self):
"""Manage storage, return True if new key was loaded"""
buttons = [
# id, text
(None, "Manage keys on SD card and internal flash"),
(0, "Save key"),
(1, "Load key"),
(2, "Delete key"),
]

# disabled if SD card is not present
buttons.append((3, "Export recovery phrase to SD", platform.is_sd_present()))

# we stay in this menu until back is pressed
while True:
# wait for menu selection
menuitem = await self.show(Menu(buttons, last=(255, None)))
# process the menu button:
# back button
if menuitem == 255:
return False
elif menuitem == 0:
filename = await self.save_mnemonic()
if filename:
await self.show(
Alert("Success!", "Your key is stored now.\n\nName: %s" % filename, button_text="OK")
)
elif menuitem == 1:
if await self.load_mnemonic():
await self.show(
Alert("Success!", "Your key is loaded.", button_text="OK")
)
return True
elif menuitem == 2:
if await self.delete_mnemonic():
await self.show(
Alert("Success!", "Your key is deleted.", button_text="OK")
)
elif menuitem == 3:
await self.export_mnemonic()
return await super().storage_menu(title="Manage keys on SD card and internal flash")

0 comments on commit 5b8ca01

Please sign in to comment.