Skip to content

Commit 2f19b9e

Browse files
ask for overwrite if something on smartcard (#270)
* ask for overwrite if something on smartcard * fix: only show success screen if user did not cancel * add warning style * add warning option to prompt class * improve copy on overwriting data screen + add warning text * add icon to Prompt and add warning icon if there is a warning * rename key_saved to data_saved + change copy to "Card has data" * fix: initialize an empty icon label * make "Keep as plain text" the CTA btn and change copy to explain the risks of encrypting the secret with device --------- Co-authored-by: moneymanolis <[email protected]>
1 parent 207dce3 commit 2f19b9e

File tree

3 files changed

+69
-27
lines changed

3 files changed

+69
-27
lines changed

src/gui/common.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ def init_styles(dark=True):
115115
lv.style_copy(styles["small"], styles["hint"])
116116
styles["small"].text.color = ctxt
117117

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

119122
def add_label(text, y=PADDING, scr=None, style=None, width=None):
120123
"""Helper functions that creates a title-styled label"""

src/gui/screens/prompt.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

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

2124
(self.cancel_button, self.confirm_button) = add_button_pair(
2225
cancel_text,
@@ -25,3 +28,17 @@ def __init__(self, title="Are you sure?", message="Make a choice",
2528
on_release(cb_with_args(self.set_value, True)),
2629
scr=self,
2730
)
31+
32+
if warning:
33+
self.warning = add_label(warning, scr=self, style="warning")
34+
# Display warning symbol in the icon label
35+
self.icon.set_text(lv.SYMBOL.WARNING)
36+
37+
# Align warning text
38+
y_pos = self.cancel_button.get_y() - 60 # above the buttons
39+
x_pos = self.get_width() // 2 - self.warning.get_width() // 2 # in the center of the prompt
40+
self.warning.set_pos(x_pos, y_pos)
41+
42+
# Align warning icon to the left of the title
43+
self.icon.align(self.title, lv.ALIGN.IN_LEFT_MID, 90, 0)
44+

src/keystore/memorycard.py

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@
44
from .javacard.util import get_connection
55
from platform import CriticalErrorWipeImmediately
66
import platform
7-
from rng import get_random_bytes
87
from embit import bip39
98
from helpers import tagged_hash, aead_encrypt, aead_decrypt
109
import hmac
11-
from gui.screens import Alert, Progress, Menu, MnemonicScreen, Prompt
10+
from gui.screens import Alert, Progress, Menu, Prompt
1211
import asyncio
1312
from io import BytesIO
14-
from uscard import SmartcardException
1513
from binascii import hexlify
1614
import lvgl as lv
1715

@@ -228,15 +226,35 @@ def _set_pin(self, pin):
228226

229227
async def save_mnemonic(self):
230228
await self.check_card(check_pin=True)
231-
encrypt = await self.show(Prompt("Encrypt the secret?",
229+
data_saved, encrypted, decryptable, same_mnemonic = self.get_secret_info()
230+
if data_saved:
231+
if not decryptable:
232+
msg = ("There is data on the card, but its nature is unknown since we are unable to decrypt it.\n\n"
233+
"Thus, we cannot confirm whether a mnemonic is already saved on your card or if it matches the one you are about to save.")
234+
elif same_mnemonic:
235+
msg = ("The mnemonic you are about to save is already stored on the smart card.\n"
236+
"If you proceed, the existing data will be overwritten with the same mnemonic.\n\n"
237+
"This can be useful if you want to store this mnemonic in a different form (plaintext vs. encrypted) on the card.\n\n"
238+
"Currently, your mnemonic is saved in {} form on the card.".format('encrypted' if encrypted else 'plaintext'))
239+
else:
240+
msg = ("A different mnemonic is already saved on the card.\n\n"
241+
"Continuing will replace the existing mnemonic with the one you are about to save.")
242+
243+
confirm = await self.show(Prompt("Overwrite data?",
244+
"\n%s" % msg + "\n\nDo you want to continue?", 'Continue', warning="Irreversibly overwrite the data on the card"
245+
))
246+
if not confirm:
247+
return
248+
keep_as_plain_text = await self.show(Prompt("Encrypt the secret?",
232249
"\nIf you encrypt the secret on the card "
233-
"it will only work with this device.\n\n"
234-
"Otherwise it will be readable on any device "
250+
"it will only work with the device you are currently using.\n\n"
251+
"If you keep it as plain text, it will be readable on any Specter DIY device "
235252
"after you enter the PIN code.\n\n"
236-
"Keep in mind that with encryption enabled "
237-
"wiping the device makes the secret unusable!",
238-
confirm_text="Yes, encrypt",
239-
cancel_text="Keep as plain text"))
253+
"Activating encryption means that if the device is wiped, the stored secret on the card becomes inaccessible.",
254+
confirm_text="Keep as plain text",
255+
cancel_text="Encrypt",
256+
))
257+
encrypt = not keep_as_plain_text
240258
self.show_loader("Saving secret to the card...")
241259
d = self.serialize_data(
242260
{"entropy": bip39.mnemonic_to_bytes(self.mnemonic)},
@@ -246,6 +264,13 @@ async def save_mnemonic(self):
246264
self._is_key_saved = True
247265
# check it's ok
248266
await self.load_mnemonic()
267+
await self.show(
268+
Alert(
269+
"Success!",
270+
"Your key is stored on the smartcard now.",
271+
button_text="OK",
272+
)
273+
)
249274

250275
@property
251276
def is_key_saved(self):
@@ -359,13 +384,6 @@ async def storage_menu(self):
359384
return False
360385
elif menuitem == 0:
361386
await self.save_mnemonic()
362-
await self.show(
363-
Alert(
364-
"Success!",
365-
"Your key is stored on the smartcard now.",
366-
button_text="OK",
367-
)
368-
)
369387
elif menuitem == 1:
370388
await self.load_mnemonic()
371389
await self.show(
@@ -407,23 +425,27 @@ async def storage_menu(self):
407425
else:
408426
raise KeyStoreError("Invalid menu")
409427

410-
async def show_card_info(self):
411-
note = "Card fingerprint: %s" % self.hexid
412-
version = "%s v%s" % (self.applet.NAME, self.applet.version)
413-
platform = self.applet.platform
428+
def get_secret_info(self):
414429
data = self.applet.get_secret()
415-
key_saved = len(data) > 0
430+
data_saved = len(data) > 0
416431
encrypted = True
417432
decryptable = True
418433
same_mnemonic = False
419-
if key_saved:
434+
if data_saved:
420435
try:
421436
d, encrypted = self.parse_data(data)
422437
if "entropy" in d:
423438
self._is_key_saved = True
424439
same_mnemonic = (self.mnemonic == bip39.mnemonic_from_bytes(d["entropy"]))
425-
except KeyStoreError as e:
440+
except KeyStoreError:
426441
decryptable = False
442+
return data_saved, encrypted, decryptable, same_mnemonic
443+
444+
async def show_card_info(self):
445+
note = "Card fingerprint: %s" % self.hexid
446+
version = "%s v%s" % (self.applet.NAME, self.applet.version)
447+
platform = self.applet.platform
448+
data_saved, encrypted, decryptable, same_mnemonic = self.get_secret_info()
427449
# yes = lv.SYMBOL.OK+" Yes"
428450
# no = lv.SYMBOL.CLOSE+" No"
429451
yes = "Yes"
@@ -433,9 +455,9 @@ async def show_card_info(self):
433455
"Implementation: %s" % platform,
434456
"Version: %s" % version,
435457
"\n#7f8fa4 KEY INFO: #",
436-
"Key saved: " + (yes if key_saved else no),
458+
"Card has data: " + (yes if data_saved else no),
437459
]
438-
if key_saved:
460+
if data_saved:
439461
if decryptable:
440462
props.append("Same as current key: " + (yes if same_mnemonic else no))
441463
props.append("Encrypted: " + (yes if encrypted else no))

0 commit comments

Comments
 (0)