Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ask for overwrite if something on smartcard #270

Merged
3 changes: 3 additions & 0 deletions src/gui/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ def init_styles(dark=True):
lv.style_copy(styles["small"], styles["hint"])
styles["small"].text.color = ctxt

styles["warning"] = lv.style_t()
lv.style_copy(styles["warning"], th.style.label.prim)
styles["warning"].text.color = lv.color_hex(0xFF9A00)

def add_label(text, y=PADDING, scr=None, style=None, width=None):
"""Helper functions that creates a title-styled label"""
Expand Down
19 changes: 18 additions & 1 deletion src/gui/screens/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class Prompt(Screen):
def __init__(self, title="Are you sure?", message="Make a choice",
confirm_text="Confirm", cancel_text="Cancel", note=None):
confirm_text="Confirm", cancel_text="Cancel", note=None, warning=None):
super().__init__()
self.title = add_label(title, scr=self, style="title")
if note is not None:
Expand All @@ -17,6 +17,9 @@ def __init__(self, title="Are you sure?", message="Make a choice",
self.page.set_size(480, 600)
self.message = add_label(message, scr=self.page)
self.page.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 0)
# Initialize an empty icon label. It will display nothing until a symbol is set.
self.icon = lv.label(self)
self.icon.set_text("")

(self.cancel_button, self.confirm_button) = add_button_pair(
cancel_text,
Expand All @@ -25,3 +28,17 @@ def __init__(self, title="Are you sure?", message="Make a choice",
on_release(cb_with_args(self.set_value, True)),
scr=self,
)

if warning:
self.warning = add_label(warning, scr=self, style="warning")
# Display warning symbol in the icon label
self.icon.set_text(lv.SYMBOL.WARNING)

# Align warning text
y_pos = self.cancel_button.get_y() - 60 # above the buttons
x_pos = self.get_width() // 2 - self.warning.get_width() // 2 # in the center of the prompt
self.warning.set_pos(x_pos, y_pos)

# Align warning icon to the left of the title
self.icon.align(self.title, lv.ALIGN.IN_LEFT_MID, 90, 0)

74 changes: 48 additions & 26 deletions src/keystore/memorycard.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
from .javacard.util import get_connection
from platform import CriticalErrorWipeImmediately
import platform
from rng import get_random_bytes
from embit import bip39
from helpers import tagged_hash, aead_encrypt, aead_decrypt
import hmac
from gui.screens import Alert, Progress, Menu, MnemonicScreen, Prompt
from gui.screens import Alert, Progress, Menu, Prompt
import asyncio
from io import BytesIO
from uscard import SmartcardException
from binascii import hexlify
import lvgl as lv

Expand Down Expand Up @@ -228,15 +226,35 @@ def _set_pin(self, pin):

async def save_mnemonic(self):
await self.check_card(check_pin=True)
encrypt = await self.show(Prompt("Encrypt the secret?",
data_saved, encrypted, decryptable, same_mnemonic = self.get_secret_info()
if data_saved:
if not decryptable:
msg = ("There is data on the card, but its nature is unknown since we are unable to decrypt it.\n\n"
"Thus, we cannot confirm whether a mnemonic is already saved on your card or if it matches the one you are about to save.")
elif same_mnemonic:
msg = ("The mnemonic you are about to save is already stored on the smart card.\n"
"If you proceed, the existing data will be overwritten with the same mnemonic.\n\n"
"This can be useful if you want to store this mnemonic in a different form (plaintext vs. encrypted) on the card.\n\n"
"Currently, your mnemonic is saved in {} form on the card.".format('encrypted' if encrypted else 'plaintext'))
else:
msg = ("A different mnemonic is already saved on the card.\n\n"
"Continuing will replace the existing mnemonic with the one you are about to save.")

confirm = await self.show(Prompt("Overwrite data?",
"\n%s" % msg + "\n\nDo you want to continue?", 'Continue', warning="Irreversibly overwrite the data on the card"
))
if not confirm:
return
keep_as_plain_text = await self.show(Prompt("Encrypt the secret?",
"\nIf you encrypt the secret on the card "
"it will only work with this device.\n\n"
"Otherwise it will be readable on any device "
"it will only work with the device you are currently using.\n\n"
"If you keep it as plain text, it will be readable on any Specter DIY device "
"after you enter the PIN code.\n\n"
"Keep in mind that with encryption enabled "
"wiping the device makes the secret unusable!",
confirm_text="Yes, encrypt",
cancel_text="Keep as plain text"))
"Activating encryption means that if the device is wiped, the stored secret on the card becomes inaccessible.",
confirm_text="Keep as plain text",
cancel_text="Encrypt",
))
encrypt = not keep_as_plain_text
self.show_loader("Saving secret to the card...")
d = self.serialize_data(
{"entropy": bip39.mnemonic_to_bytes(self.mnemonic)},
Expand All @@ -246,6 +264,13 @@ async def save_mnemonic(self):
self._is_key_saved = True
# check it's ok
await self.load_mnemonic()
await self.show(
Alert(
"Success!",
"Your key is stored on the smartcard now.",
button_text="OK",
)
)

@property
def is_key_saved(self):
Expand Down Expand Up @@ -359,13 +384,6 @@ async def storage_menu(self):
return False
elif menuitem == 0:
await self.save_mnemonic()
await self.show(
Alert(
"Success!",
"Your key is stored on the smartcard now.",
button_text="OK",
)
)
elif menuitem == 1:
await self.load_mnemonic()
await self.show(
Expand Down Expand Up @@ -407,23 +425,27 @@ async def storage_menu(self):
else:
raise KeyStoreError("Invalid menu")

async def show_card_info(self):
note = "Card fingerprint: %s" % self.hexid
version = "%s v%s" % (self.applet.NAME, self.applet.version)
platform = self.applet.platform
def get_secret_info(self):
data = self.applet.get_secret()
key_saved = len(data) > 0
data_saved = len(data) > 0
encrypted = True
decryptable = True
same_mnemonic = False
if key_saved:
if data_saved:
try:
d, encrypted = self.parse_data(data)
if "entropy" in d:
self._is_key_saved = True
same_mnemonic = (self.mnemonic == bip39.mnemonic_from_bytes(d["entropy"]))
except KeyStoreError as e:
except KeyStoreError:
decryptable = False
return data_saved, encrypted, decryptable, same_mnemonic

async def show_card_info(self):
note = "Card fingerprint: %s" % self.hexid
version = "%s v%s" % (self.applet.NAME, self.applet.version)
platform = self.applet.platform
data_saved, encrypted, decryptable, same_mnemonic = self.get_secret_info()
# yes = lv.SYMBOL.OK+" Yes"
# no = lv.SYMBOL.CLOSE+" No"
yes = "Yes"
Expand All @@ -433,9 +455,9 @@ async def show_card_info(self):
"Implementation: %s" % platform,
"Version: %s" % version,
"\n#7f8fa4 KEY INFO: #",
"Key saved: " + (yes if key_saved else no),
"Card has data: " + (yes if data_saved else no),
]
if key_saved:
if data_saved:
if decryptable:
props.append("Same as current key: " + (yes if same_mnemonic else no))
props.append("Encrypted: " + (yes if encrypted else no))
Expand Down