From 2353f1bd04fa98fd89efec9bce50006f0e293722 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Tue, 28 Nov 2023 22:51:00 -0500 Subject: [PATCH 01/55] Add initial functionality to load secrets from SeedKeeper --- requirements-raspi.txt | 4 +- src/seedsigner/gui/components.py | 1 + src/seedsigner/views/seed_views.py | 155 ++++++++++++++++++++++++++++- src/seedsigner/views/view.py | 4 +- 4 files changed, 157 insertions(+), 7 deletions(-) diff --git a/requirements-raspi.txt b/requirements-raspi.txt index 8053eeab7..520c60ea1 100644 --- a/requirements-raspi.txt +++ b/requirements-raspi.txt @@ -1,3 +1,3 @@ picamera==1.13 -RPi.GPIO==0.7.0 -spidev==3.5 \ No newline at end of file +RPi.GPIO +spidev==3.5 diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index ea32b964e..0e40d1c4f 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -89,6 +89,7 @@ class FontAwesomeIconConstants: MAP = "\uf279" PAPER_PLANE = "\uf1d8" PEN = "\uf304" + NFC = "\uf023" SQUARE_CARET_DOWN = "\uf150" SQUARE_CARET_LEFT = "\uf191" SQUARE_CARET_RIGHT = "\uf152" diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 15c3fde97..623ed5391 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -80,7 +80,6 @@ class SeedSelectSeedView(View): TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD) TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD) - def __init__(self, flow: str = Controller.FLOW__VERIFY_SINGLESIG_ADDR): super().__init__() self.flow = flow @@ -163,6 +162,7 @@ class LoadSeedView(View): SEED_QR = (" Scan a SeedQR", SeedSignerIconConstants.QRCODE) TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD) TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD) + IMPORT_NFC = ("From SeedKeeper", FontAwesomeIconConstants.NFC) CREATE = (" Create a seed", SeedSignerIconConstants.PLUS) def run(self): @@ -170,6 +170,7 @@ def run(self): self.SEED_QR, self.TYPE_12WORD, self.TYPE_24WORD, + self.IMPORT_NFC, self.CREATE, ] selected_menu_num = self.run_screen( @@ -181,11 +182,11 @@ def run(self): if selected_menu_num == RET_CODE__BACK_BUTTON: return Destination(BackStackView) - + if button_data[selected_menu_num] == self.SEED_QR: from .scan_views import ScanSeedQRView return Destination(ScanSeedQRView) - + elif button_data[selected_menu_num] == self.TYPE_12WORD: self.controller.storage.init_pending_mnemonic(num_words=12) return Destination(SeedMnemonicEntryView) @@ -194,11 +195,159 @@ def run(self): self.controller.storage.init_pending_mnemonic(num_words=24) return Destination(SeedMnemonicEntryView) + elif button_data[selected_menu_num] == self.IMPORT_NFC: + return Destination(SeedKeeperPINView) + elif button_data[selected_menu_num] == self.CREATE: from .tools_views import ToolsMenuView return Destination(ToolsMenuView) +SeedkeeperPIN = "" + +class SeedKeeperPINView(View): + def run(self): + global SeedkeeperPIN + ret = seed_screens.SeedAddPassphraseScreen(title="Seedkeeper PIN").display() + + if ret == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + # The new passphrase will be the return value; it might be empty. + + if len(ret) > 0: + SeedkeeperPIN = ret + return Destination(SeedKeeperSelectView) + else: + return Destination(BackStackView) + +class SeedKeeperSelectView(View): + def run(self): + global SeedkeeperPIN + try: + from pysatochip.CardConnector import CardConnector + from pysatochip.JCconstants import SEEDKEEPER_DIC_TYPE, SEEDKEEPER_DIC_ORIGIN, SEEDKEEPER_DIC_EXPORT_RIGHTS + from binascii import unhexlify + + import os + os.system("ifdnfc-activate") + time.sleep(0.5) # give some time to initialize reader... + + status = None + for i in range(20): + try: + Satochip_Connector = CardConnector() + time.sleep(0.1) # give some time to initialize reader... + status = Satochip_Connector.card_get_status() + break + except Exception as e: + print(e) + time.sleep(0.5) # Sleep for half a second + + if not status: + self.run_screen( + WarningScreen, + title="Unable to Connect", + status_headline=None, + text=f"Unable to find SeedKeeper, missing card or missing reader", + show_back_button=True, + ) + return Destination(BackStackView) + + if (Satochip_Connector.needs_secure_channel): + print("Initiating Secure Channel") + Satochip_Connector.card_initiate_secure_channel() + + Satochip_Connector.set_pin(0, list(bytes(SeedkeeperPIN, "utf-8"))) + + try: + headers = Satochip_Connector.seedkeeper_list_secret_headers() + except RuntimeError as e: #Incorrect PIN + print(e) # + self.run_screen( + WarningScreen, + title="Incorrect PIN", + status_headline=None, + text=f"Unable to unlock SeedKeeper, Incorrect PIN", + show_back_button=True, + ) + return Destination(BackStackView) + + print(headers) + headers_parsed = [] + button_data = [] + for header in headers: + sid = header['id'] + label = header['label'] + stype = SEEDKEEPER_DIC_TYPE.get(header['type'], hex(header['type'])) # hex(header['type']) + origin = SEEDKEEPER_DIC_ORIGIN.get(header['origin'], hex(header['origin'])) # hex(header['origin']) + export_rights = SEEDKEEPER_DIC_EXPORT_RIGHTS.get(header['export_rights'], + hex(header[ + 'export_rights'])) # str(header['export_rights']) + export_nbplain = str(header['export_nbplain']) + export_nbsecure = str(header['export_nbsecure']) + export_nbcounter = str(header['export_counter']) if header['type'] == 0x70 else 'N/A' + fingerprint = header['fingerprint'] + + if stype == "BIP39 mnemonic" and export_rights == 'Plaintext export allowed': + headers_parsed.append((sid, label)) + button_data.append(label) + + + print(headers_parsed) + + selected_menu_num = self.run_screen( + ButtonListScreen, + title="Select Secret", + is_button_text_centered=False, + button_data=button_data + ) + + print(type(headers_parsed[selected_menu_num][0])) + + secret_dict = Satochip_Connector.seedkeeper_export_secret(headers_parsed[selected_menu_num][0], None) + + print(secret_dict) + + secret_dict['secret'] = unhexlify(secret_dict['secret'])[1:].decode().rstrip("\x00") + + bip39_secret = secret_dict['secret'] + + secret_size = secret_dict['secret_list'][0] + + secret_mnemonic = bip39_secret[:secret_size] + secret_passphrase = bip39_secret[secret_size+1:] + + print("BIP39-Mnemonic:", secret_mnemonic, "BIP39-Passphrase:", secret_passphrase) + + except Exception as e: + print(e) + self.run_screen( + WarningScreen, + title="Unknown Error...", + status_headline=None, + text=f"Load from Seedkeeper Failed", + show_back_button=True, + ) + return Destination(BackStackView) + + mnemonic = secret_mnemonic.split(" ") + print(len(mnemonic)) + self.controller.storage.init_pending_mnemonic(num_words=len(mnemonic)) + for i, word in enumerate(mnemonic): + self.controller.storage.update_pending_mnemonic(word, i) + + try: + self.controller.storage.convert_pending_mnemonic_to_pending_seed() + except InvalidSeedException: + return Destination(SeedMnemonicInvalidView) + + if len(secret_passphrase) > 0: + self.seed = self.controller.storage.get_pending_seed() + self.seed.set_passphrase(secret_passphrase) + return Destination(SeedReviewPassphraseView) + # Attempt to finalize the mnemonic + return Destination(SeedFinalizeView) class SeedMnemonicEntryView(View): def __init__(self, cur_word_index: int = 0, is_calc_final_word: bool=False): diff --git a/src/seedsigner/views/view.py b/src/seedsigner/views/view.py index 3f507a73f..c81a884d7 100644 --- a/src/seedsigner/views/view.py +++ b/src/seedsigner/views/view.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Type from seedsigner.gui.components import FontAwesomeIconConstants, SeedSignerIconConstants @@ -311,7 +311,7 @@ class ErrorView(View): status_headline: str = None text: str = None button_text: str = None - next_destination: Destination = Destination(MainMenuView, clear_history=True) + next_destination: Destination = field(default_factory=lambda: Destination(MainMenuView, clear_history=True)) def run(self): From e51fde796656398d38749e1ce0622c6edc611a61 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Wed, 29 Nov 2023 10:38:45 -0500 Subject: [PATCH 02/55] Initial ability to export secret to seedkeeper --- src/seedsigner/views/seed_views.py | 129 +++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 18 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 623ed5391..d2fe887ad 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -25,7 +25,10 @@ from seedsigner.models.threads import BaseThread, ThreadsafeCounter from seedsigner.views.view import NotYetImplementedView, OptionDisabledView, View, Destination, BackStackView, MainMenuView - +from pysatochip.CardConnector import CardConnector +from pysatochip.JCconstants import SEEDKEEPER_DIC_TYPE, SEEDKEEPER_DIC_ORIGIN, SEEDKEEPER_DIC_EXPORT_RIGHTS +from binascii import unhexlify +import os class SeedsMenuView(View): @@ -162,7 +165,7 @@ class LoadSeedView(View): SEED_QR = (" Scan a SeedQR", SeedSignerIconConstants.QRCODE) TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD) TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD) - IMPORT_NFC = ("From SeedKeeper", FontAwesomeIconConstants.NFC) + IMPORT_SEEDKEEPER = ("From SeedKeeper", FontAwesomeIconConstants.NFC) CREATE = (" Create a seed", SeedSignerIconConstants.PLUS) def run(self): @@ -170,7 +173,7 @@ def run(self): self.SEED_QR, self.TYPE_12WORD, self.TYPE_24WORD, - self.IMPORT_NFC, + self.IMPORT_SEEDKEEPER, self.CREATE, ] selected_menu_num = self.run_screen( @@ -195,40 +198,44 @@ def run(self): self.controller.storage.init_pending_mnemonic(num_words=24) return Destination(SeedMnemonicEntryView) - elif button_data[selected_menu_num] == self.IMPORT_NFC: - return Destination(SeedKeeperPINView) + elif button_data[selected_menu_num] == self.IMPORT_SEEDKEEPER: + return Destination(SeedKeeperPINView, view_args={"load_workflow": True}) elif button_data[selected_menu_num] == self.CREATE: from .tools_views import ToolsMenuView return Destination(ToolsMenuView) -SeedkeeperPIN = "" - class SeedKeeperPINView(View): + def __init__(self, save_workflow = False, load_workflow = False, seed_num = None): + super().__init__() + self.save_workflow = save_workflow + self.load_workflow = load_workflow + self.seed_num = seed_num + def run(self): - global SeedkeeperPIN ret = seed_screens.SeedAddPassphraseScreen(title="Seedkeeper PIN").display() if ret == RET_CODE__BACK_BUTTON: return Destination(BackStackView) # The new passphrase will be the return value; it might be empty. - if len(ret) > 0: - SeedkeeperPIN = ret - return Destination(SeedKeeperSelectView) + seedkeeper_pin = ret + if self.load_workflow: + return Destination(SeedKeeperSelectView, view_args={"seedkeeper_pin": seedkeeper_pin}) + if self.save_workflow: + return Destination(SaveToSeedkeeperView, view_args={"seedkeeper_pin": seedkeeper_pin, "seed_num": self.seed_num }) + else: return Destination(BackStackView) class SeedKeeperSelectView(View): + def __init__(self, seedkeeper_pin): + super().__init__() + self.seedkeeper_pin = seedkeeper_pin def run(self): - global SeedkeeperPIN try: - from pysatochip.CardConnector import CardConnector - from pysatochip.JCconstants import SEEDKEEPER_DIC_TYPE, SEEDKEEPER_DIC_ORIGIN, SEEDKEEPER_DIC_EXPORT_RIGHTS - from binascii import unhexlify - import os os.system("ifdnfc-activate") time.sleep(0.5) # give some time to initialize reader... @@ -257,7 +264,7 @@ def run(self): print("Initiating Secure Channel") Satochip_Connector.card_initiate_secure_channel() - Satochip_Connector.set_pin(0, list(bytes(SeedkeeperPIN, "utf-8"))) + Satochip_Connector.set_pin(0, list(bytes(self.seedkeeper_pin, "utf-8"))) try: headers = Satochip_Connector.seedkeeper_list_secret_headers() @@ -687,6 +694,7 @@ def run(self): class SeedBackupView(View): VIEW_WORDS = "View Seed Words" EXPORT_SEEDQR = "Export as SeedQR" + TO_SEEDKEEPER = "To SeedKeeper" def __init__(self, seed_num): super().__init__() @@ -695,7 +703,7 @@ def __init__(self, seed_num): def run(self): - button_data = [self.VIEW_WORDS, self.EXPORT_SEEDQR] + button_data = [self.VIEW_WORDS, self.EXPORT_SEEDQR, self.TO_SEEDKEEPER] selected_menu_num = self.run_screen( ButtonListScreen, @@ -713,6 +721,8 @@ def run(self): elif button_data[selected_menu_num] == self.EXPORT_SEEDQR: return Destination(SeedTranscribeSeedQRFormatView, view_args={"seed_num": self.seed_num}) + elif button_data[selected_menu_num] == self.TO_SEEDKEEPER: + return Destination(SeedKeeperPINView, view_args={"save_workflow": True, "seed_num": self.seed_num}) """**************************************************************************** @@ -2226,3 +2236,86 @@ def run(self): # Exiting/Canceling the QR display screen always returns Home return Destination(MainMenuView, skip_current_view=True) + + +"""**************************************************************************** + Save to SeedKeeper Workflow +****************************************************************************""" +class SaveToSeedkeeperView(View): + def __init__(self, seed_num: int, seedkeeper_pin = None, bip85_data: dict = None): + super().__init__() + self.seed_num = seed_num + self.bip85_data = bip85_data + self.seedkeeper_pin = seedkeeper_pin + + def run(self): + try: + import os + os.system("ifdnfc-activate") + time.sleep(0.5) # give some time to initialize reader... + + status = None + for i in range(20): + try: + Satochip_Connector = CardConnector() + time.sleep(0.1) # give some time to initialize reader... + status = Satochip_Connector.card_get_status() + break + except Exception as e: + print(e) + time.sleep(0.5) # Sleep for half a second + + if not status: + self.run_screen( + WarningScreen, + title="Unable to Connect", + status_headline=None, + text=f"Unable to find SeedKeeper, missing card or missing reader", + show_back_button=True, + ) + return Destination(BackStackView) + + if (Satochip_Connector.needs_secure_channel): + print("Initiating Secure Channel") + Satochip_Connector.card_initiate_secure_channel() + + Satochip_Connector.set_pin(0, list(bytes(self.seedkeeper_pin, "utf-8"))) + + try: + headers = Satochip_Connector.seedkeeper_list_secret_headers() + except RuntimeError as e: # Incorrect PIN + print(e) # + self.run_screen( + WarningScreen, + title="Incorrect PIN", + status_headline=None, + text=f"Unable to unlock SeedKeeper, Incorrect PIN", + show_back_button=True, + ) + return Destination(BackStackView) + + + label = "test-export" + export_rights = "Plaintext export allowed" + type = "BIP39 mnemonic" + header = Satochip_Connector.make_header(type, export_rights, label) + seed = self.controller.get_seed(self.seed_num) + secret = seed.mnemonic_str + secret_list = list(bytes(secret, 'utf-8')) + secret_list = [len(secret_list)] + secret_list + secret_dic = {'header': header, 'secret_list': secret_list} + (sid, fingerprint) = Satochip_Connector.seedkeeper_import_secret(secret_dic) + print("Imported - SID:", sid, " Fingerprint:", fingerprint) + + self.run_screen( + WarningScreen, + title="Secret Saved", + status_headline=None, + text=f"Secret Successfully Saved to Seedkeeper", + show_back_button=True, + ) + return Destination(SeedOptionsView, view_args={"seed_num": self.seed_num}, clear_history=True) + + except Exception as e: + print(e) + return Destination(BackStackView) \ No newline at end of file From f11c395d2e31772891f70b92b76177c991d26a8b Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Wed, 29 Nov 2023 17:35:58 -0500 Subject: [PATCH 03/55] add ability to export seed+passphrase --- src/seedsigner/views/seed_views.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index d2fe887ad..e1163e7ad 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -2294,15 +2294,21 @@ def run(self): ) return Destination(BackStackView) + ret = seed_screens.SeedAddPassphraseScreen(title="Seed Label").display() - label = "test-export" + if ret == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + label = ret export_rights = "Plaintext export allowed" type = "BIP39 mnemonic" header = Satochip_Connector.make_header(type, export_rights, label) seed = self.controller.get_seed(self.seed_num) - secret = seed.mnemonic_str - secret_list = list(bytes(secret, 'utf-8')) - secret_list = [len(secret_list)] + secret_list + bip39_mnemonic = seed.mnemonic_str + bip39_mnemonic_list = list(bytes(bip39_mnemonic, 'utf-8')) + bip39_passphrase = seed.passphrase + bip39_passphrase_list = list(bytes(bip39_passphrase, 'utf-8')) + secret_list = [len(bip39_mnemonic_list)] + bip39_mnemonic_list + [len(bip39_passphrase_list)] + bip39_passphrase_list secret_dic = {'header': header, 'secret_list': secret_list} (sid, fingerprint) = Satochip_Connector.seedkeeper_import_secret(secret_dic) print("Imported - SID:", sid, " Fingerprint:", fingerprint) From de8abc1b99dde5aa472416ce11d447540da96e30 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Thu, 30 Nov 2023 10:58:57 -0500 Subject: [PATCH 04/55] Improve workflows Add PIN change to tools menu --- src/seedsigner/gui/components.py | 1 - src/seedsigner/views/seed_views.py | 138 ++++------------------------ src/seedsigner/views/tools_views.py | 70 +++++++++++++- 3 files changed, 88 insertions(+), 121 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index 0e40d1c4f..ea32b964e 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -89,7 +89,6 @@ class FontAwesomeIconConstants: MAP = "\uf279" PAPER_PLANE = "\uf1d8" PEN = "\uf304" - NFC = "\uf023" SQUARE_CARET_DOWN = "\uf150" SQUARE_CARET_LEFT = "\uf191" SQUARE_CARET_RIGHT = "\uf152" diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index e1163e7ad..37a3c35a9 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -25,11 +25,9 @@ from seedsigner.models.threads import BaseThread, ThreadsafeCounter from seedsigner.views.view import NotYetImplementedView, OptionDisabledView, View, Destination, BackStackView, MainMenuView -from pysatochip.CardConnector import CardConnector from pysatochip.JCconstants import SEEDKEEPER_DIC_TYPE, SEEDKEEPER_DIC_ORIGIN, SEEDKEEPER_DIC_EXPORT_RIGHTS +from seedsigner.helpers import seedkeeper_utils from binascii import unhexlify -import os - class SeedsMenuView(View): LOAD = "Load a seed" @@ -165,7 +163,7 @@ class LoadSeedView(View): SEED_QR = (" Scan a SeedQR", SeedSignerIconConstants.QRCODE) TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD) TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD) - IMPORT_SEEDKEEPER = ("From SeedKeeper", FontAwesomeIconConstants.NFC) + IMPORT_SEEDKEEPER = ("From SeedKeeper", FontAwesomeIconConstants.LOCK) CREATE = (" Create a seed", SeedSignerIconConstants.PLUS) def run(self): @@ -199,85 +197,21 @@ def run(self): return Destination(SeedMnemonicEntryView) elif button_data[selected_menu_num] == self.IMPORT_SEEDKEEPER: - return Destination(SeedKeeperPINView, view_args={"load_workflow": True}) + return Destination(SeedKeeperSelectView) elif button_data[selected_menu_num] == self.CREATE: from .tools_views import ToolsMenuView return Destination(ToolsMenuView) - -class SeedKeeperPINView(View): - def __init__(self, save_workflow = False, load_workflow = False, seed_num = None): - super().__init__() - self.save_workflow = save_workflow - self.load_workflow = load_workflow - self.seed_num = seed_num - - def run(self): - ret = seed_screens.SeedAddPassphraseScreen(title="Seedkeeper PIN").display() - - if ret == RET_CODE__BACK_BUTTON: - return Destination(BackStackView) - - # The new passphrase will be the return value; it might be empty. - if len(ret) > 0: - seedkeeper_pin = ret - if self.load_workflow: - return Destination(SeedKeeperSelectView, view_args={"seedkeeper_pin": seedkeeper_pin}) - if self.save_workflow: - return Destination(SaveToSeedkeeperView, view_args={"seedkeeper_pin": seedkeeper_pin, "seed_num": self.seed_num }) - - else: - return Destination(BackStackView) class SeedKeeperSelectView(View): - def __init__(self, seedkeeper_pin): - super().__init__() - self.seedkeeper_pin = seedkeeper_pin def run(self): try: + Satochip_Connector = seedkeeper_utils.init_seedkeeper(self) - os.system("ifdnfc-activate") - time.sleep(0.5) # give some time to initialize reader... - - status = None - for i in range(20): - try: - Satochip_Connector = CardConnector() - time.sleep(0.1) # give some time to initialize reader... - status = Satochip_Connector.card_get_status() - break - except Exception as e: - print(e) - time.sleep(0.5) # Sleep for half a second - - if not status: - self.run_screen( - WarningScreen, - title="Unable to Connect", - status_headline=None, - text=f"Unable to find SeedKeeper, missing card or missing reader", - show_back_button=True, - ) + if not Satochip_Connector: return Destination(BackStackView) - if (Satochip_Connector.needs_secure_channel): - print("Initiating Secure Channel") - Satochip_Connector.card_initiate_secure_channel() - - Satochip_Connector.set_pin(0, list(bytes(self.seedkeeper_pin, "utf-8"))) - - try: - headers = Satochip_Connector.seedkeeper_list_secret_headers() - except RuntimeError as e: #Incorrect PIN - print(e) # - self.run_screen( - WarningScreen, - title="Incorrect PIN", - status_headline=None, - text=f"Unable to unlock SeedKeeper, Incorrect PIN", - show_back_button=True, - ) - return Destination(BackStackView) + headers = Satochip_Connector.seedkeeper_list_secret_headers() print(headers) headers_parsed = [] @@ -299,8 +233,16 @@ def run(self): headers_parsed.append((sid, label)) button_data.append(label) - print(headers_parsed) + if len(headers_parsed) < 1: + self.run_screen( + WarningScreen, + title="No Secrets to Load", + status_headline=None, + text=f"No BIP39 Secrets to Load from Seedkeeper", + show_back_button=True, + ) + return Destination(BackStackView) selected_menu_num = self.run_screen( ButtonListScreen, @@ -722,7 +664,7 @@ def run(self): return Destination(SeedTranscribeSeedQRFormatView, view_args={"seed_num": self.seed_num}) elif button_data[selected_menu_num] == self.TO_SEEDKEEPER: - return Destination(SeedKeeperPINView, view_args={"save_workflow": True, "seed_num": self.seed_num}) + return Destination(SaveToSeedkeeperView, view_args={"seed_num": self.seed_num}) """**************************************************************************** @@ -2242,56 +2184,16 @@ def run(self): Save to SeedKeeper Workflow ****************************************************************************""" class SaveToSeedkeeperView(View): - def __init__(self, seed_num: int, seedkeeper_pin = None, bip85_data: dict = None): + def __init__(self, seed_num: int, bip85_data: dict = None): super().__init__() self.seed_num = seed_num self.bip85_data = bip85_data - self.seedkeeper_pin = seedkeeper_pin def run(self): try: - import os - os.system("ifdnfc-activate") - time.sleep(0.5) # give some time to initialize reader... - - status = None - for i in range(20): - try: - Satochip_Connector = CardConnector() - time.sleep(0.1) # give some time to initialize reader... - status = Satochip_Connector.card_get_status() - break - except Exception as e: - print(e) - time.sleep(0.5) # Sleep for half a second + Satochip_Connector = seedkeeper_utils.init_seedkeeper(self) - if not status: - self.run_screen( - WarningScreen, - title="Unable to Connect", - status_headline=None, - text=f"Unable to find SeedKeeper, missing card or missing reader", - show_back_button=True, - ) - return Destination(BackStackView) - - if (Satochip_Connector.needs_secure_channel): - print("Initiating Secure Channel") - Satochip_Connector.card_initiate_secure_channel() - - Satochip_Connector.set_pin(0, list(bytes(self.seedkeeper_pin, "utf-8"))) - - try: - headers = Satochip_Connector.seedkeeper_list_secret_headers() - except RuntimeError as e: # Incorrect PIN - print(e) # - self.run_screen( - WarningScreen, - title="Incorrect PIN", - status_headline=None, - text=f"Unable to unlock SeedKeeper, Incorrect PIN", - show_back_button=True, - ) + if not Satochip_Connector: return Destination(BackStackView) ret = seed_screens.SeedAddPassphraseScreen(title="Seed Label").display() @@ -2314,7 +2216,7 @@ def run(self): print("Imported - SID:", sid, " Fingerprint:", fingerprint) self.run_screen( - WarningScreen, + LargeIconStatusScreen, title="Secret Saved", status_headline=None, text=f"Secret Successfully Saved to Seedkeeper", diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 12a4493b3..16b3dfaa1 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -20,8 +20,11 @@ from seedsigner.models.settings_definition import SettingsConstants from seedsigner.views.seed_views import SeedDiscardView, SeedFinalizeView, SeedMnemonicEntryView, SeedOptionsView, SeedWordsWarningView, SeedExportXpubScriptTypeView -from .view import View, Destination, BackStackView +from .view import View, Destination, BackStackView, MainMenuView +from seedsigner.helpers import seedkeeper_utils +from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, + WarningScreen, DireWarningScreen, seed_screens, LargeIconStatusScreen) class ToolsMenuView(View): @@ -30,9 +33,10 @@ class ToolsMenuView(View): KEYBOARD = ("Calc 12th/24th word", FontAwesomeIconConstants.KEYBOARD) EXPLORER = "Address Explorer" ADDRESS = "Verify address" + SEEDKEEPER = ("SeedKeeper", FontAwesomeIconConstants.LOCK) def run(self): - button_data = [self.IMAGE, self.DICE, self.KEYBOARD, self.EXPLORER, self.ADDRESS] + button_data = [self.IMAGE, self.DICE, self.KEYBOARD, self.EXPLORER, self.ADDRESS, self.SEEDKEEPER] selected_menu_num = self.run_screen( ButtonListScreen, @@ -60,6 +64,9 @@ def run(self): from seedsigner.views.scan_views import ScanAddressView return Destination(ScanAddressView) + elif button_data[selected_menu_num] == self.SEEDKEEPER: + return Destination(ToolsSeedkeeperMenuView) + """**************************************************************************** @@ -703,3 +710,62 @@ def run(self): # Exiting/Cancelling the QR display screen always returns to the list return Destination(ToolsAddressExplorerAddressListView, view_args=dict(is_change=self.is_change, start_index=self.start_index, selected_button_index=self.index - self.start_index, initial_scroll=self.parent_initial_scroll), skip_current_view=True) + +"""**************************************************************************** + Seedkeeper Views +****************************************************************************""" +class ToolsSeedkeeperMenuView(View): + CHANGE_PIN = ("Change PIN") + + def run(self): + button_data = [self.CHANGE_PIN] + + selected_menu_num = self.run_screen( + ButtonListScreen, + title="Tools", + is_button_text_centered=False, + button_data=button_data + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + elif button_data[selected_menu_num] == self.CHANGE_PIN: + return Destination(ToolsSeedkeeperChangePinView) + +class ToolsSeedkeeperChangePinView(View): + def run(self): + + Satochip_Connector = seedkeeper_utils.init_seedkeeper(self) + + if not Satochip_Connector: + return Destination(BackStackView) + + NewPin = seed_screens.SeedAddPassphraseScreen(title="New PIN").display() + + if NewPin == RET_CODE__BACK_BUTTON: + return Destination(ToolsSeedkeeperMenuView) + + new_pin = list(NewPin.encode('utf8')) + response, sw1, sw2 = Satochip_Connector.card_change_PIN(0, Satochip_Connector.pin, new_pin) + if sw1 == 0x90 and sw2 == 0x00: + print("Success: Pin Changed") + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"PIN Updated", + show_back_button=False, + ) + else: + print("Failure: Pin Change Failed") + self.run_screen( + WarningScreen, + title="Invalid PIN", + status_headline=None, + text=f"Invalid PIN entered, select another and try again.", + show_back_button=True, + ) + + return Destination(MainMenuView) + From 93c890521348bfcfd1fa172ba40bbd2a4a09fc9c Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Thu, 30 Nov 2023 13:02:17 -0500 Subject: [PATCH 05/55] Add ability to install/uninstall applets (Installation is unreliable over NFC) --- src/seedsigner/helpers/seedkeeper_utils.py | 134 +++++++++++++++++++++ src/seedsigner/views/tools_views.py | 48 +++++++- 2 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 src/seedsigner/helpers/seedkeeper_utils.py diff --git a/src/seedsigner/helpers/seedkeeper_utils.py b/src/seedsigner/helpers/seedkeeper_utils.py new file mode 100644 index 000000000..82c6fd8e6 --- /dev/null +++ b/src/seedsigner/helpers/seedkeeper_utils.py @@ -0,0 +1,134 @@ +from pysatochip.CardConnector import CardConnector +from pysatochip.JCconstants import SEEDKEEPER_DIC_TYPE, SEEDKEEPER_DIC_ORIGIN, SEEDKEEPER_DIC_EXPORT_RIGHTS +from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, + WarningScreen, DireWarningScreen, seed_screens, LargeIconStatusScreen) + +import os +import time +from os import urandom + +def init_seedkeeper(parentObject): + # os.system("ifdnfc-activate") + # time.sleep(0.1) # give some time to initialize reader... + + # Spam connecting for 10 seconds to give the user time to insert the card + status = None + for i in range(100): + try: + Satochip_Connector = CardConnector(card_filter="seedkeeper") + time.sleep(0.1) # give some time to initialize reader... + status = Satochip_Connector.card_get_status() + break + except Exception as e: + print(e) + time.sleep(0.1) # Sleep for 100ms + + if not status: + parentObject.run_screen( + WarningScreen, + title="Unable to Connect", + status_headline=None, + text=f"Unable to find SeedKeeper, missing card or missing reader", + show_back_button=True, + ) + return None + + status = Satochip_Connector.card_get_status() + print("Found Card:") + print(status[3]) + + if (Satochip_Connector.needs_secure_channel): + print("Initiating Secure Channel") + Satochip_Connector.card_initiate_secure_channel() + + if status[3]['setup_done']: + ret = seed_screens.SeedAddPassphraseScreen(title="Seedkeeper PIN").display() + + if ret == RET_CODE__BACK_BUTTON: + return None + + Satochip_Connector.set_pin(0, list(bytes(ret, "utf-8"))) + + try: + print("Verifying PIN") + (response, sw1, sw2) = Satochip_Connector.card_verify_PIN() + print(response) + except RuntimeError as e: #Incorrect PIN + print(e) # + status = Satochip_Connector.card_get_status() + pin_tries_left = status[3]['PIN0_remaining_tries'] + if pin_tries_left == 0: + parentObject.run_screen( + WarningScreen, + title="Card Blocked", + status_headline=None, + text=f"Incorrect-PIN entered too many times, card locked...", + show_back_button=True, + ) + else: + parentObject.run_screen( + WarningScreen, + title="Incorrect PIN", + status_headline=None, + text=f"Unable to unlock SeedKeeper, Incorrect PIN " + str(pin_tries_left) + " tries remain before card locks...", + show_back_button=True, + ) + return None + else: + print("Seedkeeper Needs Initial Setup") + parentObject.run_screen( + WarningScreen, + title="Seedkeeper Uninitialised", + status_headline=None, + text=f"Set a device PIN to complete Card Setup", + show_back_button=True, + ) + + ret = seed_screens.SeedAddPassphraseScreen(title="Seedkeeper PIN").display() + + if ret == RET_CODE__BACK_BUTTON: + return None + + """Run the initial card setup process""" + pin_0 = list(ret.encode('utf8')) + # Just stick with the defaults from SeedKeeper tool + pin_tries_0 = 0x05 + ublk_tries_0 = 0x01 + # PUK code can be used when PIN is unknown and the card is locked + # We use a random value as the PUK is not used currently and is not user friendly + ublk_0 = list(urandom(16)) + pin_tries_1 = 0x01 + ublk_tries_1 = 0x01 + pin_1 = list(urandom(16)) # the second pin is not used currently + ublk_1 = list(urandom(16)) + secmemsize = 32 # 0x0000 # => for satochip - TODO: hardcode value? + memsize = 0x0000 # RFU + create_object_ACL = 0x01 # RFU + create_key_ACL = 0x01 # RFU + create_pin_ACL = 0x01 # RFU + + (response, sw1, sw2) = Satochip_Connector.card_setup(pin_tries_0, ublk_tries_0, pin_0, ublk_0, pin_tries_1, ublk_tries_1, pin_1, ublk_1, secmemsize, memsize, create_object_ACL, create_key_ACL, create_pin_ACL, option_flags=0, hmacsha160_key=None, amount_limit=0) + if sw1 != 0x90 or sw2 != 0x00: + print("ERROR: Setup Failed") + parentObject.run_screen( + WarningScreen, + title="Invalid PIN", + status_headline=None, + text=f"Invalid PIN entered, select another and try again.", + show_back_button=True, + ) + return None + else: + Satochip_Connector.set_pin(0, list(bytes(ret, "utf-8"))) + print("Setup Succeeded") + parentObject.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"Card Setup Complete", + show_back_button=False, + ) + + return Satochip_Connector + + \ No newline at end of file diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 16b3dfaa1..d158c9596 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -716,9 +716,12 @@ def run(self): ****************************************************************************""" class ToolsSeedkeeperMenuView(View): CHANGE_PIN = ("Change PIN") + IFDNFC_ACTIVATE = ("Run ifdnfc-activate") + INSTALL_APPLET = ("Install Applet") + UNINSTALL_APPLET = ("Uninstall Applet") def run(self): - button_data = [self.CHANGE_PIN] + button_data = [self.CHANGE_PIN, self.IFDNFC_ACTIVATE, self.INSTALL_APPLET, self.UNINSTALL_APPLET] selected_menu_num = self.run_screen( ButtonListScreen, @@ -732,6 +735,15 @@ def run(self): elif button_data[selected_menu_num] == self.CHANGE_PIN: return Destination(ToolsSeedkeeperChangePinView) + + elif button_data[selected_menu_num] == self.IFDNFC_ACTIVATE: + return Destination(ToolsSeedkeeperStartIfdNFCView) + + elif button_data[selected_menu_num] == self.INSTALL_APPLET: + return Destination(ToolsSeedkeeperInstallAppletView) + + elif button_data[selected_menu_num] == self.UNINSTALL_APPLET: + return Destination(ToolsSeedkeeperUninstallAppletView) class ToolsSeedkeeperChangePinView(View): def run(self): @@ -769,3 +781,37 @@ def run(self): return Destination(MainMenuView) +class ToolsSeedkeeperStartIfdNFCView(View): + def run(self): + import os + + os.system("ifdnfc-activate") + time.sleep(1) + + return Destination(MainMenuView) + +class ToolsSeedkeeperInstallAppletView(View): + def run(self): + from subprocess import run + import os + + data = run("java -jar /home/pi/Satochip-DIY/gp.jar --install /home/pi/Satochip-DIY/build/SeedKeeper-official-3.0.4.cap", capture_output=True, shell=True, text=True) + print("StdOut:", data.stdout) + print("StdErr:", data.stderr) + + os.system("ifdnfc-activate") + + return Destination(MainMenuView) + +class ToolsSeedkeeperUninstallAppletView(View): + def run(self): + from subprocess import run + import os + + data = run("java -jar /home/pi/Satochip-DIY/gp.jar --uninstall /home/pi/Satochip-DIY/build/SeedKeeper-official-3.0.4.cap", capture_output=True, shell=True, text=True) + print("StdOut:", data.stdout) + print("StdErr:", data.stderr) + + os.system("ifdnfc-activate") + + return Destination(MainMenuView) From 51074c639c73096bf681fcf850b0972ed4e28538 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Thu, 30 Nov 2023 22:28:12 -0500 Subject: [PATCH 06/55] Draft documentation --- docs/smartcard_support_installation.md | 115 +++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 docs/smartcard_support_installation.md diff --git a/docs/smartcard_support_installation.md b/docs/smartcard_support_installation.md new file mode 100644 index 000000000..e286111cc --- /dev/null +++ b/docs/smartcard_support_installation.md @@ -0,0 +1,115 @@ +# Smartcard Seed Storage Support +## Background +Smart Cards are specifically designed to securely store digital data. Javacards are a type of Smart Cards that implement open standards development tools, making them ideal for DIY. + +SeedKeeper is a open source seed storage product from Satochip which can be used to securely store multiple BIP39 seeds & passphrases. (And other types of secrets, but these aren't relevant to SeedSigner) + +## Hardware Requirements +### USB Smart Card Readers +Any USB smart card reader that is compatible with will work, either hard-wired (Contact) or NFC (Contactless). + +If you are running SeedSigner on a system image that is derived from a standard Raspberry Pi OS image, USB devices should be plug and play once PC/SC services are installed. + +**Compatibility Notes** + +The **ACS ACR 122U reader** is unreliable for flashing applets and may brick your card. (Though works fine for normal operation after they have been flashed) + +### GPIO Connected Smart Card Readers +The PN352 NFC module is low cost ($5 on Aliexpress) can be connected via available IO pins and is well supported by LibNFC. + +Instructions on how to physically connect it can be found here: https://blog.stigok.com/2017/10/12/setting-up-a-pn532-nfc-module-on-a-raspberry-pi-using-i2c.html (Stop when you get to the section on LibNFC, as that part is not relevant) + +## Software Installation + +The following guide assumes that you have completed the [Manual Installation guide...](./manual_installation.md) + +### SeedSigner with SeedKeeper Support +You will need to clone this repository in the place of the existing seedsigner folder in `/home/pi/seedsigner` + +### Smartcard Libraries + +Install the following additional software + + sudo apt-get install git autoconf libtool libusb-dev libusb-dev libpcsclite-dev i2c-tools pcscd libpcsclite1 swig + +### PySatoChip +While you can install PySatoChip directly from pip, the current (Nov 2023) release of PySatoChip needs a few tweaks before it will work with the code here. (Which may have been merged into the Master by the time you read this) + +For now, you can download and build my fork using the code below. This will manually build the cryptography module which will take a few hours and also requires that you have a working installation of the Rust Compiler. + +**Install Rust** + + curl https://sh.rustup.rs -sSf | sh + +_Choose option 1 to install Rust_ + +**Install PySatoChip** + + git clone https://github.com/3rdIteration/pysatochip + pip3 install -r requirements.txt + cd pysatochip + python setup.py install + +### LibNFC + IFDNFC (Optional: Needed for PN352 connected via GPIO Pins) + +**Install LibNFC** + + git clone https://github.com/nfc-tools/libnfc + autoreconf -vis + ./configure --with-drivers=pn532_i2c + make + make install + sudo sh -c "echo /usr/local/lib > /etc/ld.so.conf.d/usr-local-lib.conf" + sudo ldconfig + +**Install IfdNFC** + + git clone https://github.com/nfc-tools/ifdnfc.git + autoreconf -vis + ./configure + make + make install + +**Add Configuration Files** +Create the folder + + mkdir /usr/local/etc/nfc/ + +Create the file `/usr/local/etc/nfc/libnfc.conf` and add the following + + device.name = "IFD-NFC" + device.connstring = "pn532_i2c:/dev/i2c-1" + +Create the file `/etc/reader.conf.d/libifdnfc` and add the following + + FRIENDLYNAME "IFD-NFC" + LIBPATH /usr/local/lib/libifdnfc.so + CHANNELID 0 + +**Restart PCSCD** + + sudo service pcscd restart + +**Activating IFD-NFC** + +You will notice that there is a menu option to `ifdnfc-activate` under the tools->SeedKeeper menu. Basically IFDNFC only needs to be run once on each boot, after which you need to restart the SeedSigner app. (But not the device) + +Applet management operations (Installing, uninstalling, etc) often terminate the `idfnfc` process after completing, so if you can no longer do SeedKeeper operations like change PIN, load or save secrets, immediatly after flashing the applet, then this is likely why. (Just re-run the `ifdnfc-activate` process I mention above, restart the app and it should work fine) + +### Javacard Managment Tools (Optional: Needed to flash SeedKeeper to Javacards) + +You just need to install openjdk-8-jdk and Apache Ant + +Follow the guide here: https://github.com/3rdIteration/Satochip-DIY + +_The applet management (install/uninstall) in the SeedSigner menu assume that the Satochip-DIY repository was cloned into /home/pi/Satochip-DIY and built as per the guide in the repository._ + +The commands that the menu items run are currently hardcoded to be: + + java -jar /home/pi/Satochip-DIY/gp.jar --install /home/pi/Satochip-DIY/build/SeedKeeper-official-3.0.4.cap + + java -jar /home/pi/Satochip-DIY/gp.jar --uninstall /home/pi/Satochip-DIY/build/SeedKeeper-official-3.0.4.cap + +### Javacard Build Environment (Optional: Needed to build SeedKeeper from Source) + +Follow the guide here: https://github.com/3rdIteration/Satochip-DIY \ No newline at end of file From 3e93764554929a12df27a911c8546643812c5836 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Thu, 30 Nov 2023 22:53:22 -0500 Subject: [PATCH 07/55] Update smartcard_support_installation.md --- docs/smartcard_support_installation.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/smartcard_support_installation.md b/docs/smartcard_support_installation.md index e286111cc..0f78d0666 100644 --- a/docs/smartcard_support_installation.md +++ b/docs/smartcard_support_installation.md @@ -2,7 +2,9 @@ ## Background Smart Cards are specifically designed to securely store digital data. Javacards are a type of Smart Cards that implement open standards development tools, making them ideal for DIY. -SeedKeeper is a open source seed storage product from Satochip which can be used to securely store multiple BIP39 seeds & passphrases. (And other types of secrets, but these aren't relevant to SeedSigner) +SeedKeeper is a open source seed storage product from Satochip which can be used to securely store multiple BIP39 seeds & passphrases. (And other types of secrets, but these aren't relevant to SeedSigner) In addition to providing the nessesary functionality, along with security features like secure-channel to protect the data exchange from eavesdropping, etc, the SeedKeeper also has standalone software available for users who may need to securely retrieve their data without access to a SeedSigner... + +This guide focuses on DIY SeedKeeper cards (which are the best for testing) but this will also work with retail SeedKeeper cards for those who prefer that simplicity... ## Hardware Requirements ### USB Smart Card Readers @@ -54,21 +56,25 @@ _Choose option 1 to install Rust_ **Install LibNFC** + cd ~ git clone https://github.com/nfc-tools/libnfc + cd libnfc autoreconf -vis ./configure --with-drivers=pn532_i2c make - make install + sudo make install sudo sh -c "echo /usr/local/lib > /etc/ld.so.conf.d/usr-local-lib.conf" sudo ldconfig **Install IfdNFC** - git clone https://github.com/nfc-tools/ifdnfc.git + cd ~ + git clone https://github.com/nfc-tools/ifdnfc + cd ifdnfc autoreconf -vis ./configure make - make install + sudo make install **Add Configuration Files** Create the folder From ab42619769d077849db685fb5138099959a92e05 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Sun, 3 Dec 2023 22:10:24 -0500 Subject: [PATCH 08/55] Add OpenCT based USB option --- docs/smartcard_support_installation.md | 65 +++++++++++++++++++--- src/seedsigner/helpers/seedkeeper_utils.py | 3 +- src/seedsigner/views/tools_views.py | 19 ++++++- 3 files changed, 77 insertions(+), 10 deletions(-) diff --git a/docs/smartcard_support_installation.md b/docs/smartcard_support_installation.md index 0f78d0666..ca38b9890 100644 --- a/docs/smartcard_support_installation.md +++ b/docs/smartcard_support_installation.md @@ -7,7 +7,7 @@ SeedKeeper is a open source seed storage product from Satochip which can be used This guide focuses on DIY SeedKeeper cards (which are the best for testing) but this will also work with retail SeedKeeper cards for those who prefer that simplicity... ## Hardware Requirements -### USB Smart Card Readers +### USB Smart Card Readers Any USB smart card reader that is compatible with will work, either hard-wired (Contact) or NFC (Contactless). If you are running SeedSigner on a system image that is derived from a standard Raspberry Pi OS image, USB devices should be plug and play once PC/SC services are installed. @@ -17,7 +17,7 @@ If you are running SeedSigner on a system image that is derived from a standard The **ACS ACR 122U reader** is unreliable for flashing applets and may brick your card. (Though works fine for normal operation after they have been flashed) ### GPIO Connected Smart Card Readers -The PN352 NFC module is low cost ($5 on Aliexpress) can be connected via available IO pins and is well supported by LibNFC. +The PN352 NFC V3 module is low cost ($5 on Aliexpress) can be connected via available IO pins and is well supported by LibNFC. Instructions on how to physically connect it can be found here: https://blog.stigok.com/2017/10/12/setting-up-a-pn532-nfc-module-on-a-raspberry-pi-using-i2c.html (Stop when you get to the section on LibNFC, as that part is not relevant) @@ -47,7 +47,9 @@ _Choose option 1 to install Rust_ **Install PySatoChip** + cd ~ git clone https://github.com/3rdIteration/pysatochip + cd pysatochip pip3 install -r requirements.txt cd pysatochip python setup.py install @@ -76,17 +78,20 @@ _Choose option 1 to install Rust_ make sudo make install +**Note Concerning IfdNFC** +You may get a message like `Insufficient buffer` you run `idfnfc-activate`, or a message like `ifdnfc inactive` but it is actually working. (Even on x86 platforms when it doesn't work with other tools like pcsc_scan) + **Add Configuration Files** Create the folder - mkdir /usr/local/etc/nfc/ + sudo mkdir /usr/local/etc/nfc/ -Create the file `/usr/local/etc/nfc/libnfc.conf` and add the following +Create the file `/usr/local/etc/nfc/libnfc.conf` and add the following (`sudo nano /usr/local/etc/nfc/libnfc.conf`) device.name = "IFD-NFC" device.connstring = "pn532_i2c:/dev/i2c-1" -Create the file `/etc/reader.conf.d/libifdnfc` and add the following +Create the file `/etc/reader.conf.d/libifdnfc` and add the following (`sudo nano /etc/reader.conf.d/libifdnfc`) FRIENDLYNAME "IFD-NFC" LIBPATH /usr/local/lib/libifdnfc.so @@ -98,7 +103,7 @@ Create the file `/etc/reader.conf.d/libifdnfc` and add the following **Activating IFD-NFC** -You will notice that there is a menu option to `ifdnfc-activate` under the tools->SeedKeeper menu. Basically IFDNFC only needs to be run once on each boot, after which you need to restart the SeedSigner app. (But not the device) +You will notice that there is a menu option to `Start PN532(PN532)` under the tools->SeedKeeper menu. Basically IFDNFC only needs to be run once on each boot, after which you need to restart the SeedSigner app. (But not the device) Applet management operations (Installing, uninstalling, etc) often terminate the `idfnfc` process after completing, so if you can no longer do SeedKeeper operations like change PIN, load or save secrets, immediatly after flashing the applet, then this is likely why. (Just re-run the `ifdnfc-activate` process I mention above, restart the app and it should work fine) @@ -118,4 +123,50 @@ The commands that the menu items run are currently hardcoded to be: ### Javacard Build Environment (Optional: Needed to build SeedKeeper from Source) -Follow the guide here: https://github.com/3rdIteration/Satochip-DIY \ No newline at end of file +Follow the guide here: https://github.com/3rdIteration/Satochip-DIY + +### OpenCT and Generic/Old Blue "Sim Readers" (Optional: Get a more modern Smart Card reader if possible... ) +**Included only for Reference/Education/Backup, as these can be built from Scratch...** + +It's possible to obtain very cheap USB "Sim Readers" (Often Blue) for under $5 USD that can be used to access the Seedkeeper. (Or you can build on using the schematic here: https://circuitsarchive.org/circuits/smartcard/smartcard-pc-serial-reader-writer-phoenix/) + +These types of devices will *not* be automatically detected or usable on modern Systems (Windows will give you an explicit error that the PL2302 USB-to-Serial converter is not supported) but can be made to work on Linux and/or Raspberry Pi through using OpenCT and configuring it to work as a Phoenix type reader. + +_Note: The version of OpenSC that can be installed via APT is buggy and will not work, it must be built from source..._ + +To Install and configure OpenCT + + cd ~ + git clone https://github.com/OpenSC/openct + cd openct + ./bootstrap + ./configure --enable-pcsc + make + sudo make install + sudo ldconfig + sudo mkdir -p /usr/local/var/run/openct/ + +Then Add configuration files to use it with PCSC tools + +Add it to the list of readers `sudo nano /etc/reader.conf.d/openct` + + FRIENDLYNAME "OpenCT" + DEVICENAME /dev/null + LIBPATH /usr/local/lib/openct-ifd.so + CHANNELID 0 + +Enable the Phoenix Driver in OpenCT `sudo nano /usr/local/etc/openct.conf` + +and add the following to the end of the file + + reader phoenix { + driver = phoenix; + device = serial:/dev/ttyUSB0; + }; + + +Once you have done this, you can boot the device with the USB SIM reader connected. + +Once the device is started, go into `tools->seedkeeper>Start OpenCT(SIM)` and the USB reader should then work until the next restart. + +_Adapted from https://timesinker.blogspot.com/2016/04/using-cheap-sim-card-readers.html_ \ No newline at end of file diff --git a/src/seedsigner/helpers/seedkeeper_utils.py b/src/seedsigner/helpers/seedkeeper_utils.py index 82c6fd8e6..e1541e535 100644 --- a/src/seedsigner/helpers/seedkeeper_utils.py +++ b/src/seedsigner/helpers/seedkeeper_utils.py @@ -13,7 +13,8 @@ def init_seedkeeper(parentObject): # Spam connecting for 10 seconds to give the user time to insert the card status = None - for i in range(100): + time_end = time.time() + 10 + while time.time() < time_end: try: Satochip_Connector = CardConnector(card_filter="seedkeeper") time.sleep(0.1) # give some time to initialize reader... diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index d158c9596..2bf523a14 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -716,12 +716,13 @@ def run(self): ****************************************************************************""" class ToolsSeedkeeperMenuView(View): CHANGE_PIN = ("Change PIN") - IFDNFC_ACTIVATE = ("Run ifdnfc-activate") + IFDNFC_ACTIVATE = ("Start PN532(PN532)") + OPENCT_ACTIVATE = ("Start OpenCT(SIM)") INSTALL_APPLET = ("Install Applet") UNINSTALL_APPLET = ("Uninstall Applet") def run(self): - button_data = [self.CHANGE_PIN, self.IFDNFC_ACTIVATE, self.INSTALL_APPLET, self.UNINSTALL_APPLET] + button_data = [self.CHANGE_PIN, self.IFDNFC_ACTIVATE, self.OPENCT_ACTIVATE self.INSTALL_APPLET, self.UNINSTALL_APPLET] selected_menu_num = self.run_screen( ButtonListScreen, @@ -739,6 +740,9 @@ def run(self): elif button_data[selected_menu_num] == self.IFDNFC_ACTIVATE: return Destination(ToolsSeedkeeperStartIfdNFCView) + elif button_data[selected_menu_num] == self.OPENCT_ACTIVATE: + return Destination(ToolsSeedkeeperStartOpenCTView) + elif button_data[selected_menu_num] == self.INSTALL_APPLET: return Destination(ToolsSeedkeeperInstallAppletView) @@ -790,6 +794,17 @@ def run(self): return Destination(MainMenuView) +class ToolsSeedkeeperStartIfdNFCView(View): + def run(self): + import os + + os.system("sudo openct-contril init") + time.sleep(1) + os.system("sudo service pcscd restart") + time.sleep(1) + + return Destination(MainMenuView) + class ToolsSeedkeeperInstallAppletView(View): def run(self): from subprocess import run From dabae4880073a82a1350e4dab9b3ca13a7027c11 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Sun, 3 Dec 2023 22:18:12 -0500 Subject: [PATCH 09/55] Update tools_views.py --- src/seedsigner/views/tools_views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 2bf523a14..16cb34852 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -722,7 +722,7 @@ class ToolsSeedkeeperMenuView(View): UNINSTALL_APPLET = ("Uninstall Applet") def run(self): - button_data = [self.CHANGE_PIN, self.IFDNFC_ACTIVATE, self.OPENCT_ACTIVATE self.INSTALL_APPLET, self.UNINSTALL_APPLET] + button_data = [self.CHANGE_PIN, self.IFDNFC_ACTIVATE, self.OPENCT_ACTIVATE, self.INSTALL_APPLET, self.UNINSTALL_APPLET] selected_menu_num = self.run_screen( ButtonListScreen, @@ -794,7 +794,7 @@ def run(self): return Destination(MainMenuView) -class ToolsSeedkeeperStartIfdNFCView(View): +class ToolsSeedkeeperStartOpenCTView(View): def run(self): import os From ac0b9e5661e92a3c97e515747baff7fa84b28754 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Sun, 3 Dec 2023 22:22:08 -0500 Subject: [PATCH 10/55] Update tools_views.py --- src/seedsigner/views/tools_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 16cb34852..7c1f3ab8f 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -798,7 +798,7 @@ class ToolsSeedkeeperStartOpenCTView(View): def run(self): import os - os.system("sudo openct-contril init") + os.system("sudo openct-control init") time.sleep(1) os.system("sudo service pcscd restart") time.sleep(1) From 544ac8740e447ff93ad83d46cfadb12a43dd5a33 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Sun, 3 Dec 2023 22:49:57 -0500 Subject: [PATCH 11/55] Update smartcard_support_installation.md --- docs/smartcard_support_installation.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/smartcard_support_installation.md b/docs/smartcard_support_installation.md index ca38b9890..0258a97e6 100644 --- a/docs/smartcard_support_installation.md +++ b/docs/smartcard_support_installation.md @@ -165,8 +165,12 @@ and add the following to the end of the file }; -Once you have done this, you can boot the device with the USB SIM reader connected. +Once you have done this, you can boot the device with the USB SIM reader connected. Once the device is started, go into `tools->seedkeeper>Start OpenCT(SIM)` and the USB reader should then work until the next restart. +**Troubleshooting Connection Issues with OpenCT(Sim Readers)** + +It's possible that when you run `Start OpenCT(SIM)` that this command will fail and the device will go into a bugged state. During normal operation, the Red LED on the SIM reader will flash once or twice when you start OpenCT, but should then stay off unless you are performing operations on your SeedKeeper... If the red LED just flashes continiously after you have started OpenCT, disconnect the power, re-start the device and try again... (And if it keeps happening, try a different power supply) + _Adapted from https://timesinker.blogspot.com/2016/04/using-cheap-sim-card-readers.html_ \ No newline at end of file From 81cbd737cd64ade266e5942eb9274083a6fd6222 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Mon, 4 Dec 2023 09:58:40 -0500 Subject: [PATCH 12/55] Update smartcard_support_installation.md --- docs/smartcard_support_installation.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/smartcard_support_installation.md b/docs/smartcard_support_installation.md index 0258a97e6..9f0520feb 100644 --- a/docs/smartcard_support_installation.md +++ b/docs/smartcard_support_installation.md @@ -6,6 +6,9 @@ SeedKeeper is a open source seed storage product from Satochip which can be used This guide focuses on DIY SeedKeeper cards (which are the best for testing) but this will also work with retail SeedKeeper cards for those who prefer that simplicity... +Demo 1: Pi4 + NFC - https://youtu.be/WHVWqJJBNdA +Demo 2: PiZero 1.3 + NFC + USB Sim Reader (Phoenix) - https://youtu.be/uG44Fw3rOLg + ## Hardware Requirements ### USB Smart Card Readers Any USB smart card reader that is compatible with will work, either hard-wired (Contact) or NFC (Contactless). @@ -173,4 +176,4 @@ Once the device is started, go into `tools->seedkeeper>Start OpenCT(SIM)` and th It's possible that when you run `Start OpenCT(SIM)` that this command will fail and the device will go into a bugged state. During normal operation, the Red LED on the SIM reader will flash once or twice when you start OpenCT, but should then stay off unless you are performing operations on your SeedKeeper... If the red LED just flashes continiously after you have started OpenCT, disconnect the power, re-start the device and try again... (And if it keeps happening, try a different power supply) -_Adapted from https://timesinker.blogspot.com/2016/04/using-cheap-sim-card-readers.html_ \ No newline at end of file +_Adapted from https://timesinker.blogspot.com/2016/04/using-cheap-sim-card-readers.html_ From dbf05cfef5d055ed5140436a208cddd455b63b7b Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Mon, 4 Dec 2023 11:17:10 -0500 Subject: [PATCH 13/55] Update smartcard_support_installation.md --- docs/smartcard_support_installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/smartcard_support_installation.md b/docs/smartcard_support_installation.md index 9f0520feb..479028d7c 100644 --- a/docs/smartcard_support_installation.md +++ b/docs/smartcard_support_installation.md @@ -135,7 +135,7 @@ It's possible to obtain very cheap USB "Sim Readers" (Often Blue) for under $5 U These types of devices will *not* be automatically detected or usable on modern Systems (Windows will give you an explicit error that the PL2302 USB-to-Serial converter is not supported) but can be made to work on Linux and/or Raspberry Pi through using OpenCT and configuring it to work as a Phoenix type reader. -_Note: The version of OpenSC that can be installed via APT is buggy and will not work, it must be built from source..._ +_Note: The version of OpenST that can be installed via APT is buggy and will not work, it must be built from source..._ To Install and configure OpenCT From 65e64a3c7c44adcbe65c5e3515ba1d6d1342c158 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Mon, 4 Dec 2023 15:36:54 -0500 Subject: [PATCH 14/55] add loading screens for slow operations --- src/seedsigner/controller.py | 4 ++-- src/seedsigner/helpers/seedkeeper_utils.py | 11 +++++++++-- src/seedsigner/views/tools_views.py | 11 ++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 30a500762..6b8b6046b 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -93,7 +93,7 @@ class Controller(Singleton): rather than at the top in order avoid circular imports. """ - VERSION = "0.7.0" + VERSION = "0.7.0+SeedKeeper-ALPHA" # Declare class member vars with type hints to enable richer IDE support throughout # the code. @@ -279,7 +279,7 @@ def run(self): next_destination = Destination(MainMenuView) # Set up our one-time toast notification tip to remove the SD card - self.activate_toast(RemoveSDCardToastManagerThread()) + # self.activate_toast(RemoveSDCardToastManagerThread()) TODO ADD THIS BACK FOR ANY PROPER MERGE while True: # Destination(None) is a special case; render the Home screen diff --git a/src/seedsigner/helpers/seedkeeper_utils.py b/src/seedsigner/helpers/seedkeeper_utils.py index e1541e535..a1d5c2c25 100644 --- a/src/seedsigner/helpers/seedkeeper_utils.py +++ b/src/seedsigner/helpers/seedkeeper_utils.py @@ -2,14 +2,16 @@ from pysatochip.JCconstants import SEEDKEEPER_DIC_TYPE, SEEDKEEPER_DIC_ORIGIN, SEEDKEEPER_DIC_EXPORT_RIGHTS from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen, DireWarningScreen, seed_screens, LargeIconStatusScreen) +from seedsigner.gui.screens.screen import LoadingScreenThread + import os import time from os import urandom def init_seedkeeper(parentObject): - # os.system("ifdnfc-activate") - # time.sleep(0.1) # give some time to initialize reader... + parentObject.loading_screen = LoadingScreenThread(text="Searching for SeedKeeper") + parentObject.loading_screen.start() # Spam connecting for 10 seconds to give the user time to insert the card status = None @@ -24,6 +26,8 @@ def init_seedkeeper(parentObject): print(e) time.sleep(0.1) # Sleep for 100ms + parentObject.loading_screen.stop() + if not status: parentObject.run_screen( WarningScreen, @@ -42,6 +46,8 @@ def init_seedkeeper(parentObject): print("Initiating Secure Channel") Satochip_Connector.card_initiate_secure_channel() + + if status[3]['setup_done']: ret = seed_screens.SeedAddPassphraseScreen(title="Seedkeeper PIN").display() @@ -85,6 +91,7 @@ def init_seedkeeper(parentObject): show_back_button=True, ) + ret = seed_screens.SeedAddPassphraseScreen(title="Seedkeeper PIN").display() if ret == RET_CODE__BACK_BUTTON: diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 7c1f3ab8f..1935b7825 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -26,7 +26,6 @@ from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen, DireWarningScreen, seed_screens, LargeIconStatusScreen) - class ToolsMenuView(View): IMAGE = (" New seed", FontAwesomeIconConstants.CAMERA) DICE = ("New seed", FontAwesomeIconConstants.DICE) @@ -809,12 +808,15 @@ class ToolsSeedkeeperInstallAppletView(View): def run(self): from subprocess import run import os + from seedsigner.gui.screens.screen import LoadingScreenThread + self.loading_screen = LoadingScreenThread(text="Installing Applet") + self.loading_screen.start() data = run("java -jar /home/pi/Satochip-DIY/gp.jar --install /home/pi/Satochip-DIY/build/SeedKeeper-official-3.0.4.cap", capture_output=True, shell=True, text=True) print("StdOut:", data.stdout) print("StdErr:", data.stderr) - os.system("ifdnfc-activate") + self.loading_screen.stop() return Destination(MainMenuView) @@ -822,11 +824,14 @@ class ToolsSeedkeeperUninstallAppletView(View): def run(self): from subprocess import run import os + from seedsigner.gui.screens.screen import LoadingScreenThread + self.loading_screen = LoadingScreenThread(text="Uninstalling Applet") + self.loading_screen.start() data = run("java -jar /home/pi/Satochip-DIY/gp.jar --uninstall /home/pi/Satochip-DIY/build/SeedKeeper-official-3.0.4.cap", capture_output=True, shell=True, text=True) print("StdOut:", data.stdout) print("StdErr:", data.stderr) - os.system("ifdnfc-activate") + self.loading_screen.stop() return Destination(MainMenuView) From 29db388b4f8f5c23f82b9b11145a6a764e3d8766 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Mon, 4 Dec 2023 16:42:52 -0500 Subject: [PATCH 15/55] add more useful errors for managing applets --- src/seedsigner/views/tools_views.py | 75 ++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 1935b7825..e63ae9cc7 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -813,10 +813,49 @@ def run(self): self.loading_screen.start() data = run("java -jar /home/pi/Satochip-DIY/gp.jar --install /home/pi/Satochip-DIY/build/SeedKeeper-official-3.0.4.cap", capture_output=True, shell=True, text=True) + + self.loading_screen.stop() + print("StdOut:", data.stdout) print("StdErr:", data.stderr) - self.loading_screen.stop() + data.stderr = data.stderr.replace("Warning: no keys given, defaulting to 404142434445464748494A4B4C4D4E4F", "") + print("StdErr:", len(data.stderr)) + + if len(data.stderr) > 1: + # If it fails, report the error back (And make it more human readable for common errors) + failureText = data.stderr + if "Applet loading not allowed" in data.stderr: + failureText = "Applet is already installed..." + + if "Multiple readers, must choose one" in data.stderr: + failureText = "Multiple readers connected, please run with a single reader connected/activated..." + + if "0x6444" in data.stderr or "0x6F00" in data.stderr: + failureText = "Incompatible Javacard..." + + if "Not enough memory space" in data.stderr: + failureText = "Not enough space on Javacard for Applet..." + + if " Card cryptogram invalid" in data.stderr: + failureText = "Card is locked with custom keys. Please refer to the Satochip-DIY documentation..." + + self.run_screen( + WarningScreen, + title="Install Failed", + status_headline=None, + text=failureText, + show_back_button=True, + ) + else: + print("Applet Installed") + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"Applet Installed", + show_back_button=False, + ) return Destination(MainMenuView) @@ -829,9 +868,41 @@ def run(self): self.loading_screen.start() data = run("java -jar /home/pi/Satochip-DIY/gp.jar --uninstall /home/pi/Satochip-DIY/build/SeedKeeper-official-3.0.4.cap", capture_output=True, shell=True, text=True) + self.loading_screen.stop() + print("StdOut:", data.stdout) print("StdErr:", data.stderr) - self.loading_screen.stop() + data.stderr = data.stderr.replace("Warning: no keys given, defaulting to 404142434445464748494A4B4C4D4E4F", "") + print("StdErr:", len(data.stderr)) + + if len(data.stderr) > 1: + # If it fails, report the error back (And make it more human readable for common errors) + failureText = data.stderr + if "is not present on card" in data.stderr: + failureText = "Applet is not on the card, nothing to uninstall..." + + if "Multiple readers, must choose one" in data.stderr: + failureText = "Multiple readers connected, please run with a single reader connected/activated..." + + if " Card cryptogram invalid" in data.stderr: + failureText = "Card is locked with custom keys. Please refer to the Satochip-DIY documentation..." + + self.run_screen( + WarningScreen, + title="Uninstall Failed", + status_headline=None, + text=failureText, + show_back_button=True, + ) + else: + print("Applet Uninstalled") + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"Applet Uninstalled", + show_back_button=False, + ) return Destination(MainMenuView) From cad9333e261be9e53da6f348687b19f8c782ad1b Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Mon, 4 Dec 2023 17:36:14 -0500 Subject: [PATCH 16/55] Update tools_views.py --- src/seedsigner/views/tools_views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index e63ae9cc7..517ae877b 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -840,6 +840,9 @@ def run(self): if " Card cryptogram invalid" in data.stderr: failureText = "Card is locked with custom keys. Please refer to the Satochip-DIY documentation..." + if "SCARD_E_NO_SMARTCARD" in data.stderr: + failureText = "Unable to detect Card and/or Reader..." + self.run_screen( WarningScreen, title="Install Failed", @@ -888,6 +891,9 @@ def run(self): if " Card cryptogram invalid" in data.stderr: failureText = "Card is locked with custom keys. Please refer to the Satochip-DIY documentation..." + if "SCARD_E_NO_SMARTCARD" in data.stderr: + failureText = "Unable to detect Card and/or Reader..." + self.run_screen( WarningScreen, title="Uninstall Failed", From 17b496027bcaea37c7e1a590f25e710efcacd073 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Sun, 10 Dec 2023 23:06:33 -0500 Subject: [PATCH 17/55] Add additional NFC Scan test tool --- src/seedsigner/views/tools_views.py | 108 +++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 517ae877b..d1bf557d0 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -715,13 +715,14 @@ def run(self): ****************************************************************************""" class ToolsSeedkeeperMenuView(View): CHANGE_PIN = ("Change PIN") + TEST_NFC = ("Test NFC Scan") IFDNFC_ACTIVATE = ("Start PN532(PN532)") OPENCT_ACTIVATE = ("Start OpenCT(SIM)") INSTALL_APPLET = ("Install Applet") UNINSTALL_APPLET = ("Uninstall Applet") def run(self): - button_data = [self.CHANGE_PIN, self.IFDNFC_ACTIVATE, self.OPENCT_ACTIVATE, self.INSTALL_APPLET, self.UNINSTALL_APPLET] + button_data = [self.CHANGE_PIN, self.TEST_NFC, self.IFDNFC_ACTIVATE, self.OPENCT_ACTIVATE, self.INSTALL_APPLET, self.UNINSTALL_APPLET] selected_menu_num = self.run_screen( ButtonListScreen, @@ -736,6 +737,9 @@ def run(self): elif button_data[selected_menu_num] == self.CHANGE_PIN: return Destination(ToolsSeedkeeperChangePinView) + elif button_data[selected_menu_num] == self.TEST_NFC: + return Destination(ToolsSeedkeeperTestNFCView) + elif button_data[selected_menu_num] == self.IFDNFC_ACTIVATE: return Destination(ToolsSeedkeeperStartIfdNFCView) @@ -784,6 +788,108 @@ def run(self): return Destination(MainMenuView) +class ToolsSeedkeeperTestNFCView(View): + def run(self): + + from seedsigner.gui.screens.screen import LoadingScreenThread + self.loading_screen = LoadingScreenThread(text="Scanning for NFC Tag") + self.loading_screen.start() + + """Quick start example that presents how to use libnfc""" + import sys + import nfc + import binascii + + context = nfc.init() + nfcdevice = nfc.open(context) + if nfcdevice is None: + self.loading_screen.stop() + print('ERROR: Unable to open NFC device.') + self.run_screen( + WarningScreen, + title="NFC Failure", + status_headline=None, + text=f"ERROR: Unable to open NFC device.", + show_back_button=True, + ) + return Destination(BackStackView) + + if nfc.initiator_init(nfcdevice) < 0: + self.loading_screen.stop() + print('ERROR: Unable to init NFC device.') + self.run_screen( + WarningScreen, + title="NFC Failure", + status_headline=None, + text=f"ERROR: Unable to init NFC device.", + show_back_button=True, + ) + return Destination(BackStackView) + + print('NFC reader: %s opened' % nfc.device_get_name(nfcdevice)) + + nfcmodulation = nfc.modulation() + nfcmodulation.nmt = nfc.NMT_ISO14443A + nfcmodulation.nbr = nfc.NBR_106 + + nt = nfc.target() + + # Scan for 15 seconds + ret = nfc.initiator_poll_target(nfcdevice, nfcmodulation, 1, 100, 1, nt) + + self.loading_screen.stop() + + + if ret and nt.nti.nai.szUidLen: + + print('The following (NFC) ISO14443A tag was found:') + print(' ATQA (SENS_RES): ', end='') + nfc.print_hex(nt.nti.nai.abtAtqa, 2) + id = 1 + if nt.nti.nai.abtUid[0] == 8: + id = 3 + print(' UID (NFCID%d): ' % id , end='') + nfc.print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen) + foundtext="UID:\n" + binascii.hexlify(nt.nti.nai.abtUid).decode()[:14] + + print(' SAK (SEL_RES): ', end='') + print(nt.nti.nai.btSak) + if nt.nti.nai.szAtsLen: + print(' ATS (ATR): ', end='') + nfc.print_hex(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen) + foundtext = foundtext + "\nATR:\n" + binascii.hexlify(nt.nti.nai.abtAts).decode()[:28] + + self.run_screen( + LargeIconStatusScreen, + title="Found NFC Tag", + status_headline=None, + text=foundtext, + show_back_button=False, + ) + elif ret: + print('Warning: IFD-NFC Conflict.') + self.run_screen( + WarningScreen, + title="NFC Conflict", + status_headline=None, + text=f"Can't scan when IFD-NFC Active", + show_back_button=True, + ) + else: + print('Warning: No NFC Tag Detected.') + self.run_screen( + WarningScreen, + title="Warning", + status_headline=None, + text=f"Warning: No NFC Tag Detected.", + show_back_button=True, + ) + + nfc.close(nfcdevice) + nfc.exit(context) + + return Destination(MainMenuView) + class ToolsSeedkeeperStartIfdNFCView(View): def run(self): import os From 2ca1e5d8e93c085a60bf8ba50037534428563c72 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Mon, 11 Dec 2023 09:59:48 -0500 Subject: [PATCH 18/55] Update smartcard_support_installation.md --- docs/smartcard_support_installation.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/smartcard_support_installation.md b/docs/smartcard_support_installation.md index 479028d7c..919d3435d 100644 --- a/docs/smartcard_support_installation.md +++ b/docs/smartcard_support_installation.md @@ -106,10 +106,26 @@ Create the file `/etc/reader.conf.d/libifdnfc` and add the following (`sudo nano **Activating IFD-NFC** -You will notice that there is a menu option to `Start PN532(PN532)` under the tools->SeedKeeper menu. Basically IFDNFC only needs to be run once on each boot, after which you need to restart the SeedSigner app. (But not the device) +You will notice that there is a menu option to `Start PN532(PN532)` under the tools->SeedKeeper menu. Basically IFDNFC only needs to be run once on each boot, after which you may also need to restart the SeedSigner app. (But not the device) Applet management operations (Installing, uninstalling, etc) often terminate the `idfnfc` process after completing, so if you can no longer do SeedKeeper operations like change PIN, load or save secrets, immediatly after flashing the applet, then this is likely why. (Just re-run the `ifdnfc-activate` process I mention above, restart the app and it should work fine) +### Python Bindings for LibNFC (Optional: Useful for Debugging the PN532 NFC) + +Install some additional build packages + + sudo apt install cmake + +Download, install and build + + cd ~ + git clone https://github.com/xantares/nfc-bindings.git + cd nfc-bindings + cmake cmake -DCMAKE_INSTALL_PREFIX=~/.local -DPYTHON_EXECUTABLE=/usr/local/bin/python3.10 -DPYTHON_LIBRARY=/usr/local/lib/libpython3.10.a -DPYTHON_INCLUDE_DIR=/usr/local/include/python3.10 + make install + cp /home/pi/.local/lib/python3.7/site-packages/_nfc.py ~/.envs/seedsigner-env/lib/python3.10/site-packages/nfc.py + cp /home/pi/.local/lib/python3.7/site-packages/_nfc.so ~/.envs/seedsigner-env/lib/python3.10/site-packages/_nfc.so + ### Javacard Managment Tools (Optional: Needed to flash SeedKeeper to Javacards) You just need to install openjdk-8-jdk and Apache Ant From 217897e4d18b6c5f3fff6725a2a88bcf05cb9b9e Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Thu, 14 Dec 2023 11:31:51 -0500 Subject: [PATCH 19/55] *Add NFC diagnostics *Add smartcard interface to settings menu *Improve applet install/uninstall functionality --- src/seedsigner/helpers/seedkeeper_utils.py | 80 +++++++++- src/seedsigner/models/settings.py | 39 ++++- src/seedsigner/models/settings_definition.py | 22 ++- src/seedsigner/views/tools_views.py | 146 +++++++------------ 4 files changed, 190 insertions(+), 97 deletions(-) diff --git a/src/seedsigner/helpers/seedkeeper_utils.py b/src/seedsigner/helpers/seedkeeper_utils.py index a1d5c2c25..c6639efb0 100644 --- a/src/seedsigner/helpers/seedkeeper_utils.py +++ b/src/seedsigner/helpers/seedkeeper_utils.py @@ -139,4 +139,82 @@ def init_seedkeeper(parentObject): return Satochip_Connector - \ No newline at end of file +def run_globalplatform(parentObject, command, loadingText = "Loading", successtext = "Success"): + from subprocess import run + + parentObject.loading_screen = LoadingScreenThread(text=loadingText) + parentObject.loading_screen.start() + + commandString = "java -jar /home/pi/Satochip-DIY/gp.jar " + command + + data = run(commandString, capture_output=True, shell=True, text=True) + parentObject.loading_screen.stop() + + print("StdOut:", data.stdout) + print("StdErr:", data.stderr) + + #data.stderr = data.stderr.replace("Warning: no keys given, defaulting to 404142434445464748494A4B4C4D4E4F", "") + + data.stderr = data.stderr.split('\n') + + errors_cleaned = [] + for errorLine in data.stderr: + if "[INFO]" in errorLine: + continue + elif "404142434445464748494A4B4C4D4E4F" in errorLine: + continue + elif len(errorLine) < 1: + continue + + errors_cleaned.append(errorLine) + + print("StdErr (Cleaned):", errors_cleaned) + + errors_cleaned = " ".join(errors_cleaned) + + if len(errors_cleaned) > 1: + # If it fails, report the error back (And make it more human readable for common errors) + failureText = errors_cleaned + if "is not present on card" in errors_cleaned: + failureText = "Applet is not on the card, nothing to uninstall..." + + if "Multiple readers, must choose one" in errors_cleaned: + failureText = "Multiple readers connected, please run with a single reader connected/activated..." + + if " Card cryptogram invalid" in errors_cleaned: + failureText = "Card is locked with custom keys. Please refer to the Satochip-DIY documentation..." + + if "SCARD_E_NO_SMARTCARD" in errors_cleaned: + failureText = "Unable to detect Card and/or Reader..." + + if "Applet loading not allowed" in errors_cleaned: + failureText = "Applet is already installed..." + + if "0x6444" in errors_cleaned or "0x6F00" in errors_cleaned: + failureText = "Incompatible Javacard..." + + if "Not enough memory space" in errors_cleaned: + failureText = "Not enough space on Javacard for Applet..." + + if "SCARD_E_NO_SMARTCARD" in errors_cleaned: + failureText = "Unable to detect Card and/or Reader..." + + parentObject.run_screen( + WarningScreen, + title="Failed", + status_headline=None, + text=failureText, + show_back_button=True, + ) + else: + if successtext: + print(successtext) + parentObject.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=successtext, + show_back_button=False, + ) + + return data.stdout \ No newline at end of file diff --git a/src/seedsigner/models/settings.py b/src/seedsigner/models/settings.py index f0b1c97f0..51f571d97 100644 --- a/src/seedsigner/models/settings.py +++ b/src/seedsigner/models/settings.py @@ -115,6 +115,10 @@ def save(self): def update(self, new_settings: dict): + print("Updating Settings") + print("Existing Settings:", self._data) + print() + print("New Settings:", new_settings) """ Replaces the current settings with the incoming dict. @@ -140,9 +144,11 @@ def update(self, new_settings: dict): # Can't just merge the _data dict; have to replace keys they have in common # (otherwise list values will be merged instead of replaced). + # Do this by running set_value for key, value in new_settings.items(): - self._data.pop(key, None) - self._data[key] = value + #self._data.pop(key, None) + #self._data[key] = value + self.set_value(key,value) def set_value(self, attr_name: str, value: any): @@ -165,10 +171,37 @@ def set_value(self, attr_name: str, value: any): print(f"Removed {self.SETTINGS_FILENAME}") except: print(f"{self.SETTINGS_FILENAME} not found to be removed") + + # Special handling for enabling Smartcard readers + if attr_name == SettingsConstants.SETTING__SMARTCARD_INTERFACES: + import time + print("Smartcard Interface Changed") + + if "pn532" in value and "pn532" not in self._data[attr_name]: + print("PN532 Enabled") + os.system("ifdnfc-activate yes") + + if "pn532" not in value and "pn532" in self._data[attr_name]: + print("PN532 Disabled") + os.system("ifdnfc-activate no") + + if "phoenix" in value and "phoenix" not in self._data[attr_name]: + print("Phoenix Enabled") + os.system("sudo openct-control init") + time.sleep(0.5) + os.system("sudo service pcscd restart") + + if "phoenix" not in value and "phoenix" in self._data[attr_name]: + print("Phoenix Disabled") + print("Phoenix Enabled") + os.system("sudo openct-control shutdown") + time.sleep(0.5) + os.system("sudo service pcscd restart") + self._data[attr_name] = value self.save() - + def get_value(self, attr_name: str): """ diff --git a/src/seedsigner/models/settings_definition.py b/src/seedsigner/models/settings_definition.py index 76534cecd..8e9c3fc68 100644 --- a/src/seedsigner/models/settings_definition.py +++ b/src/seedsigner/models/settings_definition.py @@ -92,6 +92,18 @@ class SettingsConstants: (REGTEST, "Regtest") ] + #Smartcard Related Constants + SMARTCARD_INTERFACE_NONE = "none" + SMARTCARD_INTERFACE_USB = "usb" + SMARTCARD_INTERFACE_PN532 = "pn532" + SMARTCARD_INTERFACE_PHOENIX = "phoenix" + ALL_SMARTCARD_INTERFACES = [ + (SMARTCARD_INTERFACE_NONE, "None"), + (SMARTCARD_INTERFACE_USB, "USB PC/SC Reader"), + (SMARTCARD_INTERFACE_PN532, "PN532 via GPIO"), + (SMARTCARD_INTERFACE_PHOENIX, "Phoenix via USB") + ] + @classmethod def map_network_to_embit(cls, network) -> str: if network == SettingsConstants.MAINNET: @@ -149,6 +161,7 @@ def map_network_to_embit(cls, network) -> str: SETTING__PERSISTENT_SETTINGS = "persistent_settings" SETTING__COORDINATORS = "coordinators" SETTING__BTC_DENOMINATION = "denomination" + SETTING__SMARTCARD_INTERFACES = "smartcard_interfaces" SETTING__NETWORK = "network" SETTING__QR_DENSITY = "qr_density" @@ -379,7 +392,14 @@ class SettingsDefinition: type=SettingsConstants.TYPE__SELECT_1, selection_options=SettingsConstants.ALL_BTC_DENOMINATIONS, default_value=SettingsConstants.BTC_DENOMINATION__THRESHOLD), - + + SettingsEntry(category=SettingsConstants.CATEGORY__SYSTEM, + attr_name=SettingsConstants.SETTING__SMARTCARD_INTERFACES, + abbreviated_name="screaders", + display_name="Smartcard Interfaces", + type=SettingsConstants.TYPE__SELECT_1, + selection_options=SettingsConstants.ALL_SMARTCARD_INTERFACES, + default_value=SettingsConstants.SMARTCARD_INTERFACE_NONE), # Advanced options SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES, diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index d1bf557d0..5a614cd21 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -32,10 +32,10 @@ class ToolsMenuView(View): KEYBOARD = ("Calc 12th/24th word", FontAwesomeIconConstants.KEYBOARD) EXPLORER = "Address Explorer" ADDRESS = "Verify address" - SEEDKEEPER = ("SeedKeeper", FontAwesomeIconConstants.LOCK) + SMARTCARD = ("Smartcard Tools", FontAwesomeIconConstants.LOCK) def run(self): - button_data = [self.IMAGE, self.DICE, self.KEYBOARD, self.EXPLORER, self.ADDRESS, self.SEEDKEEPER] + button_data = [self.IMAGE, self.DICE, self.KEYBOARD, self.EXPLORER, self.ADDRESS, self.SMARTCARD] selected_menu_num = self.run_screen( ButtonListScreen, @@ -63,8 +63,8 @@ def run(self): from seedsigner.views.scan_views import ScanAddressView return Destination(ScanAddressView) - elif button_data[selected_menu_num] == self.SEEDKEEPER: - return Destination(ToolsSeedkeeperMenuView) + elif button_data[selected_menu_num] == self.SMARTCARD: + return Destination(ToolsSmartcardMenuView) @@ -713,7 +713,7 @@ def run(self): """**************************************************************************** Seedkeeper Views ****************************************************************************""" -class ToolsSeedkeeperMenuView(View): +class ToolsSmartcardMenuView(View): CHANGE_PIN = ("Change PIN") TEST_NFC = ("Test NFC Scan") IFDNFC_ACTIVATE = ("Start PN532(PN532)") @@ -722,7 +722,7 @@ class ToolsSeedkeeperMenuView(View): UNINSTALL_APPLET = ("Uninstall Applet") def run(self): - button_data = [self.CHANGE_PIN, self.TEST_NFC, self.IFDNFC_ACTIVATE, self.OPENCT_ACTIVATE, self.INSTALL_APPLET, self.UNINSTALL_APPLET] + button_data = [self.CHANGE_PIN, self.TEST_NFC, self.INSTALL_APPLET, self.UNINSTALL_APPLET] selected_menu_num = self.run_screen( ButtonListScreen, @@ -795,8 +795,10 @@ def run(self): self.loading_screen = LoadingScreenThread(text="Scanning for NFC Tag") self.loading_screen.start() - """Quick start example that presents how to use libnfc""" - import sys + os.system("ifdnfc-activate no") # Need to disable IFD-NFC to be able to scan using libnfc-bindings... + + time.sleep(0.2) #Just give the loading screen a chance to load before moving on... + import nfc import binascii @@ -809,7 +811,7 @@ def run(self): WarningScreen, title="NFC Failure", status_headline=None, - text=f"ERROR: Unable to open NFC device.", + text=f"ERROR: Unable to open NFC device. \n(May not be connected)", show_back_button=True, ) return Destination(BackStackView) @@ -830,7 +832,7 @@ def run(self): nfcmodulation = nfc.modulation() nfcmodulation.nmt = nfc.NMT_ISO14443A - nfcmodulation.nbr = nfc.NBR_106 + nfcmodulation.nbr = nfc.NBR_847 #Test at the highest baud rate for the best simulation of Smartcard operation nt = nfc.target() @@ -839,7 +841,6 @@ def run(self): self.loading_screen.stop() - if ret and nt.nti.nai.szUidLen: print('The following (NFC) ISO14443A tag was found:') @@ -887,6 +888,11 @@ def run(self): nfc.close(nfcdevice) nfc.exit(context) + + scinterface = self.settings.get_value(SettingsConstants.SETTING__SMARTCARD_INTERFACES) + + if "pn532" in scinterface: + os.system("ifdnfc-activate yes") # Need to re-enable IFD-NFC if required... return Destination(MainMenuView) @@ -894,7 +900,7 @@ class ToolsSeedkeeperStartIfdNFCView(View): def run(self): import os - os.system("ifdnfc-activate") + os.system("ifdnfc-activate yes") time.sleep(1) return Destination(MainMenuView) @@ -915,56 +921,23 @@ def run(self): from subprocess import run import os from seedsigner.gui.screens.screen import LoadingScreenThread - self.loading_screen = LoadingScreenThread(text="Installing Applet") - self.loading_screen.start() - - data = run("java -jar /home/pi/Satochip-DIY/gp.jar --install /home/pi/Satochip-DIY/build/SeedKeeper-official-3.0.4.cap", capture_output=True, shell=True, text=True) - - self.loading_screen.stop() - - print("StdOut:", data.stdout) - print("StdErr:", data.stderr) - - data.stderr = data.stderr.replace("Warning: no keys given, defaulting to 404142434445464748494A4B4C4D4E4F", "") - print("StdErr:", len(data.stderr)) - - if len(data.stderr) > 1: - # If it fails, report the error back (And make it more human readable for common errors) - failureText = data.stderr - if "Applet loading not allowed" in data.stderr: - failureText = "Applet is already installed..." - if "Multiple readers, must choose one" in data.stderr: - failureText = "Multiple readers connected, please run with a single reader connected/activated..." + cap_files = os.listdir('/home/pi/Satochip-DIY/build/') - if "0x6444" in data.stderr or "0x6F00" in data.stderr: - failureText = "Incompatible Javacard..." + selected_file_num = self.run_screen( + ButtonListScreen, + title="Select Applet", + is_button_text_centered=False, + button_data=cap_files + ) - if "Not enough memory space" in data.stderr: - failureText = "Not enough space on Javacard for Applet..." + if selected_file_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) - if " Card cryptogram invalid" in data.stderr: - failureText = "Card is locked with custom keys. Please refer to the Satochip-DIY documentation..." + applet_file = cap_files[selected_file_num] + print("Selected:", applet_file) - if "SCARD_E_NO_SMARTCARD" in data.stderr: - failureText = "Unable to detect Card and/or Reader..." - - self.run_screen( - WarningScreen, - title="Install Failed", - status_headline=None, - text=failureText, - show_back_button=True, - ) - else: - print("Applet Installed") - self.run_screen( - LargeIconStatusScreen, - title="Success", - status_headline=None, - text=f"Applet Installed", - show_back_button=False, - ) + installed_applets = seedkeeper_utils.run_globalplatform(self,"--install /home/pi/Satochip-DIY/build/" + applet_file, "Installing Applet", "Applet Installed") return Destination(MainMenuView) @@ -973,48 +946,37 @@ def run(self): from subprocess import run import os from seedsigner.gui.screens.screen import LoadingScreenThread - self.loading_screen = LoadingScreenThread(text="Uninstalling Applet") - self.loading_screen.start() - data = run("java -jar /home/pi/Satochip-DIY/gp.jar --uninstall /home/pi/Satochip-DIY/build/SeedKeeper-official-3.0.4.cap", capture_output=True, shell=True, text=True) - self.loading_screen.stop() + installed_applets = seedkeeper_utils.run_globalplatform(self,"-l -v", "Checking Installed Applets", None) - print("StdOut:", data.stdout) - print("StdErr:", data.stderr) + installed_applets = installed_applets.split('\n') - data.stderr = data.stderr.replace("Warning: no keys given, defaulting to 404142434445464748494A4B4C4D4E4F", "") - print("StdErr:", len(data.stderr)) + installed_applets_aids = [] + installed_applets_list = [] - if len(data.stderr) > 1: - # If it fails, report the error back (And make it more human readable for common errors) - failureText = data.stderr - if "is not present on card" in data.stderr: - failureText = "Applet is not on the card, nothing to uninstall..." + for line in installed_applets: + if "PKG: " in line: + package_info = line.split() + print(package_info) + # Ignore system packages + if package_info[1] in ['A0000001515350', 'A00000016443446F634C697465', 'A0000000620204', 'A0000000620202', 'D27600012401']: + continue + + installed_applets_list.append(package_info[3][2:-2]) + installed_applets_aids.append(package_info[1]) - if "Multiple readers, must choose one" in data.stderr: - failureText = "Multiple readers connected, please run with a single reader connected/activated..." + selected_applet_num = self.run_screen( + ButtonListScreen, + title="Select Applet", + is_button_text_centered=False, + button_data=installed_applets_list + ) - if " Card cryptogram invalid" in data.stderr: - failureText = "Card is locked with custom keys. Please refer to the Satochip-DIY documentation..." + if selected_applet_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) - if "SCARD_E_NO_SMARTCARD" in data.stderr: - failureText = "Unable to detect Card and/or Reader..." + applet_aid = installed_applets_aids[selected_applet_num] - self.run_screen( - WarningScreen, - title="Uninstall Failed", - status_headline=None, - text=failureText, - show_back_button=True, - ) - else: - print("Applet Uninstalled") - self.run_screen( - LargeIconStatusScreen, - title="Success", - status_headline=None, - text=f"Applet Uninstalled", - show_back_button=False, - ) + seedkeeper_utils.run_globalplatform(self,"--delete " + applet_aid + " -force", "Uninstalling Applet", "Applet Uninstalled") return Destination(MainMenuView) From 55ebee885a9db1de797cd4a3df9339dec170c938 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Thu, 14 Dec 2023 11:53:19 -0500 Subject: [PATCH 20/55] menu fix --- src/seedsigner/helpers/seedkeeper_utils.py | 7 ++- src/seedsigner/views/tools_views.py | 53 +++++++++++----------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/seedsigner/helpers/seedkeeper_utils.py b/src/seedsigner/helpers/seedkeeper_utils.py index c6639efb0..9e976905e 100644 --- a/src/seedsigner/helpers/seedkeeper_utils.py +++ b/src/seedsigner/helpers/seedkeeper_utils.py @@ -204,8 +204,11 @@ def run_globalplatform(parentObject, command, loadingText = "Loading", successte title="Failed", status_headline=None, text=failureText, - show_back_button=True, + show_back_button=False, ) + + return None + else: if successtext: print(successtext) @@ -217,4 +220,4 @@ def run_globalplatform(parentObject, command, loadingText = "Loading", successte show_back_button=False, ) - return data.stdout \ No newline at end of file + return data.stdout \ No newline at end of file diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 5a614cd21..a81aae404 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -949,34 +949,35 @@ def run(self): installed_applets = seedkeeper_utils.run_globalplatform(self,"-l -v", "Checking Installed Applets", None) - installed_applets = installed_applets.split('\n') - - installed_applets_aids = [] - installed_applets_list = [] - - for line in installed_applets: - if "PKG: " in line: - package_info = line.split() - print(package_info) - # Ignore system packages - if package_info[1] in ['A0000001515350', 'A00000016443446F634C697465', 'A0000000620204', 'A0000000620202', 'D27600012401']: - continue - - installed_applets_list.append(package_info[3][2:-2]) - installed_applets_aids.append(package_info[1]) - - selected_applet_num = self.run_screen( - ButtonListScreen, - title="Select Applet", - is_button_text_centered=False, - button_data=installed_applets_list - ) + if installed_applets: + installed_applets = installed_applets.split('\n') + + installed_applets_aids = [] + installed_applets_list = [] + + for line in installed_applets: + if "PKG: " in line: + package_info = line.split() + print(package_info) + # Ignore system packages + if package_info[1] in ['A0000001515350', 'A00000016443446F634C697465', 'A0000000620204', 'A0000000620202', 'D27600012401']: + continue + + installed_applets_list.append(package_info[3][2:-2]) + installed_applets_aids.append(package_info[1]) + + selected_applet_num = self.run_screen( + ButtonListScreen, + title="Select Applet", + is_button_text_centered=False, + button_data=installed_applets_list + ) - if selected_applet_num == RET_CODE__BACK_BUTTON: - return Destination(BackStackView) + if selected_applet_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) - applet_aid = installed_applets_aids[selected_applet_num] + applet_aid = installed_applets_aids[selected_applet_num] - seedkeeper_utils.run_globalplatform(self,"--delete " + applet_aid + " -force", "Uninstalling Applet", "Applet Uninstalled") + seedkeeper_utils.run_globalplatform(self,"--delete " + applet_aid + " -force", "Uninstalling Applet", "Applet Uninstalled") return Destination(MainMenuView) From d66457129c602b0daac394d52ff6f0e5ab396cc2 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Thu, 14 Dec 2023 12:47:15 -0500 Subject: [PATCH 21/55] Make Phoenix initialisation more reliable --- src/seedsigner/models/settings.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/seedsigner/models/settings.py b/src/seedsigner/models/settings.py index 51f571d97..6efdd6d6b 100644 --- a/src/seedsigner/models/settings.py +++ b/src/seedsigner/models/settings.py @@ -177,28 +177,28 @@ def set_value(self, attr_name: str, value: any): import time print("Smartcard Interface Changed") - if "pn532" in value and "pn532" not in self._data[attr_name]: - print("PN532 Enabled") - os.system("ifdnfc-activate yes") - - if "pn532" not in value and "pn532" in self._data[attr_name]: - print("PN532 Disabled") - os.system("ifdnfc-activate no") - + # Execution order matters here if swithing from Phoenix to PN352, basically we want to disable phoenix first and then enable PN532 if "phoenix" in value and "phoenix" not in self._data[attr_name]: print("Phoenix Enabled") - os.system("sudo openct-control init") - time.sleep(0.5) + os.system("sudo openct-control init") # OpenCT needs a bit of time to get going before restarting PCSCD (At least two seconds) to work reliabily + time.sleep(3) os.system("sudo service pcscd restart") if "phoenix" not in value and "phoenix" in self._data[attr_name]: print("Phoenix Disabled") print("Phoenix Enabled") os.system("sudo openct-control shutdown") - time.sleep(0.5) + time.sleep(3) os.system("sudo service pcscd restart") - - + + if "pn532" in value and "pn532" not in self._data[attr_name]: + print("PN532 Enabled") + os.system("ifdnfc-activate yes") + + if "pn532" not in value and "pn532" in self._data[attr_name]: + print("PN532 Disabled") + os.system("ifdnfc-activate no") + self._data[attr_name] = value self.save() From f9c98d1bd400f20f9b7aa814a9cdaf734794def7 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Thu, 14 Dec 2023 15:43:12 -0500 Subject: [PATCH 22/55] Add ability to disable USB ports when not needed --- docs/smartcard_support_installation.md | 8 ++++++++ src/seedsigner/models/settings.py | 17 ++++++++++++++++- src/seedsigner/models/settings_definition.py | 4 ++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/smartcard_support_installation.md b/docs/smartcard_support_installation.md index 919d3435d..c9b2b367a 100644 --- a/docs/smartcard_support_installation.md +++ b/docs/smartcard_support_installation.md @@ -126,6 +126,14 @@ Download, install and build cp /home/pi/.local/lib/python3.7/site-packages/_nfc.py ~/.envs/seedsigner-env/lib/python3.10/site-packages/nfc.py cp /home/pi/.local/lib/python3.7/site-packages/_nfc.so ~/.envs/seedsigner-env/lib/python3.10/site-packages/_nfc.so +### hub-ctrl (Optional: Disables USB ports when not needed for Smartcard Interface) + + cd ~ + git clone https://github.com/yy502/hub-ctrl.git + cd hub-ctrl + gcc -o hub-ctrl hub-ctrl.c -lusb -std=c99 + sudo cp hub-ctrl /usr/local/bin/hub-ctrl + ### Javacard Managment Tools (Optional: Needed to flash SeedKeeper to Javacards) You just need to install openjdk-8-jdk and Apache Ant diff --git a/src/seedsigner/models/settings.py b/src/seedsigner/models/settings.py index 6efdd6d6b..df02d38fc 100644 --- a/src/seedsigner/models/settings.py +++ b/src/seedsigner/models/settings.py @@ -177,6 +177,22 @@ def set_value(self, attr_name: str, value: any): import time print("Smartcard Interface Changed") + # Basically just check through a a bunch of possible USB hubs and ports and enable/disable them all (Should cover all RPi models, RPi4 has lots of USB ports...) + if "usb" not in value and "usb" in self._data[attr_name]: + print("Disabling USB") + for hub in range(3): + for port in range(3): + os.system("sudo hub-ctrl -H " + str(hub) + " -P " + str(port) + " -p 0") + + if "usb" in value and "usb" not in self._data[attr_name]: + print("Enabling USB") + for hub in range(3): + for port in range(5): + os.system("sudo hub-ctrl -H " + str(hub) + " -P " + str(port) + " -p 1") + + time.sleep(1) + os.system("sudo service pcscd restart") # PCSC doesn't always work properly after USB ports have been re-enabled + # Execution order matters here if swithing from Phoenix to PN352, basically we want to disable phoenix first and then enable PN532 if "phoenix" in value and "phoenix" not in self._data[attr_name]: print("Phoenix Enabled") @@ -186,7 +202,6 @@ def set_value(self, attr_name: str, value: any): if "phoenix" not in value and "phoenix" in self._data[attr_name]: print("Phoenix Disabled") - print("Phoenix Enabled") os.system("sudo openct-control shutdown") time.sleep(3) os.system("sudo service pcscd restart") diff --git a/src/seedsigner/models/settings_definition.py b/src/seedsigner/models/settings_definition.py index 8e9c3fc68..584f9d7f5 100644 --- a/src/seedsigner/models/settings_definition.py +++ b/src/seedsigner/models/settings_definition.py @@ -96,7 +96,7 @@ class SettingsConstants: SMARTCARD_INTERFACE_NONE = "none" SMARTCARD_INTERFACE_USB = "usb" SMARTCARD_INTERFACE_PN532 = "pn532" - SMARTCARD_INTERFACE_PHOENIX = "phoenix" + SMARTCARD_INTERFACE_PHOENIX = "phoenix-usb" ALL_SMARTCARD_INTERFACES = [ (SMARTCARD_INTERFACE_NONE, "None"), (SMARTCARD_INTERFACE_USB, "USB PC/SC Reader"), @@ -399,7 +399,7 @@ class SettingsDefinition: display_name="Smartcard Interfaces", type=SettingsConstants.TYPE__SELECT_1, selection_options=SettingsConstants.ALL_SMARTCARD_INTERFACES, - default_value=SettingsConstants.SMARTCARD_INTERFACE_NONE), + default_value=SettingsConstants.SMARTCARD_INTERFACE_USB), # Advanced options SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES, From 8f2b2cb7ca7abfc2739bb97b4fca2a242f818c45 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Sun, 17 Dec 2023 23:14:12 -0500 Subject: [PATCH 23/55] re-org menus add satochip functions(initialise, seed and enable 2fa) --- src/seedsigner/helpers/seedkeeper_utils.py | 44 ++- src/seedsigner/models/encode_qr.py | 13 + src/seedsigner/models/qr_type.py | 2 + src/seedsigner/views/seed_views.py | 4 +- src/seedsigner/views/settings_views.py | 116 ++++++++ src/seedsigner/views/tools_views.py | 323 +++++++++++++-------- 6 files changed, 371 insertions(+), 131 deletions(-) diff --git a/src/seedsigner/helpers/seedkeeper_utils.py b/src/seedsigner/helpers/seedkeeper_utils.py index 9e976905e..ac293699b 100644 --- a/src/seedsigner/helpers/seedkeeper_utils.py +++ b/src/seedsigner/helpers/seedkeeper_utils.py @@ -9,8 +9,8 @@ import time from os import urandom -def init_seedkeeper(parentObject): - parentObject.loading_screen = LoadingScreenThread(text="Searching for SeedKeeper") +def init_satochip(parentObject): + parentObject.loading_screen = LoadingScreenThread(text="Searching for Card") parentObject.loading_screen.start() # Spam connecting for 10 seconds to give the user time to insert the card @@ -18,7 +18,7 @@ def init_seedkeeper(parentObject): time_end = time.time() + 10 while time.time() < time_end: try: - Satochip_Connector = CardConnector(card_filter="seedkeeper") + Satochip_Connector = CardConnector() time.sleep(0.1) # give some time to initialize reader... status = Satochip_Connector.card_get_status() break @@ -33,7 +33,7 @@ def init_seedkeeper(parentObject): WarningScreen, title="Unable to Connect", status_headline=None, - text=f"Unable to find SeedKeeper, missing card or missing reader", + text=f"Unable to find SmartCard, missing card or missing reader", show_back_button=True, ) return None @@ -46,10 +46,8 @@ def init_seedkeeper(parentObject): print("Initiating Secure Channel") Satochip_Connector.card_initiate_secure_channel() - - if status[3]['setup_done']: - ret = seed_screens.SeedAddPassphraseScreen(title="Seedkeeper PIN").display() + ret = seed_screens.SeedAddPassphraseScreen(title="Card PIN").display() if ret == RET_CODE__BACK_BUTTON: return None @@ -59,7 +57,20 @@ def init_seedkeeper(parentObject): try: print("Verifying PIN") (response, sw1, sw2) = Satochip_Connector.card_verify_PIN() - print(response) + if sw1 == 0x90 and sw2 == 0x00: + pass #Pin is correct + else: + # Pin is not incorrect, but isn't valid either (doesn't increment the failed pin counter) + print("Failure: Invalid PIN") + parentObject.run_screen( + WarningScreen, + title="Invalid PIN", + status_headline=None, + text=f"Invalid PIN entered, select another and try again.", + show_back_button=True, + ) + return None + except RuntimeError as e: #Incorrect PIN print(e) # status = Satochip_Connector.card_get_status() @@ -77,22 +88,22 @@ def init_seedkeeper(parentObject): WarningScreen, title="Incorrect PIN", status_headline=None, - text=f"Unable to unlock SeedKeeper, Incorrect PIN " + str(pin_tries_left) + " tries remain before card locks...", + text=f"Unable to unlock Card, Incorrect PIN " + str(pin_tries_left) + " tries remain before card locks...", show_back_button=True, ) return None else: - print("Seedkeeper Needs Initial Setup") + print("Card Needs Initial Setup") parentObject.run_screen( WarningScreen, - title="Seedkeeper Uninitialised", + title="Card Uninitialised", status_headline=None, text=f"Set a device PIN to complete Card Setup", show_back_button=True, ) - ret = seed_screens.SeedAddPassphraseScreen(title="Seedkeeper PIN").display() + ret = seed_screens.SeedAddPassphraseScreen(title="Card PIN").display() if ret == RET_CODE__BACK_BUTTON: return None @@ -141,6 +152,7 @@ def init_seedkeeper(parentObject): def run_globalplatform(parentObject, command, loadingText = "Loading", successtext = "Success"): from subprocess import run + from seedsigner.models.settings import Settings, SettingsConstants, SettingsDefinition parentObject.loading_screen = LoadingScreenThread(text=loadingText) parentObject.loading_screen.start() @@ -148,6 +160,14 @@ def run_globalplatform(parentObject, command, loadingText = "Loading", successte commandString = "java -jar /home/pi/Satochip-DIY/gp.jar " + command data = run(commandString, capture_output=True, shell=True, text=True) + + # This process often kills IFD-NFC, so restart it if required + scinterface = parentObject.settings.get_value(SettingsConstants.SETTING__SMARTCARD_INTERFACES) + if "pn532" in scinterface: + os.system("ifdnfc-activate no") + time.sleep(1) + os.system("ifdnfc-activate yes") + parentObject.loading_screen.stop() print("StdOut:", data.stdout) diff --git a/src/seedsigner/models/encode_qr.py b/src/seedsigner/models/encode_qr.py index 6efa715db..4c70cd1ad 100644 --- a/src/seedsigner/models/encode_qr.py +++ b/src/seedsigner/models/encode_qr.py @@ -40,6 +40,7 @@ class EncodeQR: wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH bitcoin_address: str = None signed_message: str = None + hex_string: str = None def __post_init__(self): self.qr = QR() @@ -106,6 +107,9 @@ def __post_init__(self): elif self.qr_type == QRType.SIGN_MESSAGE: self.encoder = SignedMessageEncoder(signed_message=self.signed_message) + elif self.qr_type == QRType.HEX_STRING: + self.encoder = HexKeyEncoder(hex_string=self.hex_string) + else: raise Exception('QR Type not supported') @@ -536,3 +540,12 @@ def seq_len(self): def next_part(self) -> str: return self.ur2_encode.next_part().upper() + +class HexKeyEncoder(BaseStaticQrEncoder): + def __init__(self, hex_string: str): + super().__init__() + self.hex_string = hex_string + + + def next_part(self): + return self.hex_string \ No newline at end of file diff --git a/src/seedsigner/models/qr_type.py b/src/seedsigner/models/qr_type.py index 6ec1f5ecc..f9d5228bd 100644 --- a/src/seedsigner/models/qr_type.py +++ b/src/seedsigner/models/qr_type.py @@ -31,4 +31,6 @@ class QRType: ACCOUNT__UR = "account__ur" BYTES__UR = "bytes__ur" + HEX_STRING = "hex_string" + INVALID = "invalid" \ No newline at end of file diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 37a3c35a9..4a8b831bb 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -206,7 +206,7 @@ def run(self): class SeedKeeperSelectView(View): def run(self): try: - Satochip_Connector = seedkeeper_utils.init_seedkeeper(self) + Satochip_Connector = seedkeeper_utils.init_satochip(self) if not Satochip_Connector: return Destination(BackStackView) @@ -2191,7 +2191,7 @@ def __init__(self, seed_num: int, bip85_data: dict = None): def run(self): try: - Satochip_Connector = seedkeeper_utils.init_seedkeeper(self) + Satochip_Connector = seedkeeper_utils.init_satochip(self) if not Satochip_Connector: return Destination(BackStackView) diff --git a/src/seedsigner/views/settings_views.py b/src/seedsigner/views/settings_views.py index e2a742ca7..0a6499068 100644 --- a/src/seedsigner/views/settings_views.py +++ b/src/seedsigner/views/settings_views.py @@ -13,6 +13,7 @@ class SettingsMenuView(View): IO_TEST = "I/O test" + NFC_TEST = "Test NFC Scan" DONATE = "Donate" def __init__(self, visibility: str = SettingsConstants.VISIBILITY__GENERAL, selected_attr: str = None, initial_scroll: int = 0): @@ -45,6 +46,7 @@ def run(self): next_destination = Destination(SettingsMenuView, view_args={"visibility": SettingsConstants.VISIBILITY__ADVANCED}) button_data.append(self.IO_TEST) + button_data.append(self.NFC_TEST) button_data.append(self.DONATE) elif self.visibility == SettingsConstants.VISIBILITY__ADVANCED: @@ -84,6 +86,9 @@ def run(self): elif len(button_data) > selected_menu_num and button_data[selected_menu_num] == self.IO_TEST: return Destination(IOTestView) + + elif button_data[selected_menu_num] == self.NFC_TEST: + return Destination(NFCTestView) elif len(button_data) > selected_menu_num and button_data[selected_menu_num] == self.DONATE: return Destination(DonateView) @@ -227,6 +232,117 @@ def run(self): return Destination(SettingsMenuView) +class NFCTestView(View): + def run(self): + + from seedsigner.gui.screens.screen import LoadingScreenThread + import os + import time + from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen, DireWarningScreen, seed_screens, LargeIconStatusScreen) + + self.loading_screen = LoadingScreenThread(text="Scanning for NFC Tag") + self.loading_screen.start() + + os.system("ifdnfc-activate no") # Need to disable IFD-NFC to be able to scan using libnfc-bindings... + + time.sleep(0.2) #Just give the loading screen a chance to load before moving on... + + import nfc + import binascii + + context = nfc.init() + nfcdevice = nfc.open(context) + if nfcdevice is None: + self.loading_screen.stop() + print('ERROR: Unable to open NFC device.') + self.run_screen( + WarningScreen, + title="NFC Failure", + status_headline=None, + text=f"ERROR: Unable to open NFC device. \n(May not be connected)", + show_back_button=True, + ) + return Destination(BackStackView) + + if nfc.initiator_init(nfcdevice) < 0: + self.loading_screen.stop() + print('ERROR: Unable to init NFC device.') + self.run_screen( + WarningScreen, + title="NFC Failure", + status_headline=None, + text=f"ERROR: Unable to init NFC device.", + show_back_button=True, + ) + return Destination(BackStackView) + + print('NFC reader: %s opened' % nfc.device_get_name(nfcdevice)) + + nfcmodulation = nfc.modulation() + nfcmodulation.nmt = nfc.NMT_ISO14443A + nfcmodulation.nbr = nfc.NBR_847 #Test at the highest baud rate for the best simulation of Smartcard operation + + nt = nfc.target() + + # Scan for 15 seconds + ret = nfc.initiator_poll_target(nfcdevice, nfcmodulation, 1, 100, 1, nt) + + self.loading_screen.stop() + + if ret and nt.nti.nai.szUidLen: + + print('The following (NFC) ISO14443A tag was found:') + print(' ATQA (SENS_RES): ', end='') + nfc.print_hex(nt.nti.nai.abtAtqa, 2) + id = 1 + if nt.nti.nai.abtUid[0] == 8: + id = 3 + print(' UID (NFCID%d): ' % id , end='') + nfc.print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen) + foundtext="UID:\n" + binascii.hexlify(nt.nti.nai.abtUid).decode()[:14] + + print(' SAK (SEL_RES): ', end='') + print(nt.nti.nai.btSak) + if nt.nti.nai.szAtsLen: + print(' ATS (ATR): ', end='') + nfc.print_hex(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen) + foundtext = foundtext + "\nATR:\n" + binascii.hexlify(nt.nti.nai.abtAts).decode()[:28] + + self.run_screen( + LargeIconStatusScreen, + title="Found NFC Tag", + status_headline=None, + text=foundtext, + show_back_button=False, + ) + elif ret: + print('Warning: IFD-NFC Conflict.') + self.run_screen( + WarningScreen, + title="NFC Conflict", + status_headline=None, + text=f"Can't scan when IFD-NFC Active", + show_back_button=True, + ) + else: + print('Warning: No NFC Tag Detected.') + self.run_screen( + WarningScreen, + title="Warning", + status_headline=None, + text=f"Warning: No NFC Tag Detected.", + show_back_button=True, + ) + + nfc.close(nfcdevice) + nfc.exit(context) + + scinterface = self.settings.get_value(SettingsConstants.SETTING__SMARTCARD_INTERFACES) + + if "pn532" in scinterface: + os.system("ifdnfc-activate yes") # Need to re-enable IFD-NFC if required... + + return Destination(MainMenuView) class DonateView(View): diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index a81aae404..36f44739b 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -18,7 +18,7 @@ from seedsigner.models.qr_type import QRType from seedsigner.models.seed import Seed from seedsigner.models.settings_definition import SettingsConstants -from seedsigner.views.seed_views import SeedDiscardView, SeedFinalizeView, SeedMnemonicEntryView, SeedOptionsView, SeedWordsWarningView, SeedExportXpubScriptTypeView +from seedsigner.views.seed_views import SeedDiscardView, SeedFinalizeView, SeedMnemonicEntryView, SeedOptionsView, SeedWordsWarningView, SeedExportXpubScriptTypeView, LoadSeedView from .view import View, Destination, BackStackView, MainMenuView @@ -471,7 +471,7 @@ def run(self): # Most of the options require us to go through a side flow(s) before we can # continue to the address explorer. Set the Controller-level flow so that it # knows to re-route us once the side flow is complete. - self.controller.resume_main_flow = Controller.FLOW__ADDRESS_EXPLORER + # self.controller.resume_main_flow = Controller.FLOW__ADDRESS_EXPLORER if len(seeds) > 0 and selected_menu_num < len(seeds): # User selected one of the n seeds @@ -711,18 +711,16 @@ def run(self): return Destination(ToolsAddressExplorerAddressListView, view_args=dict(is_change=self.is_change, start_index=self.start_index, selected_button_index=self.index - self.start_index, initial_scroll=self.parent_initial_scroll), skip_current_view=True) """**************************************************************************** - Seedkeeper Views + Smartcard Views ****************************************************************************""" class ToolsSmartcardMenuView(View): CHANGE_PIN = ("Change PIN") - TEST_NFC = ("Test NFC Scan") - IFDNFC_ACTIVATE = ("Start PN532(PN532)") - OPENCT_ACTIVATE = ("Start OpenCT(SIM)") - INSTALL_APPLET = ("Install Applet") - UNINSTALL_APPLET = ("Uninstall Applet") + CHANGE_LABEL = ("Change Label") + SATOCHIP = ("Satochip Functions") + Satochip_DIY = ("DIY Tools") def run(self): - button_data = [self.CHANGE_PIN, self.TEST_NFC, self.INSTALL_APPLET, self.UNINSTALL_APPLET] + button_data = [self.CHANGE_PIN, self.CHANGE_LABEL, self.SATOCHIP, self.Satochip_DIY] selected_menu_num = self.run_screen( ButtonListScreen, @@ -735,27 +733,22 @@ def run(self): return Destination(BackStackView) elif button_data[selected_menu_num] == self.CHANGE_PIN: - return Destination(ToolsSeedkeeperChangePinView) - - elif button_data[selected_menu_num] == self.TEST_NFC: - return Destination(ToolsSeedkeeperTestNFCView) + return Destination(ToolsSatochipChangePinView) - elif button_data[selected_menu_num] == self.IFDNFC_ACTIVATE: - return Destination(ToolsSeedkeeperStartIfdNFCView) + elif button_data[selected_menu_num] == self.CHANGE_LABEL: + return Destination(ToolsSatochipChangeLabelView) - elif button_data[selected_menu_num] == self.OPENCT_ACTIVATE: - return Destination(ToolsSeedkeeperStartOpenCTView) + elif button_data[selected_menu_num] == self.SATOCHIP: + return Destination(ToolsSatochipView) - elif button_data[selected_menu_num] == self.INSTALL_APPLET: - return Destination(ToolsSeedkeeperInstallAppletView) + elif button_data[selected_menu_num] == self.Satochip_DIY: + return Destination(ToolsSatochipDIYView) - elif button_data[selected_menu_num] == self.UNINSTALL_APPLET: - return Destination(ToolsSeedkeeperUninstallAppletView) -class ToolsSeedkeeperChangePinView(View): +class ToolsSatochipChangePinView(View): def run(self): - Satochip_Connector = seedkeeper_utils.init_seedkeeper(self) + Satochip_Connector = seedkeeper_utils.init_satochip(self) if not Satochip_Connector: return Destination(BackStackView) @@ -763,7 +756,7 @@ def run(self): NewPin = seed_screens.SeedAddPassphraseScreen(title="New PIN").display() if NewPin == RET_CODE__BACK_BUTTON: - return Destination(ToolsSeedkeeperMenuView) + return Destination(ToolsSmartcardMenuView) new_pin = list(NewPin.encode('utf8')) response, sw1, sw2 = Satochip_Connector.card_change_PIN(0, Satochip_Connector.pin, new_pin) @@ -788,135 +781,217 @@ def run(self): return Destination(MainMenuView) -class ToolsSeedkeeperTestNFCView(View): +class ToolsSatochipChangeLabelView(View): def run(self): - from seedsigner.gui.screens.screen import LoadingScreenThread - self.loading_screen = LoadingScreenThread(text="Scanning for NFC Tag") - self.loading_screen.start() + Satochip_Connector = seedkeeper_utils.init_satochip(self) - os.system("ifdnfc-activate no") # Need to disable IFD-NFC to be able to scan using libnfc-bindings... + if not Satochip_Connector: + return Destination(BackStackView) - time.sleep(0.2) #Just give the loading screen a chance to load before moving on... + NewLabel = seed_screens.SeedAddPassphraseScreen(title="New Label").display() - import nfc - import binascii + if NewLabel == RET_CODE__BACK_BUTTON: + return Destination(ToolsSmartcardMenuView) - context = nfc.init() - nfcdevice = nfc.open(context) - if nfcdevice is None: - self.loading_screen.stop() - print('ERROR: Unable to open NFC device.') - self.run_screen( - WarningScreen, - title="NFC Failure", - status_headline=None, - text=f"ERROR: Unable to open NFC device. \n(May not be connected)", - show_back_button=True, - ) + """Sets a plain text label for the card (Optional)""" + try: + (response, sw1, sw2) = Satochip_Connector.card_set_label(NewLabel) + if sw1 != 0x90 or sw2 != 0x00: + print("ERROR: Set Label Failed") + self.run_screen( + WarningScreen, + title="Failed", + status_headline=None, + text=f"Set Label Failed...", + show_back_button=True, + ) + else: + print("Device Label Updated") + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"Label Updated", + show_back_button=False, + ) + except Exception as e: + print(e) + + return Destination(MainMenuView) + +class ToolsSatochipView(View): + IMPORT_SEED = ("Import Seed") + ENABLE_2FA = ("Enable 2FA") + + def run(self): + button_data = [self.IMPORT_SEED, self.ENABLE_2FA] + + selected_menu_num = self.run_screen( + ButtonListScreen, + title="Satochip", + is_button_text_centered=False, + button_data=button_data + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: return Destination(BackStackView) - - if nfc.initiator_init(nfcdevice) < 0: - self.loading_screen.stop() - print('ERROR: Unable to init NFC device.') - self.run_screen( - WarningScreen, - title="NFC Failure", - status_headline=None, - text=f"ERROR: Unable to init NFC device.", - show_back_button=True, - ) + + elif button_data[selected_menu_num] == self.IMPORT_SEED: + return Destination(ToolsSatochipImportSeedView) + + elif button_data[selected_menu_num] == self.ENABLE_2FA: + return Destination(ToolsSatochipEnable2FAView) + +class ToolsSatochipImportSeedView(View): + SCAN_SEED = ("Scan a seed", SeedSignerIconConstants.QRCODE) + SCAN_DESCRIPTOR = ("Scan wallet descriptor", SeedSignerIconConstants.QRCODE) + TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD) + TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD) + + def run(self): + + Satochip_Connector = seedkeeper_utils.init_satochip(self) + + if not Satochip_Connector: return Destination(BackStackView) - print('NFC reader: %s opened' % nfc.device_get_name(nfcdevice)) + seeds = self.controller.storage.seeds + button_data = [] + for seed in seeds: + button_str = seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK)) + button_data.append((button_str, SeedSignerIconConstants.FINGERPRINT)) + button_data = button_data + [self.SCAN_SEED, self.SCAN_DESCRIPTOR, self.TYPE_12WORD, self.TYPE_24WORD] + + selected_menu_num = self.run_screen( + ButtonListScreen, + title="Seed to Import", + button_data=button_data, + is_button_text_centered=False, + is_bottom_list=True, + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + # Most of the options require us to go through a side flow(s) before we can + # continue to the address explorer. Set the Controller-level flow so that it + # knows to re-route us once the side flow is complete. + self.controller.resume_main_flow = Controller.FLOW__SATOCHIP_IMPORT_SEED - nfcmodulation = nfc.modulation() - nfcmodulation.nmt = nfc.NMT_ISO14443A - nfcmodulation.nbr = nfc.NBR_847 #Test at the highest baud rate for the best simulation of Smartcard operation + print(seeds[selected_menu_num]) - nt = nfc.target() + if len(seeds) > 0 and selected_menu_num < len(seeds): + # User selected one of the n seeds + try: + Satochip_Connector.card_bip32_import_seed(seeds[selected_menu_num].seed_bytes) + print("Seed Successfully Imported") + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"Seed Imported", + show_back_button=False, + ) + except Exception as e: + print(e) + self.run_screen( + WarningScreen, + title="Failed", + status_headline=None, + text=f"Seed Import Failed", + show_back_button=False, + ) - # Scan for 15 seconds - ret = nfc.initiator_poll_target(nfcdevice, nfcmodulation, 1, 100, 1, nt) + elif button_data[selected_menu_num] == self.SCAN_SEED: + from seedsigner.views.scan_views import ScanSeedQRView + return Destination(ScanSeedQRView) - self.loading_screen.stop() + elif button_data[selected_menu_num] == self.SCAN_DESCRIPTOR: + from seedsigner.views.scan_views import ScanWalletDescriptorView + return Destination(ScanWalletDescriptorView) - if ret and nt.nti.nai.szUidLen: + elif button_data[selected_menu_num] in [self.TYPE_12WORD, self.TYPE_24WORD]: + from seedsigner.views.seed_views import SeedMnemonicEntryView + if button_data[selected_menu_num] == self.TYPE_12WORD: + self.controller.storage.init_pending_mnemonic(num_words=12) + else: + self.controller.storage.init_pending_mnemonic(num_words=24) + return Destination(SeedMnemonicEntryView) + + return Destination(MainMenuView) - print('The following (NFC) ISO14443A tag was found:') - print(' ATQA (SENS_RES): ', end='') - nfc.print_hex(nt.nti.nai.abtAtqa, 2) - id = 1 - if nt.nti.nai.abtUid[0] == 8: - id = 3 - print(' UID (NFCID%d): ' % id , end='') - nfc.print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen) - foundtext="UID:\n" + binascii.hexlify(nt.nti.nai.abtUid).decode()[:14] +class ToolsSatochipEnable2FAView(View): + def run(self): + from os import urandom + import binascii + key = urandom(20) + print("2FA Key:", binascii.hexlify(key)) - print(' SAK (SEL_RES): ', end='') - print(nt.nti.nai.btSak) - if nt.nti.nai.szAtsLen: - print(' ATS (ATR): ', end='') - nfc.print_hex(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen) - foundtext = foundtext + "\nATR:\n" + binascii.hexlify(nt.nti.nai.abtAts).decode()[:28] + Satochip_Connector = seedkeeper_utils.init_satochip(self) + if not Satochip_Connector: + return Destination(BackStackView) + + try: self.run_screen( - LargeIconStatusScreen, - title="Found NFC Tag", + WarningScreen, + title="Warning", status_headline=None, - text=foundtext, + text=f"Scan the following QR code with the Satochip 2FA app before proceeding (You will not see this code again...)", show_back_button=False, ) - elif ret: - print('Warning: IFD-NFC Conflict.') + from seedsigner.gui.screens.screen import QRDisplayScreen + qr_encoder = EncodeQR(qr_type=QRType.HEX_STRING, hex_string=binascii.hexlify(key).decode()) self.run_screen( - WarningScreen, - title="NFC Conflict", + QRDisplayScreen, + qr_encoder=qr_encoder, + ) + Satochip_Connector.card_set_2FA_key(key, 0) + print("Success: 2FA Key Imported and Enabled") + self.run_screen( + LargeIconStatusScreen, + title="Success", status_headline=None, - text=f"Can't scan when IFD-NFC Active", - show_back_button=True, + text=f"Seed Imported", + show_back_button=False, ) - else: - print('Warning: No NFC Tag Detected.') + except Exception as e: + print(e) self.run_screen( WarningScreen, - title="Warning", + title="Failed", status_headline=None, - text=f"Warning: No NFC Tag Detected.", - show_back_button=True, + text=f"Enable 2FA Failed", + show_back_button=False, ) - nfc.close(nfcdevice) - nfc.exit(context) - - scinterface = self.settings.get_value(SettingsConstants.SETTING__SMARTCARD_INTERFACES) - - if "pn532" in scinterface: - os.system("ifdnfc-activate yes") # Need to re-enable IFD-NFC if required... - return Destination(MainMenuView) -class ToolsSeedkeeperStartIfdNFCView(View): - def run(self): - import os +class ToolsSatochipDIYView(View): + INSTALL_APPLET = ("Install Applet") + UNINSTALL_APPLET = ("Uninstall Applet") - os.system("ifdnfc-activate yes") - time.sleep(1) + def run(self): + button_data = [self.INSTALL_APPLET, self.UNINSTALL_APPLET] - return Destination(MainMenuView) + selected_menu_num = self.run_screen( + ButtonListScreen, + title="Tools", + is_button_text_centered=False, + button_data=button_data + ) -class ToolsSeedkeeperStartOpenCTView(View): - def run(self): - import os + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) - os.system("sudo openct-control init") - time.sleep(1) - os.system("sudo service pcscd restart") - time.sleep(1) + elif button_data[selected_menu_num] == self.INSTALL_APPLET: + return Destination(ToolsDIYInstallAppletView) - return Destination(MainMenuView) + elif button_data[selected_menu_num] == self.UNINSTALL_APPLET: + return Destination(ToolsDIYUninstallAppletView) -class ToolsSeedkeeperInstallAppletView(View): +class ToolsDIYInstallAppletView(View): def run(self): from subprocess import run import os @@ -939,9 +1014,16 @@ def run(self): installed_applets = seedkeeper_utils.run_globalplatform(self,"--install /home/pi/Satochip-DIY/build/" + applet_file, "Installing Applet", "Applet Installed") + # This process often kills IFD-NFC, so restart it if required + scinterface = self.settings.get_value(SettingsConstants.SETTING__SMARTCARD_INTERFACES) + if "pn532" in scinterface: + os.system("ifdnfc-activate no") + time.sleep(1) + os.system("ifdnfc-activate yes") + return Destination(MainMenuView) -class ToolsSeedkeeperUninstallAppletView(View): +class ToolsDIYUninstallAppletView(View): def run(self): from subprocess import run import os @@ -980,4 +1062,11 @@ def run(self): seedkeeper_utils.run_globalplatform(self,"--delete " + applet_aid + " -force", "Uninstalling Applet", "Applet Uninstalled") + # This process often kills IFD-NFC, so restart it if required + scinterface = self.settings.get_value(SettingsConstants.SETTING__SMARTCARD_INTERFACES) + if "pn532" in scinterface: + os.system("ifdnfc-activate no") + time.sleep(1) + os.system("ifdnfc-activate yes") + return Destination(MainMenuView) From de5199f5b815a2a23d57098b0b25c47d36a7d4b2 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Mon, 18 Dec 2023 14:44:16 -0500 Subject: [PATCH 24/55] *Add loading screen to enable/disable USB. *Fixes *Rename generic qr type to string --- src/seedsigner/models/encode_qr.py | 14 +++++++------- src/seedsigner/models/qr_type.py | 2 +- src/seedsigner/models/settings.py | 21 +++++++++++++++++++++ src/seedsigner/views/tools_views.py | 8 ++++---- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/seedsigner/models/encode_qr.py b/src/seedsigner/models/encode_qr.py index 4c70cd1ad..939fe5e25 100644 --- a/src/seedsigner/models/encode_qr.py +++ b/src/seedsigner/models/encode_qr.py @@ -40,7 +40,7 @@ class EncodeQR: wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH bitcoin_address: str = None signed_message: str = None - hex_string: str = None + generic_string: str = None def __post_init__(self): self.qr = QR() @@ -107,8 +107,8 @@ def __post_init__(self): elif self.qr_type == QRType.SIGN_MESSAGE: self.encoder = SignedMessageEncoder(signed_message=self.signed_message) - elif self.qr_type == QRType.HEX_STRING: - self.encoder = HexKeyEncoder(hex_string=self.hex_string) + elif self.qr_type == QRType.GENERIC_STRING: + self.encoder = GenericStringEncoder(generic_string=self.generic_string) else: raise Exception('QR Type not supported') @@ -541,11 +541,11 @@ def seq_len(self): def next_part(self) -> str: return self.ur2_encode.next_part().upper() -class HexKeyEncoder(BaseStaticQrEncoder): - def __init__(self, hex_string: str): +class GenericStringEncoder(BaseStaticQrEncoder): + def __init__(self, generic_string: str): super().__init__() - self.hex_string = hex_string + self.generic_string = generic_string def next_part(self): - return self.hex_string \ No newline at end of file + return self.generic_string \ No newline at end of file diff --git a/src/seedsigner/models/qr_type.py b/src/seedsigner/models/qr_type.py index f9d5228bd..f04a868bb 100644 --- a/src/seedsigner/models/qr_type.py +++ b/src/seedsigner/models/qr_type.py @@ -31,6 +31,6 @@ class QRType: ACCOUNT__UR = "account__ur" BYTES__UR = "bytes__ur" - HEX_STRING = "hex_string" + GENERIC_STRING = "generic_string" INVALID = "invalid" \ No newline at end of file diff --git a/src/seedsigner/models/settings.py b/src/seedsigner/models/settings.py index df02d38fc..1748c9e4f 100644 --- a/src/seedsigner/models/settings.py +++ b/src/seedsigner/models/settings.py @@ -175,16 +175,23 @@ def set_value(self, attr_name: str, value: any): # Special handling for enabling Smartcard readers if attr_name == SettingsConstants.SETTING__SMARTCARD_INTERFACES: import time + from seedsigner.gui.screens.screen import LoadingScreenThread + print("Smartcard Interface Changed") # Basically just check through a a bunch of possible USB hubs and ports and enable/disable them all (Should cover all RPi models, RPi4 has lots of USB ports...) if "usb" not in value and "usb" in self._data[attr_name]: + self.loading_screen = LoadingScreenThread(text="Disabling USB Ports") + self.loading_screen.start() print("Disabling USB") for hub in range(3): for port in range(3): os.system("sudo hub-ctrl -H " + str(hub) + " -P " + str(port) + " -p 0") + self.loading_screen.stop() if "usb" in value and "usb" not in self._data[attr_name]: + self.loading_screen = LoadingScreenThread(text="Enabling USB Ports") + self.loading_screen.start() print("Enabling USB") for hub in range(3): for port in range(5): @@ -192,27 +199,41 @@ def set_value(self, attr_name: str, value: any): time.sleep(1) os.system("sudo service pcscd restart") # PCSC doesn't always work properly after USB ports have been re-enabled + self.loading_screen.stop() # Execution order matters here if swithing from Phoenix to PN352, basically we want to disable phoenix first and then enable PN532 if "phoenix" in value and "phoenix" not in self._data[attr_name]: + self.loading_screen = LoadingScreenThread(text="Starting OpenCT") + self.loading_screen.start() print("Phoenix Enabled") + os.system("sudo openct-control init") # OpenCT needs a bit of time to get going before restarting PCSCD (At least two seconds) to work reliabily time.sleep(3) os.system("sudo service pcscd restart") + self.loading_screen.stop() if "phoenix" not in value and "phoenix" in self._data[attr_name]: + self.loading_screen = LoadingScreenThread(text="Stopping OpenCT") + self.loading_screen.start() print("Phoenix Disabled") os.system("sudo openct-control shutdown") time.sleep(3) os.system("sudo service pcscd restart") + self.loading_screen.stop() if "pn532" in value and "pn532" not in self._data[attr_name]: + self.loading_screen = LoadingScreenThread(text="Enabling PN532") + self.loading_screen.start() print("PN532 Enabled") os.system("ifdnfc-activate yes") + self.loading_screen.stop() if "pn532" not in value and "pn532" in self._data[attr_name]: + self.loading_screen = LoadingScreenThread(text="Disabling PN532") + self.loading_screen.start() print("PN532 Disabled") os.system("ifdnfc-activate no") + self.loading_screen.stop() self._data[attr_name] = value self.save() diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 36f44739b..84bb01f4b 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -471,7 +471,7 @@ def run(self): # Most of the options require us to go through a side flow(s) before we can # continue to the address explorer. Set the Controller-level flow so that it # knows to re-route us once the side flow is complete. - # self.controller.resume_main_flow = Controller.FLOW__ADDRESS_EXPLORER + self.controller.resume_main_flow = Controller.FLOW__ADDRESS_EXPLORER if len(seeds) > 0 and selected_menu_num < len(seeds): # User selected one of the n seeds @@ -877,7 +877,7 @@ def run(self): # Most of the options require us to go through a side flow(s) before we can # continue to the address explorer. Set the Controller-level flow so that it # knows to re-route us once the side flow is complete. - self.controller.resume_main_flow = Controller.FLOW__SATOCHIP_IMPORT_SEED + # self.controller.resume_main_flow = Controller.FLOW__SATOCHIP_IMPORT_SEED print(seeds[selected_menu_num]) @@ -942,7 +942,7 @@ def run(self): show_back_button=False, ) from seedsigner.gui.screens.screen import QRDisplayScreen - qr_encoder = EncodeQR(qr_type=QRType.HEX_STRING, hex_string=binascii.hexlify(key).decode()) + qr_encoder = EncodeQR(qr_type=QRType.GENERIC_STRING, generic_string=binascii.hexlify(key).decode()) self.run_screen( QRDisplayScreen, qr_encoder=qr_encoder, @@ -953,7 +953,7 @@ def run(self): LargeIconStatusScreen, title="Success", status_headline=None, - text=f"Seed Imported", + text=f"2FA Enabled", show_back_button=False, ) except Exception as e: From e148d60142b2de4165858f376c48450975f9cc02 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Fri, 22 Dec 2023 22:41:48 -0500 Subject: [PATCH 25/55] *Improve reliability of processes when connecting over PN532 NFC *Improve error messages --- src/seedsigner/helpers/seedkeeper_utils.py | 57 ++++++++++++++-------- src/seedsigner/views/seed_views.py | 20 +++----- src/seedsigner/views/settings_views.py | 2 +- 3 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/seedsigner/helpers/seedkeeper_utils.py b/src/seedsigner/helpers/seedkeeper_utils.py index ac293699b..c540aea7d 100644 --- a/src/seedsigner/helpers/seedkeeper_utils.py +++ b/src/seedsigner/helpers/seedkeeper_utils.py @@ -10,25 +10,40 @@ from os import urandom def init_satochip(parentObject): + from seedsigner.models.settings import Settings, SettingsConstants, SettingsDefinition + parentObject.loading_screen = LoadingScreenThread(text="Searching for Card") parentObject.loading_screen.start() - # Spam connecting for 10 seconds to give the user time to insert the card + # Spam connecting for 5 seconds to give the user time to insert the card status = None - time_end = time.time() + 10 + time_end = time.time() + 5 while time.time() < time_end: try: Satochip_Connector = CardConnector() - time.sleep(0.1) # give some time to initialize reader... + time.sleep(1) # give some time to initialize reader... status = Satochip_Connector.card_get_status() - break + + print("Found Card:") + print(status[3]) + + if (Satochip_Connector.needs_secure_channel): + print("Initiating Secure Channel") + Satochip_Connector.card_initiate_secure_channel() + print("Secure Channel Initialised") + + if len(status[3]) > 0: #Sometimes it's possible to end up with an invalid of zero length here... + break except Exception as e: print(e) time.sleep(0.1) # Sleep for 100ms + + status = None # Reset this every loop... parentObject.loading_screen.stop() if not status: + parentObject.run_screen( WarningScreen, title="Unable to Connect", @@ -39,39 +54,32 @@ def init_satochip(parentObject): return None status = Satochip_Connector.card_get_status() - print("Found Card:") - print(status[3]) - - if (Satochip_Connector.needs_secure_channel): - print("Initiating Secure Channel") - Satochip_Connector.card_initiate_secure_channel() if status[3]['setup_done']: - ret = seed_screens.SeedAddPassphraseScreen(title="Card PIN").display() - - if ret == RET_CODE__BACK_BUTTON: + card_pin = seed_screens.SeedAddPassphraseScreen(title="Card PIN").display() + if card_pin == RET_CODE__BACK_BUTTON: return None - Satochip_Connector.set_pin(0, list(bytes(ret, "utf-8"))) + Satochip_Connector.set_pin(0, list(bytes(card_pin, "utf-8"))) try: print("Verifying PIN") (response, sw1, sw2) = Satochip_Connector.card_verify_PIN() if sw1 == 0x90 and sw2 == 0x00: + print("Pin Correct") pass #Pin is correct else: - # Pin is not incorrect, but isn't valid either (doesn't increment the failed pin counter) - print("Failure: Invalid PIN") parentObject.run_screen( WarningScreen, - title="Invalid PIN", + title="Failure", status_headline=None, - text=f"Invalid PIN entered, select another and try again.", + text=f"Failed, Code:" + str(sw1) + " " + str(sw2), show_back_button=True, ) return None - + except RuntimeError as e: #Incorrect PIN + print("RunTimeError") print(e) # status = Satochip_Connector.card_get_status() pin_tries_left = status[3]['PIN0_remaining_tries'] @@ -92,6 +100,16 @@ def init_satochip(parentObject): show_back_button=True, ) return None + + except Exception as e: + parentObject.run_screen( + WarningScreen, + title="Failed", + status_headline=None, + text=str(e), + show_back_button=True, + ) + return None else: print("Card Needs Initial Setup") parentObject.run_screen( @@ -102,7 +120,6 @@ def init_satochip(parentObject): show_back_button=True, ) - ret = seed_screens.SeedAddPassphraseScreen(title="Card PIN").display() if ret == RET_CODE__BACK_BUTTON: diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 4a8b831bb..c133ae29e 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -207,13 +207,12 @@ class SeedKeeperSelectView(View): def run(self): try: Satochip_Connector = seedkeeper_utils.init_satochip(self) - + if not Satochip_Connector: return Destination(BackStackView) headers = Satochip_Connector.seedkeeper_list_secret_headers() - print(headers) headers_parsed = [] button_data = [] for header in headers: @@ -240,7 +239,7 @@ def run(self): title="No Secrets to Load", status_headline=None, text=f"No BIP39 Secrets to Load from Seedkeeper", - show_back_button=True, + show_back_button=False, ) return Destination(BackStackView) @@ -248,15 +247,15 @@ def run(self): ButtonListScreen, title="Select Secret", is_button_text_centered=False, - button_data=button_data + button_data=button_data, + show_back_button=True, ) - print(type(headers_parsed[selected_menu_num][0])) + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) secret_dict = Satochip_Connector.seedkeeper_export_secret(headers_parsed[selected_menu_num][0], None) - print(secret_dict) - secret_dict['secret'] = unhexlify(secret_dict['secret'])[1:].decode().rstrip("\x00") bip39_secret = secret_dict['secret'] @@ -266,21 +265,18 @@ def run(self): secret_mnemonic = bip39_secret[:secret_size] secret_passphrase = bip39_secret[secret_size+1:] - print("BIP39-Mnemonic:", secret_mnemonic, "BIP39-Passphrase:", secret_passphrase) - except Exception as e: print(e) self.run_screen( WarningScreen, - title="Unknown Error...", + title="Error", status_headline=None, - text=f"Load from Seedkeeper Failed", + text=str(e), show_back_button=True, ) return Destination(BackStackView) mnemonic = secret_mnemonic.split(" ") - print(len(mnemonic)) self.controller.storage.init_pending_mnemonic(num_words=len(mnemonic)) for i, word in enumerate(mnemonic): self.controller.storage.update_pending_mnemonic(word, i) diff --git a/src/seedsigner/views/settings_views.py b/src/seedsigner/views/settings_views.py index 0a6499068..2ec464846 100644 --- a/src/seedsigner/views/settings_views.py +++ b/src/seedsigner/views/settings_views.py @@ -2,7 +2,7 @@ from seedsigner.gui.components import SeedSignerIconConstants from seedsigner.hardware.microsd import MicroSD -from .view import View, Destination, MainMenuView +from .view import View, Destination, MainMenuView, BackStackView from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, settings_screens) from seedsigner.models.settings import Settings, SettingsConstants, SettingsDefinition From dab4c773a7b6d348b58911adc8e6f1da85cc469c Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Fri, 22 Dec 2023 22:54:42 -0500 Subject: [PATCH 26/55] Update controller.py --- src/seedsigner/controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 6b8b6046b..6c26e001a 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -93,7 +93,7 @@ class Controller(Singleton): rather than at the top in order avoid circular imports. """ - VERSION = "0.7.0+SeedKeeper-ALPHA" + VERSION = "0.7.0+Satochip-ALPHA1" # Declare class member vars with type hints to enable richer IDE support throughout # the code. From 129e3c811fddecdbe5bf0eb8d1facaecea49b27f Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Mon, 5 Feb 2024 21:16:57 -0500 Subject: [PATCH 27/55] seedsigner os tweaks --- src/seedsigner/helpers/seedkeeper_utils.py | 5 ++- src/seedsigner/models/settings.py | 36 +++++++++++++++------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/seedsigner/helpers/seedkeeper_utils.py b/src/seedsigner/helpers/seedkeeper_utils.py index c540aea7d..65c673ff7 100644 --- a/src/seedsigner/helpers/seedkeeper_utils.py +++ b/src/seedsigner/helpers/seedkeeper_utils.py @@ -174,7 +174,10 @@ def run_globalplatform(parentObject, command, loadingText = "Loading", successte parentObject.loading_screen = LoadingScreenThread(text=loadingText) parentObject.loading_screen.start() - commandString = "java -jar /home/pi/Satochip-DIY/gp.jar " + command + if HOSTNAME == SEEDSIGNER_OS: + commandString = "/mnt/diy/jdk/bin/java -jar /mnt/diy/jdk/Satochip-DIY/gp.jar " + command + else: + commandString = "java -jar /home/pi/Satochip-DIY/gp.jar " + command data = run(commandString, capture_output=True, shell=True, text=True) diff --git a/src/seedsigner/models/settings.py b/src/seedsigner/models/settings.py index 1748c9e4f..a7587663f 100644 --- a/src/seedsigner/models/settings.py +++ b/src/seedsigner/models/settings.py @@ -17,6 +17,7 @@ class Settings(Singleton): HOSTNAME = platform.uname()[1] SEEDSIGNER_OS = "seedsigner-os" SETTINGS_FILENAME = "/mnt/microsd/settings.json" if HOSTNAME == SEEDSIGNER_OS else "settings.json" + SU_COMMAND_PREFIX = "" if HOSTNAME == SEEDSIGNER_OS else "sudo " @classmethod def get_instance(cls): @@ -184,21 +185,22 @@ def set_value(self, attr_name: str, value: any): self.loading_screen = LoadingScreenThread(text="Disabling USB Ports") self.loading_screen.start() print("Disabling USB") - for hub in range(3): - for port in range(3): - os.system("sudo hub-ctrl -H " + str(hub) + " -P " + str(port) + " -p 0") + os.system(self.SU_COMMAND_PREFIX + "uhubctl -a 0") self.loading_screen.stop() if "usb" in value and "usb" not in self._data[attr_name]: self.loading_screen = LoadingScreenThread(text="Enabling USB Ports") self.loading_screen.start() print("Enabling USB") - for hub in range(3): - for port in range(5): - os.system("sudo hub-ctrl -H " + str(hub) + " -P " + str(port) + " -p 1") + os.system(self.SU_COMMAND_PREFIX + "uhubctl -a 1") time.sleep(1) - os.system("sudo service pcscd restart") # PCSC doesn't always work properly after USB ports have been re-enabled + if HOSTNAME == SEEDSIGNER_OS: + os.system( + "/etc/init.d/S10pcscd restart") # PCSC doesn't always work properly after USB ports have been re-enabled + else: + os.system("sudo service pcscd restart") # PCSC doesn't always work properly after USB ports have been re-enabled + self.loading_screen.stop() # Execution order matters here if swithing from Phoenix to PN352, basically we want to disable phoenix first and then enable PN532 @@ -207,18 +209,30 @@ def set_value(self, attr_name: str, value: any): self.loading_screen.start() print("Phoenix Enabled") - os.system("sudo openct-control init") # OpenCT needs a bit of time to get going before restarting PCSCD (At least two seconds) to work reliabily + os.system(self.SU_COMMAND_PREFIX + "openct-control init") # OpenCT needs a bit of time to get going before restarting PCSCD (At least two seconds) to work reliabily time.sleep(3) - os.system("sudo service pcscd restart") + + if HOSTNAME == SEEDSIGNER_OS: + os.system( + "/etc/init.d/S10pcscd restart") + else: + os.system("sudo service pcscd restart") + self.loading_screen.stop() if "phoenix" not in value and "phoenix" in self._data[attr_name]: self.loading_screen = LoadingScreenThread(text="Stopping OpenCT") self.loading_screen.start() print("Phoenix Disabled") - os.system("sudo openct-control shutdown") + os.system(self.SU_COMMAND_PREFIX + "openct-control shutdown") time.sleep(3) - os.system("sudo service pcscd restart") + + if HOSTNAME == SEEDSIGNER_OS: + os.system( + "/etc/init.d/S10pcscd restart") + else: + os.system("sudo service pcscd restart") + self.loading_screen.stop() if "pn532" in value and "pn532" not in self._data[attr_name]: From b427ea9a5b5e97552b31ff9a7b5d0adc7c483b05 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 6 Feb 2024 17:20:11 -0500 Subject: [PATCH 28/55] fix typo --- src/seedsigner/helpers/seedkeeper_utils.py | 3 ++- src/seedsigner/models/settings.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/seedsigner/helpers/seedkeeper_utils.py b/src/seedsigner/helpers/seedkeeper_utils.py index 65c673ff7..5e6195862 100644 --- a/src/seedsigner/helpers/seedkeeper_utils.py +++ b/src/seedsigner/helpers/seedkeeper_utils.py @@ -8,6 +8,7 @@ import os import time from os import urandom +import platform def init_satochip(parentObject): from seedsigner.models.settings import Settings, SettingsConstants, SettingsDefinition @@ -174,7 +175,7 @@ def run_globalplatform(parentObject, command, loadingText = "Loading", successte parentObject.loading_screen = LoadingScreenThread(text=loadingText) parentObject.loading_screen.start() - if HOSTNAME == SEEDSIGNER_OS: + if platform.uname()[1] == "seedsigner-os": commandString = "/mnt/diy/jdk/bin/java -jar /mnt/diy/jdk/Satochip-DIY/gp.jar " + command else: commandString = "java -jar /home/pi/Satochip-DIY/gp.jar " + command diff --git a/src/seedsigner/models/settings.py b/src/seedsigner/models/settings.py index a7587663f..c98491046 100644 --- a/src/seedsigner/models/settings.py +++ b/src/seedsigner/models/settings.py @@ -195,7 +195,7 @@ def set_value(self, attr_name: str, value: any): os.system(self.SU_COMMAND_PREFIX + "uhubctl -a 1") time.sleep(1) - if HOSTNAME == SEEDSIGNER_OS: + if self.HOSTNAME == self.SEEDSIGNER_OS: os.system( "/etc/init.d/S10pcscd restart") # PCSC doesn't always work properly after USB ports have been re-enabled else: @@ -212,7 +212,7 @@ def set_value(self, attr_name: str, value: any): os.system(self.SU_COMMAND_PREFIX + "openct-control init") # OpenCT needs a bit of time to get going before restarting PCSCD (At least two seconds) to work reliabily time.sleep(3) - if HOSTNAME == SEEDSIGNER_OS: + if self.HOSTNAME == self.SEEDSIGNER_OS: os.system( "/etc/init.d/S10pcscd restart") else: @@ -227,7 +227,7 @@ def set_value(self, attr_name: str, value: any): os.system(self.SU_COMMAND_PREFIX + "openct-control shutdown") time.sleep(3) - if HOSTNAME == SEEDSIGNER_OS: + if self.HOSTNAME == self.SEEDSIGNER_OS: os.system( "/etc/init.d/S10pcscd restart") else: From c6bb02b6a0ec09632b97894fc9613f6127d260bd Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 6 Feb 2024 17:39:15 -0500 Subject: [PATCH 29/55] fix typos --- src/seedsigner/helpers/seedkeeper_utils.py | 2 +- src/seedsigner/models/settings.py | 6 +++--- src/seedsigner/views/tools_views.py | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/seedsigner/helpers/seedkeeper_utils.py b/src/seedsigner/helpers/seedkeeper_utils.py index 5e6195862..c511e5096 100644 --- a/src/seedsigner/helpers/seedkeeper_utils.py +++ b/src/seedsigner/helpers/seedkeeper_utils.py @@ -176,7 +176,7 @@ def run_globalplatform(parentObject, command, loadingText = "Loading", successte parentObject.loading_screen.start() if platform.uname()[1] == "seedsigner-os": - commandString = "/mnt/diy/jdk/bin/java -jar /mnt/diy/jdk/Satochip-DIY/gp.jar " + command + commandString = "/mnt/diy/jdk/bin/java -jar /mnt/diy/Satochip-DIY/gp.jar " + command else: commandString = "java -jar /home/pi/Satochip-DIY/gp.jar " + command diff --git a/src/seedsigner/models/settings.py b/src/seedsigner/models/settings.py index c98491046..9612b4da1 100644 --- a/src/seedsigner/models/settings.py +++ b/src/seedsigner/models/settings.py @@ -197,7 +197,7 @@ def set_value(self, attr_name: str, value: any): time.sleep(1) if self.HOSTNAME == self.SEEDSIGNER_OS: os.system( - "/etc/init.d/S10pcscd restart") # PCSC doesn't always work properly after USB ports have been re-enabled + "/etc/init.d/S01pcscd restart") # PCSC doesn't always work properly after USB ports have been re-enabled else: os.system("sudo service pcscd restart") # PCSC doesn't always work properly after USB ports have been re-enabled @@ -214,7 +214,7 @@ def set_value(self, attr_name: str, value: any): if self.HOSTNAME == self.SEEDSIGNER_OS: os.system( - "/etc/init.d/S10pcscd restart") + "/etc/init.d/S01pcscd restart") else: os.system("sudo service pcscd restart") @@ -229,7 +229,7 @@ def set_value(self, attr_name: str, value: any): if self.HOSTNAME == self.SEEDSIGNER_OS: os.system( - "/etc/init.d/S10pcscd restart") + "/etc/init.d/S01pcscd restart") else: os.system("sudo service pcscd restart") diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 84bb01f4b..3d8b20aa3 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -997,7 +997,10 @@ def run(self): import os from seedsigner.gui.screens.screen import LoadingScreenThread - cap_files = os.listdir('/home/pi/Satochip-DIY/build/') + if platform.uname()[1] == "seedsigner-os": + cap_files = os.listdir('/mnt/microsd/javacard-cap/') + else: + cap_files = os.listdir('/home/pi/Satochip-DIY/build/') selected_file_num = self.run_screen( ButtonListScreen, From 06b8be412bad05a9c2ca0abfbae010e7a7fb2f29 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 6 Feb 2024 18:01:07 -0500 Subject: [PATCH 30/55] Update tools_views.py --- src/seedsigner/views/tools_views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 3d8b20aa3..8f367757f 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -2,6 +2,7 @@ import hashlib import os import time +import platform from embit.descriptor import Descriptor from PIL import Image From a12b69297f06a262ff1332cc1f53670e52667299 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 6 Feb 2024 18:56:54 -0500 Subject: [PATCH 31/55] Update tools_views.py --- src/seedsigner/views/tools_views.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 8f367757f..b6d3ae7f5 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1016,7 +1016,11 @@ def run(self): applet_file = cap_files[selected_file_num] print("Selected:", applet_file) - installed_applets = seedkeeper_utils.run_globalplatform(self,"--install /home/pi/Satochip-DIY/build/" + applet_file, "Installing Applet", "Applet Installed") + if platform.uname()[1] == "seedsigner-os": + installed_applets = seedkeeper_utils.run_globalplatform(self, + "--install /mnt/microsd/javacard-cap/" + applet_file, "Installing Applet", "Applet Installed") + else: + installed_applets = seedkeeper_utils.run_globalplatform(self,"--install /home/pi/Satochip-DIY/build/" + applet_file, "Installing Applet", "Applet Installed") # This process often kills IFD-NFC, so restart it if required scinterface = self.settings.get_value(SettingsConstants.SETTING__SMARTCARD_INTERFACES) From 7f3a46a97370d284fecab76c761f195fd98472b4 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 6 Feb 2024 20:13:56 -0500 Subject: [PATCH 32/55] Update settings.py --- src/seedsigner/models/settings.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/seedsigner/models/settings.py b/src/seedsigner/models/settings.py index 9612b4da1..f5c008b16 100644 --- a/src/seedsigner/models/settings.py +++ b/src/seedsigner/models/settings.py @@ -196,10 +196,13 @@ def set_value(self, attr_name: str, value: any): time.sleep(1) if self.HOSTNAME == self.SEEDSIGNER_OS: - os.system( - "/etc/init.d/S01pcscd restart") # PCSC doesn't always work properly after USB ports have been re-enabled + os.system("/etc/init.d/S01pcscd stop") + time.sleep(1) + os.system("/etc/init.d/S01pcscd start") else: - os.system("sudo service pcscd restart") # PCSC doesn't always work properly after USB ports have been re-enabled + os.system("sudo service pcscd stop") + time.sleep(1) + os.system("sudo service pcscd start") self.loading_screen.stop() @@ -213,10 +216,13 @@ def set_value(self, attr_name: str, value: any): time.sleep(3) if self.HOSTNAME == self.SEEDSIGNER_OS: - os.system( - "/etc/init.d/S01pcscd restart") + os.system("/etc/init.d/S01pcscd stop") + time.sleep(1) + os.system("/etc/init.d/S01pcscd start") else: - os.system("sudo service pcscd restart") + os.system("sudo service pcscd stop") + time.sleep(1) + os.system("sudo service pcscd start") self.loading_screen.stop() @@ -228,10 +234,13 @@ def set_value(self, attr_name: str, value: any): time.sleep(3) if self.HOSTNAME == self.SEEDSIGNER_OS: - os.system( - "/etc/init.d/S01pcscd restart") + os.system("/etc/init.d/S01pcscd stop") + time.sleep(1) + os.system("/etc/init.d/S01pcscd start") else: - os.system("sudo service pcscd restart") + os.system("sudo service pcscd stop") + time.sleep(1) + os.system("sudo service pcscd start") self.loading_screen.stop() From 30c5a408bc59e11016a336e7726b54ed60bbb139 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Mon, 12 Feb 2024 21:48:43 -0500 Subject: [PATCH 33/55] Added ability to build applets from menu Added MicroSD tools --- src/seedsigner/views/tools_views.py | 187 +++++++++++++++++++++++++++- tools/javacard-build.xml.manual | 61 +++++++++ 2 files changed, 242 insertions(+), 6 deletions(-) create mode 100755 tools/javacard-build.xml.manual diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index b6d3ae7f5..9d39ec9f1 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -34,9 +34,10 @@ class ToolsMenuView(View): EXPLORER = "Address Explorer" ADDRESS = "Verify address" SMARTCARD = ("Smartcard Tools", FontAwesomeIconConstants.LOCK) + MICROSD = "MicroSD Tools" def run(self): - button_data = [self.IMAGE, self.DICE, self.KEYBOARD, self.EXPLORER, self.ADDRESS, self.SMARTCARD] + button_data = [self.IMAGE, self.DICE, self.KEYBOARD, self.EXPLORER, self.ADDRESS, self.SMARTCARD, self.MICROSD] selected_menu_num = self.run_screen( ButtonListScreen, @@ -66,6 +67,9 @@ def run(self): elif button_data[selected_menu_num] == self.SMARTCARD: return Destination(ToolsSmartcardMenuView) + + elif button_data[selected_menu_num] == self.MICROSD: + return Destination(ToolsMicroSDMenuView) @@ -725,7 +729,7 @@ def run(self): selected_menu_num = self.run_screen( ButtonListScreen, - title="Tools", + title="Smartcard Tools", is_button_text_centered=False, button_data=button_data ) @@ -970,15 +974,16 @@ def run(self): return Destination(MainMenuView) class ToolsSatochipDIYView(View): + BUILD_APPLETS = ("Build Applets") INSTALL_APPLET = ("Install Applet") UNINSTALL_APPLET = ("Uninstall Applet") def run(self): - button_data = [self.INSTALL_APPLET, self.UNINSTALL_APPLET] + button_data = [self.BUILD_APPLETS, self.INSTALL_APPLET, self.UNINSTALL_APPLET] selected_menu_num = self.run_screen( ButtonListScreen, - title="Tools", + title="Javacard DIY", is_button_text_centered=False, button_data=button_data ) @@ -986,12 +991,62 @@ def run(self): if selected_menu_num == RET_CODE__BACK_BUTTON: return Destination(BackStackView) + elif button_data[selected_menu_num] == self.BUILD_APPLETS: + return Destination(ToolsDIYBuildAppletsView) + elif button_data[selected_menu_num] == self.INSTALL_APPLET: return Destination(ToolsDIYInstallAppletView) elif button_data[selected_menu_num] == self.UNINSTALL_APPLET: return Destination(ToolsDIYUninstallAppletView) + +class ToolsDIYBuildAppletsView(View): + def run(self): + from subprocess import run + import os + from seedsigner.gui.screens.screen import LoadingScreenThread + + self.loading_screen = LoadingScreenThread(text="Building Applets\n\n\n\n\n\n(This takes a while)") + self.loading_screen.start() + + if platform.uname()[1] == "seedsigner-os": + commandString = "/mnt/diy/ant/bin/ant -f /mnt/microsd/javacard-build.xml" + else: + if not os.path.exists("/boot/javacard-build.xml"): + os.system("sudo cp /home/pi/seedsigner/tools/javacard-build.xml.manual /boot/javacard-build.xml") + + if not os.path.exists("/boot/javacard-cap/"): + os.system("sudo mkdir -p /boot/javacard-cap/") + + commandString = "sudo ant -f /boot/javacard-build.xml" + + data = run(commandString, capture_output=True, shell=True, text=True) + + print(data) + + self.loading_screen.stop() + + if(len(data.stderr) > 1): + data.stderr = data.stderr.split("Total time:")[0] + self.run_screen( + WarningScreen, + title="Failed", + status_headline=None, + text=data.stderr.replace("\n", " "), + show_back_button=False, + ) + else: + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"Applets Built", + show_back_button=False, + ) + + return Destination(MainMenuView) + class ToolsDIYInstallAppletView(View): def run(self): from subprocess import run @@ -1001,7 +1056,7 @@ def run(self): if platform.uname()[1] == "seedsigner-os": cap_files = os.listdir('/mnt/microsd/javacard-cap/') else: - cap_files = os.listdir('/home/pi/Satochip-DIY/build/') + cap_files = os.listdir('/boot/javacard-cap/') selected_file_num = self.run_screen( ButtonListScreen, @@ -1020,7 +1075,7 @@ def run(self): installed_applets = seedkeeper_utils.run_globalplatform(self, "--install /mnt/microsd/javacard-cap/" + applet_file, "Installing Applet", "Applet Installed") else: - installed_applets = seedkeeper_utils.run_globalplatform(self,"--install /home/pi/Satochip-DIY/build/" + applet_file, "Installing Applet", "Applet Installed") + installed_applets = seedkeeper_utils.run_globalplatform(self,"--install /boot/javacard-cap/" + applet_file, "Installing Applet", "Applet Installed") # This process often kills IFD-NFC, so restart it if required scinterface = self.settings.get_value(SettingsConstants.SETTING__SMARTCARD_INTERFACES) @@ -1078,3 +1133,123 @@ def run(self): os.system("ifdnfc-activate yes") return Destination(MainMenuView) + +"""**************************************************************************** + MicroSD Views +****************************************************************************""" +class ToolsMicroSDMenuView(View): + FLASH_IMAGE = ("Flash Image") + VERIFY_IMAGE = ("Verify MicroSD") + WIPE_ZERO = ("Wipe (Zero)") + WIPE_RANDOM = ("Wipe (Random)") + + def run(self): + button_data = [self.FLASH_IMAGE, self.VERIFY_IMAGE, self.WIPE_ZERO, self.WIPE_RANDOM] + + selected_menu_num = self.run_screen( + ButtonListScreen, + title="MicroSD Tools", + is_button_text_centered=False, + button_data=button_data + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + elif button_data[selected_menu_num] == self.FLASH_IMAGE: + return Destination(ToolsMicroSDFlashView) + + elif button_data[selected_menu_num] == self.VERIFY_IMAGE: + return Destination(ToolsMicroSDVerifyView) + + elif button_data[selected_menu_num] == self.WIPE_ZERO: + return Destination(ToolsMicroSDWipeZeroView) + + elif button_data[selected_menu_num] == self.WIPE_RANDOM: + return Destination(ToolsMicroSDWipeRandomView) + +class ToolsMicroSDFlashView(View): + def run(self): + + if platform.uname()[1] == "seedsigner-os": + microsd_images = os.listdir('/mnt/microsd/microsd-images/') + else: + microsd_images = os.listdir('/boot/microsd-images/') + + selected_file_num = self.run_screen( + ButtonListScreen, + title="Select Applet", + is_button_text_centered=False, + button_data=microsd_images + ) + + if selected_file_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + microsd_image = microsd_images[selected_file_num] + print("Selected:", microsd_image) + + if platform.uname()[1] == "seedsigner-os": + os.system("cp /mnt/microsd/microsd-images/" + microsd_image + " /tmp/img.img") + os.system("dd if=/tmp/img.img of=/dev/mmcblk0") + + else: + print("cp /mnt/microsd/microsd-images/" + microsd_image + " /tmp/img.img") + #os.system("cp /mnt/microsd/microsd-images/" + microsd_image + " /tmp/img.img") + #os.system("sudo dd if=/tmp/img.img of=/dev/mmcblk0") + + return Destination(MainMenuView) + +class ToolsMicroSDVerifyView(View): + def run(self): + from subprocess import run + import os + from seedsigner.gui.screens.screen import LoadingScreenThread + + self.loading_screen = LoadingScreenThread(text="Reading MicroSD\n\n\n\n\n\n") + self.loading_screen.start() + + if platform.uname()[1] == "seedsigner-os": + os.system("dd if=/dev/mmcblk0 of=/tmp/img.img bs=1M count=26") + else: + os.system("sudo dd if=/dev/mmcblk0 of=/tmp/img.img bs=1M count=26") + + data = run("sha256sum /tmp/img.img", capture_output=True, shell=True, text=True) + + print(data) + + self.loading_screen.stop() + + checksum = data.stdout[:64] + + formatted_checksum = data.stdout[:16] + "\n" + data.stdout[16:32] + "\n" + data.stdout[32:48] + "\n" + data.stdout[48:64] + + self.run_screen( + WarningScreen, + title="Unfamilliar Checksum", + status_headline=None, + text=formatted_checksum, + show_back_button=False, + ) + + return Destination(MainMenuView) + + class ToolsMicroSDWipeZeroView(View): + def run(self): + + if platform.uname()[1] == "seedsigner-os": + os.system("dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024") + else: + os.system("sudo dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024") + + return Destination(MainMenuView) + + class ToolsMicroSDWipeRandomView(View): + def run(self): + + if platform.uname()[1] == "seedsigner-os": + os.system("dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024") + else: + os.system("sudo dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024") + + return Destination(MainMenuView) \ No newline at end of file diff --git a/tools/javacard-build.xml.manual b/tools/javacard-build.xml.manual new file mode 100755 index 000000000..6ce8b03c0 --- /dev/null +++ b/tools/javacard-build.xml.manual @@ -0,0 +1,61 @@ + + + + + + Builds, tests, and runs the project . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 63d748493c2a6b6ad8b182757073201771f72361 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Mon, 12 Feb 2024 21:59:01 -0500 Subject: [PATCH 34/55] fix for microsd imagaging on manual build --- src/seedsigner/views/tools_views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 9d39ec9f1..55ab51228 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1194,9 +1194,8 @@ def run(self): os.system("dd if=/tmp/img.img of=/dev/mmcblk0") else: - print("cp /mnt/microsd/microsd-images/" + microsd_image + " /tmp/img.img") - #os.system("cp /mnt/microsd/microsd-images/" + microsd_image + " /tmp/img.img") - #os.system("sudo dd if=/tmp/img.img of=/dev/mmcblk0") + os.system("cp /boot/microsd-images/" + microsd_image + " /tmp/img.img") + os.system("sudo dd if=/tmp/img.img of=/dev/mmcblk0") return Destination(MainMenuView) From 957b30dcfe15d034cf1331b34c39ecad35fcc14d Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Mon, 12 Feb 2024 22:06:23 -0500 Subject: [PATCH 35/55] Add javacard build file for seedsigneros --- docs/smartcard_support_installation.md | 8 +--- src/seedsigner/views/tools_views.py | 6 +++ tools/javacard-build.xml.seedsigneros | 61 ++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 tools/javacard-build.xml.seedsigneros diff --git a/docs/smartcard_support_installation.md b/docs/smartcard_support_installation.md index c9b2b367a..b430e0de9 100644 --- a/docs/smartcard_support_installation.md +++ b/docs/smartcard_support_installation.md @@ -126,13 +126,9 @@ Download, install and build cp /home/pi/.local/lib/python3.7/site-packages/_nfc.py ~/.envs/seedsigner-env/lib/python3.10/site-packages/nfc.py cp /home/pi/.local/lib/python3.7/site-packages/_nfc.so ~/.envs/seedsigner-env/lib/python3.10/site-packages/_nfc.so -### hub-ctrl (Optional: Disables USB ports when not needed for Smartcard Interface) +### uhubctl (Optional: Disables USB ports when not needed for Smartcard Interface) - cd ~ - git clone https://github.com/yy502/hub-ctrl.git - cd hub-ctrl - gcc -o hub-ctrl hub-ctrl.c -lusb -std=c99 - sudo cp hub-ctrl /usr/local/bin/hub-ctrl + sudo apt install uhubctl ### Javacard Managment Tools (Optional: Needed to flash SeedKeeper to Javacards) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 9d39ec9f1..02d9681d3 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1011,6 +1011,12 @@ def run(self): self.loading_screen.start() if platform.uname()[1] == "seedsigner-os": + if not os.path.exists("/mnt/microsd/javacard-build.xml"): + os.system("cp /opt/tools/javacard-build.xml.seedsigneros /mnt/microsd/javacard-build.xml") + + if not os.path.exists("/mnt/microsd/javacard-cap/"): + os.system("mkdir -p /mnt/microsd/javacard-cap/") + commandString = "/mnt/diy/ant/bin/ant -f /mnt/microsd/javacard-build.xml" else: if not os.path.exists("/boot/javacard-build.xml"): diff --git a/tools/javacard-build.xml.seedsigneros b/tools/javacard-build.xml.seedsigneros new file mode 100644 index 000000000..72bc7c292 --- /dev/null +++ b/tools/javacard-build.xml.seedsigneros @@ -0,0 +1,61 @@ + + + + + + Builds, tests, and runs the project . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 00cea4b1d2c4e097505424a621e48ac733ae1546 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Mon, 12 Feb 2024 22:36:35 -0500 Subject: [PATCH 36/55] microsd tools update --- src/seedsigner/views/tools_views.py | 70 ++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 1ee0f3ef3..c29a3ecdb 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1176,6 +1176,7 @@ def run(self): class ToolsMicroSDFlashView(View): def run(self): + from subprocess import run if platform.uname()[1] == "seedsigner-os": microsd_images = os.listdir('/mnt/microsd/microsd-images/') @@ -1196,13 +1197,47 @@ def run(self): print("Selected:", microsd_image) if platform.uname()[1] == "seedsigner-os": - os.system("cp /mnt/microsd/microsd-images/" + microsd_image + " /tmp/img.img") - os.system("dd if=/tmp/img.img of=/dev/mmcblk0") + data = run("cp /mnt/microsd/microsd-images/" + microsd_image + " /tmp/img.img", capture_output=True, shell=True, text=True) + if len(data.stderr) > 1: + self.run_screen( + WarningScreen, + title="Error", + status_headline=None, + text="data.stderr", + show_back_button=False, + ) + return Destination(MainMenuView) + + self.run_screen( + WarningScreen, + title="Notice", + status_headline=None, + text="Insert MicroSD to be Flashed", + show_back_button=False, + ) + + data = run("dd if=/tmp/img.img of=/dev/mmcblk0", capture_output=True, shell=True, text=True) + if len(data.stderr) > 1: + self.run_screen( + WarningScreen, + title="Error", + status_headline=None, + text="data.stderr", + show_back_button=False, + ) else: os.system("cp /boot/microsd-images/" + microsd_image + " /tmp/img.img") os.system("sudo dd if=/tmp/img.img of=/dev/mmcblk0") + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"MicroSD Flashed", + show_back_button=False, + ) + return Destination(MainMenuView) class ToolsMicroSDVerifyView(View): @@ -1239,22 +1274,23 @@ def run(self): return Destination(MainMenuView) - class ToolsMicroSDWipeZeroView(View): - def run(self): - - if platform.uname()[1] == "seedsigner-os": - os.system("dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024") - else: - os.system("sudo dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024") +class ToolsMicroSDWipeZeroView(View): + def run(self): + from subprocess import run + if platform.uname()[1] == "seedsigner-os": + os.system("dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024") + else: + os.system("sudo dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024") - return Destination(MainMenuView) + return Destination(MainMenuView) - class ToolsMicroSDWipeRandomView(View): - def run(self): +class ToolsMicroSDWipeRandomView(View): + def run(self): + from subprocess import run - if platform.uname()[1] == "seedsigner-os": - os.system("dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024") - else: - os.system("sudo dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024") + if platform.uname()[1] == "seedsigner-os": + os.system("dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024") + else: + os.system("sudo dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024") - return Destination(MainMenuView) \ No newline at end of file + return Destination(MainMenuView) \ No newline at end of file From eb360922eaa2eaf24cbde37f9a7ab4492e334084 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 13 Feb 2024 07:17:21 -0500 Subject: [PATCH 37/55] Update tools_views.py --- src/seedsigner/views/tools_views.py | 82 ++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index c29a3ecdb..a3a1ff9f3 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1217,12 +1217,21 @@ def run(self): ) data = run("dd if=/tmp/img.img of=/dev/mmcblk0", capture_output=True, shell=True, text=True) + if len(data.stderr) > 1: self.run_screen( WarningScreen, title="Error", status_headline=None, - text="data.stderr", + text=data.stderr, + show_back_button=False, + ) + else: + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"MicroSD Flashed", show_back_button=False, ) @@ -1230,14 +1239,6 @@ def run(self): os.system("cp /boot/microsd-images/" + microsd_image + " /tmp/img.img") os.system("sudo dd if=/tmp/img.img of=/dev/mmcblk0") - self.run_screen( - LargeIconStatusScreen, - title="Success", - status_headline=None, - text=f"MicroSD Flashed", - show_back_button=False, - ) - return Destination(MainMenuView) class ToolsMicroSDVerifyView(View): @@ -1277,10 +1278,38 @@ def run(self): class ToolsMicroSDWipeZeroView(View): def run(self): from subprocess import run + + self.run_screen( + WarningScreen, + title="Notice", + status_headline=None, + text="Insert MicroSD to be Wiped", + show_back_button=False, + ) + if platform.uname()[1] == "seedsigner-os": - os.system("dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024") + cmd = "dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024" else: - os.system("sudo dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024") + cmd = "sudo dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024" + + data = run(cmd, capture_output=True, shell=True, text=True) + + if len(data.stderr) > 1: + self.run_screen( + WarningScreen, + title="Error", + status_headline=None, + text=data.stderr, + show_back_button=False, + ) + else: + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"MicroSD Wiped", + show_back_button=False, + ) return Destination(MainMenuView) @@ -1288,9 +1317,36 @@ class ToolsMicroSDWipeRandomView(View): def run(self): from subprocess import run + self.run_screen( + WarningScreen, + title="Notice", + status_headline=None, + text="Insert MicroSD to be Wiped", + show_back_button=False, + ) + if platform.uname()[1] == "seedsigner-os": - os.system("dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024") + cmd = "dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024" else: - os.system("sudo dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024") + cmd = "sudo dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024" + + data = run(cmd, capture_output=True, shell=True, text=True) + + if len(data.stderr) > 1: + self.run_screen( + WarningScreen, + title="Error", + status_headline=None, + text=data.stderr, + show_back_button=False, + ) + else: + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"MicroSD Wiped", + show_back_button=False, + ) return Destination(MainMenuView) \ No newline at end of file From 1bb0060caf632d2751eba48c55b8a6bc302387fa Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 13 Feb 2024 07:22:05 -0500 Subject: [PATCH 38/55] Update tools_views.py --- src/seedsigner/views/tools_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index a3a1ff9f3..d6963c7eb 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1017,7 +1017,7 @@ def run(self): if not os.path.exists("/mnt/microsd/javacard-cap/"): os.system("mkdir -p /mnt/microsd/javacard-cap/") - commandString = "/mnt/diy/ant/bin/ant -f /mnt/microsd/javacard-build.xml" + commandString = "/mnt/diy/ant/bin/ant -f /mnt/microsd/javacard-build.xml -DJAVA_HOME /mnt/diy/jdk" else: if not os.path.exists("/boot/javacard-build.xml"): os.system("sudo cp /home/pi/seedsigner/tools/javacard-build.xml.manual /boot/javacard-build.xml") From bedea0515e49c94b673496e847df46267226adc9 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 13 Feb 2024 07:42:16 -0500 Subject: [PATCH 39/55] microsd tools tweaks --- src/seedsigner/views/tools_views.py | 68 +++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index d6963c7eb..42af0527a 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1017,7 +1017,7 @@ def run(self): if not os.path.exists("/mnt/microsd/javacard-cap/"): os.system("mkdir -p /mnt/microsd/javacard-cap/") - commandString = "/mnt/diy/ant/bin/ant -f /mnt/microsd/javacard-build.xml -DJAVA_HOME /mnt/diy/jdk" + commandString = "/mnt/diy/ant/bin/ant -f /mnt/microsd/javacard-build.xml -DJAVA_HOME=/mnt/diy/jdk" else: if not os.path.exists("/boot/javacard-build.xml"): os.system("sudo cp /home/pi/seedsigner/tools/javacard-build.xml.manual /boot/javacard-build.xml") @@ -1177,6 +1177,7 @@ def run(self): class ToolsMicroSDFlashView(View): def run(self): from subprocess import run + from seedsigner.gui.screens.screen import LoadingScreenThread if platform.uname()[1] == "seedsigner-os": microsd_images = os.listdir('/mnt/microsd/microsd-images/') @@ -1216,9 +1217,28 @@ def run(self): show_back_button=False, ) + self.loading_screen = LoadingScreenThread(text="Flashing MicroSD\n\n\n\n\n\n") + self.loading_screen.start() + data = run("dd if=/tmp/img.img of=/dev/mmcblk0", capture_output=True, shell=True, text=True) - if len(data.stderr) > 1: + self.loading_screen.stop() + + data.stderr = data.stderr.split('\n') + + errors_cleaned = [] + for errorLine in data.stderr: + if "Records In" in errorLine: + inNum = errorLine.split("+")[0] + continue + elif "Records Out" in errorLine: + outNum = errorLine.split("+")[0] + continue + elif len(errorLine) < 1: + continue + errors_cleaned.append(errorLine) + + if inNum != outNum: self.run_screen( WarningScreen, title="Error", @@ -1278,6 +1298,10 @@ def run(self): class ToolsMicroSDWipeZeroView(View): def run(self): from subprocess import run + from seedsigner.gui.screens.screen import LoadingScreenThread + + self.loading_screen = LoadingScreenThread(text="Wiping MicroSD\n\n\n\n\n\n(This takes a while)") + self.loading_screen.start() self.run_screen( WarningScreen, @@ -1294,7 +1318,23 @@ def run(self): data = run(cmd, capture_output=True, shell=True, text=True) - if len(data.stderr) > 1: + self.loading_screen.stop() + + data.stderr = data.stderr.split('\n') + + errors_cleaned = [] + for errorLine in data.stderr: + if "Records In" in errorLine: + inNum = errorLine.split("+")[0] + continue + elif "Records Out" in errorLine: + outNum = errorLine.split("+")[0] + continue + elif len(errorLine) < 1: + continue + errors_cleaned.append(errorLine) + + if inNum != outNum: self.run_screen( WarningScreen, title="Error", @@ -1316,6 +1356,10 @@ def run(self): class ToolsMicroSDWipeRandomView(View): def run(self): from subprocess import run + from seedsigner.gui.screens.screen import LoadingScreenThread + + self.loading_screen = LoadingScreenThread(text="Wiping MicroSD\n\n\n\n\n\n(This takes a while)") + self.loading_screen.start() self.run_screen( WarningScreen, @@ -1332,7 +1376,23 @@ def run(self): data = run(cmd, capture_output=True, shell=True, text=True) - if len(data.stderr) > 1: + self.loading_screen.stop() + + data.stderr = data.stderr.split('\n') + + errors_cleaned = [] + for errorLine in data.stderr: + if "Records In" in errorLine: + inNum = errorLine.split("+")[0] + continue + elif "Records Out" in errorLine: + outNum = errorLine.split("+")[0] + continue + elif len(errorLine) < 1: + continue + errors_cleaned.append(errorLine) + + if inNum != outNum: self.run_screen( WarningScreen, title="Error", From 4e64835227dbd482e6675a49a6a6f9c0c353399c Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 13 Feb 2024 07:45:28 -0500 Subject: [PATCH 40/55] Update tools_views.py --- src/seedsigner/views/tools_views.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 42af0527a..9716fcf03 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1226,7 +1226,8 @@ def run(self): data.stderr = data.stderr.split('\n') - errors_cleaned = [] + inNum = 1 + outNum = 0 for errorLine in data.stderr: if "Records In" in errorLine: inNum = errorLine.split("+")[0] @@ -1234,9 +1235,6 @@ def run(self): elif "Records Out" in errorLine: outNum = errorLine.split("+")[0] continue - elif len(errorLine) < 1: - continue - errors_cleaned.append(errorLine) if inNum != outNum: self.run_screen( @@ -1322,7 +1320,8 @@ def run(self): data.stderr = data.stderr.split('\n') - errors_cleaned = [] + inNum = 1 + outNum = 0 for errorLine in data.stderr: if "Records In" in errorLine: inNum = errorLine.split("+")[0] @@ -1330,9 +1329,6 @@ def run(self): elif "Records Out" in errorLine: outNum = errorLine.split("+")[0] continue - elif len(errorLine) < 1: - continue - errors_cleaned.append(errorLine) if inNum != outNum: self.run_screen( @@ -1380,7 +1376,8 @@ def run(self): data.stderr = data.stderr.split('\n') - errors_cleaned = [] + inNum = 1 + outNum = 0 for errorLine in data.stderr: if "Records In" in errorLine: inNum = errorLine.split("+")[0] @@ -1388,9 +1385,6 @@ def run(self): elif "Records Out" in errorLine: outNum = errorLine.split("+")[0] continue - elif len(errorLine) < 1: - continue - errors_cleaned.append(errorLine) if inNum != outNum: self.run_screen( From 3ea016c7322b59f749341c83546e349d4bad81b6 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 13 Feb 2024 08:43:56 -0500 Subject: [PATCH 41/55] Update tools_views.py --- src/seedsigner/views/tools_views.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 9716fcf03..446cecba9 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1186,7 +1186,7 @@ def run(self): selected_file_num = self.run_screen( ButtonListScreen, - title="Select Applet", + title="Select Image", is_button_text_centered=False, button_data=microsd_images ) @@ -1224,11 +1224,11 @@ def run(self): self.loading_screen.stop() - data.stderr = data.stderr.split('\n') + data_stderr_split = data.stderr.split('\n') inNum = 1 outNum = 0 - for errorLine in data.stderr: + for errorLine in data_stderr_split: if "Records In" in errorLine: inNum = errorLine.split("+")[0] continue @@ -1318,11 +1318,11 @@ def run(self): self.loading_screen.stop() - data.stderr = data.stderr.split('\n') + data_stderr_split = data.stderr.split('\n') inNum = 1 outNum = 0 - for errorLine in data.stderr: + for errorLine in data_stderr_split: if "Records In" in errorLine: inNum = errorLine.split("+")[0] continue @@ -1374,11 +1374,11 @@ def run(self): self.loading_screen.stop() - data.stderr = data.stderr.split('\n') + data_stderr_split = data.stderr.split('\n') inNum = 1 outNum = 0 - for errorLine in data.stderr: + for errorLine in data_stderr_split: if "Records In" in errorLine: inNum = errorLine.split("+")[0] continue From c8eb81041edf5d6a6de0624d18db6e91e383453f Mon Sep 17 00:00:00 2001 From: 3rdIteration Date: Tue, 13 Feb 2024 14:15:16 -0500 Subject: [PATCH 42/55] dd bugfix & tweak --- src/seedsigner/views/tools_views.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 446cecba9..049d1b7f8 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1229,10 +1229,10 @@ def run(self): inNum = 1 outNum = 0 for errorLine in data_stderr_split: - if "Records In" in errorLine: + if "records in" in errorLine: inNum = errorLine.split("+")[0] continue - elif "Records Out" in errorLine: + elif "records out" in errorLine: outNum = errorLine.split("+")[0] continue @@ -1310,9 +1310,9 @@ def run(self): ) if platform.uname()[1] == "seedsigner-os": - cmd = "dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024" + cmd = "dd if=/dev/zero of=/dev/mmcblk0 bs=10M count=50" else: - cmd = "sudo dd if=/dev/zero of=/dev/mmcblk0 bs=1M count=1024" + cmd = "sudo dd if=/dev/zero of=/dev/mmcblk0 bs=10M count=50" data = run(cmd, capture_output=True, shell=True, text=True) @@ -1323,10 +1323,10 @@ def run(self): inNum = 1 outNum = 0 for errorLine in data_stderr_split: - if "Records In" in errorLine: + if "records in" in errorLine: inNum = errorLine.split("+")[0] continue - elif "Records Out" in errorLine: + elif "records out" in errorLine: outNum = errorLine.split("+")[0] continue @@ -1366,9 +1366,9 @@ def run(self): ) if platform.uname()[1] == "seedsigner-os": - cmd = "dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024" + cmd = "dd if=/dev/urandom of=/dev/mmcblk0 bs=10M count=50" else: - cmd = "sudo dd if=/dev/urandom of=/dev/mmcblk0 bs=1M count=1024" + cmd = "sudo dd if=/dev/urandom of=/dev/mmcblk0 bs=10M count=50" data = run(cmd, capture_output=True, shell=True, text=True) @@ -1379,10 +1379,11 @@ def run(self): inNum = 1 outNum = 0 for errorLine in data_stderr_split: - if "Records In" in errorLine: + if "records in" in errorLine: inNum = errorLine.split("+")[0] continue - elif "Records Out" in errorLine: + + if "records out" in errorLine: outNum = errorLine.split("+")[0] continue From 95cfc9889822546b6289d7becd9972d42882f4fb Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 13 Feb 2024 16:11:13 -0500 Subject: [PATCH 43/55] applet build tweak --- src/seedsigner/views/tools_views.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 049d1b7f8..0cb5f8a67 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1017,7 +1017,8 @@ def run(self): if not os.path.exists("/mnt/microsd/javacard-cap/"): os.system("mkdir -p /mnt/microsd/javacard-cap/") - commandString = "/mnt/diy/ant/bin/ant -f /mnt/microsd/javacard-build.xml -DJAVA_HOME=/mnt/diy/jdk" + os.system("export JAVA_HOME=/mnt/diy/jdk") + commandString = "/mnt/diy/ant/bin/ant -f /mnt/microsd/javacard-build.xml" else: if not os.path.exists("/boot/javacard-build.xml"): os.system("sudo cp /home/pi/seedsigner/tools/javacard-build.xml.manual /boot/javacard-build.xml") @@ -1033,24 +1034,24 @@ def run(self): self.loading_screen.stop() - if(len(data.stderr) > 1): - data.stderr = data.stderr.split("Total time:")[0] + if "BUILD SUCCESSFUL" in data.stdout: self.run_screen( - WarningScreen, - title="Failed", + LargeIconStatusScreen, + title="Success", status_headline=None, - text=data.stderr.replace("\n", " "), + text=f"Applets Built", show_back_button=False, ) else: self.run_screen( - LargeIconStatusScreen, - title="Success", + WarningScreen, + title="Failed", status_headline=None, - text=f"Applets Built", + text=data.stderr.replace("\n", " "), show_back_button=False, ) + return Destination(MainMenuView) class ToolsDIYInstallAppletView(View): From 7ef383a1d8430597eb20ae2730228d6a08e8580b Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 13 Feb 2024 16:39:00 -0500 Subject: [PATCH 44/55] fix export --- src/seedsigner/views/tools_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 0cb5f8a67..306472bb3 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1017,7 +1017,7 @@ def run(self): if not os.path.exists("/mnt/microsd/javacard-cap/"): os.system("mkdir -p /mnt/microsd/javacard-cap/") - os.system("export JAVA_HOME=/mnt/diy/jdk") + os.environ["JAVA_HOME"] = "/mnt/diy/jdk" commandString = "/mnt/diy/ant/bin/ant -f /mnt/microsd/javacard-build.xml" else: if not os.path.exists("/boot/javacard-build.xml"): From 8c16dcf0c654cfa68dec57cb6051bfa0de9b0f56 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Tue, 13 Feb 2024 16:40:13 -0500 Subject: [PATCH 45/55] fix wipe screen order --- src/seedsigner/views/tools_views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 306472bb3..06128a8ce 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1299,9 +1299,6 @@ def run(self): from subprocess import run from seedsigner.gui.screens.screen import LoadingScreenThread - self.loading_screen = LoadingScreenThread(text="Wiping MicroSD\n\n\n\n\n\n(This takes a while)") - self.loading_screen.start() - self.run_screen( WarningScreen, title="Notice", @@ -1310,6 +1307,9 @@ def run(self): show_back_button=False, ) + self.loading_screen = LoadingScreenThread(text="Wiping MicroSD\n\n\n\n\n\n(This takes a while)") + self.loading_screen.start() + if platform.uname()[1] == "seedsigner-os": cmd = "dd if=/dev/zero of=/dev/mmcblk0 bs=10M count=50" else: @@ -1355,9 +1355,6 @@ def run(self): from subprocess import run from seedsigner.gui.screens.screen import LoadingScreenThread - self.loading_screen = LoadingScreenThread(text="Wiping MicroSD\n\n\n\n\n\n(This takes a while)") - self.loading_screen.start() - self.run_screen( WarningScreen, title="Notice", @@ -1366,6 +1363,9 @@ def run(self): show_back_button=False, ) + self.loading_screen = LoadingScreenThread(text="Wiping MicroSD\n\n\n\n\n\n(This takes a while)") + self.loading_screen.start() + if platform.uname()[1] == "seedsigner-os": cmd = "dd if=/dev/urandom of=/dev/mmcblk0 bs=10M count=50" else: From b48f78b981665d2b10fc4d03c060998a8ffc8e95 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Wed, 14 Feb 2024 18:09:47 -0500 Subject: [PATCH 46/55] MicroSD menu button text --- src/seedsigner/views/tools_views.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 06128a8ce..0d5e08da9 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1216,6 +1216,7 @@ def run(self): status_headline=None, text="Insert MicroSD to be Flashed", show_back_button=False, + button_data="Continue" ) self.loading_screen = LoadingScreenThread(text="Flashing MicroSD\n\n\n\n\n\n") @@ -1244,6 +1245,7 @@ def run(self): status_headline=None, text=data.stderr, show_back_button=False, + button_data="Continue" ) else: self.run_screen( @@ -1252,6 +1254,7 @@ def run(self): status_headline=None, text=f"MicroSD Flashed", show_back_button=False, + button_data="Continue" ) else: @@ -1290,6 +1293,7 @@ def run(self): status_headline=None, text=formatted_checksum, show_back_button=False, + button_data="Continue" ) return Destination(MainMenuView) @@ -1305,6 +1309,7 @@ def run(self): status_headline=None, text="Insert MicroSD to be Wiped", show_back_button=False, + button_data="Continue" ) self.loading_screen = LoadingScreenThread(text="Wiping MicroSD\n\n\n\n\n\n(This takes a while)") @@ -1338,6 +1343,7 @@ def run(self): status_headline=None, text=data.stderr, show_back_button=False, + button_data="Continue" ) else: self.run_screen( @@ -1346,6 +1352,7 @@ def run(self): status_headline=None, text=f"MicroSD Wiped", show_back_button=False, + button_data="Continue" ) return Destination(MainMenuView) @@ -1361,6 +1368,7 @@ def run(self): status_headline=None, text="Insert MicroSD to be Wiped", show_back_button=False, + button_data="Continue" ) self.loading_screen = LoadingScreenThread(text="Wiping MicroSD\n\n\n\n\n\n(This takes a while)") @@ -1395,6 +1403,7 @@ def run(self): status_headline=None, text=data.stderr, show_back_button=False, + button_data="Continue" ) else: self.run_screen( @@ -1403,6 +1412,7 @@ def run(self): status_headline=None, text=f"MicroSD Wiped", show_back_button=False, + button_data="Continue" ) return Destination(MainMenuView) \ No newline at end of file From 932dfebe7f0ec9be5c327fe00cea047ac3d8ff82 Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Wed, 14 Feb 2024 19:09:38 -0500 Subject: [PATCH 47/55] Update tools_views.py --- src/seedsigner/views/tools_views.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 0d5e08da9..15a5524ba 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1216,7 +1216,7 @@ def run(self): status_headline=None, text="Insert MicroSD to be Flashed", show_back_button=False, - button_data="Continue" + button_data=["Continue"] ) self.loading_screen = LoadingScreenThread(text="Flashing MicroSD\n\n\n\n\n\n") @@ -1245,7 +1245,7 @@ def run(self): status_headline=None, text=data.stderr, show_back_button=False, - button_data="Continue" + button_data=["Continue"] ) else: self.run_screen( @@ -1254,7 +1254,7 @@ def run(self): status_headline=None, text=f"MicroSD Flashed", show_back_button=False, - button_data="Continue" + button_data=["Continue"] ) else: @@ -1293,7 +1293,7 @@ def run(self): status_headline=None, text=formatted_checksum, show_back_button=False, - button_data="Continue" + button_data=["Continue"] ) return Destination(MainMenuView) @@ -1309,7 +1309,7 @@ def run(self): status_headline=None, text="Insert MicroSD to be Wiped", show_back_button=False, - button_data="Continue" + button_data=["Continue"] ) self.loading_screen = LoadingScreenThread(text="Wiping MicroSD\n\n\n\n\n\n(This takes a while)") @@ -1343,7 +1343,7 @@ def run(self): status_headline=None, text=data.stderr, show_back_button=False, - button_data="Continue" + button_data=["Continue"] ) else: self.run_screen( @@ -1352,7 +1352,7 @@ def run(self): status_headline=None, text=f"MicroSD Wiped", show_back_button=False, - button_data="Continue" + button_data=["Continue"] ) return Destination(MainMenuView) @@ -1368,7 +1368,7 @@ def run(self): status_headline=None, text="Insert MicroSD to be Wiped", show_back_button=False, - button_data="Continue" + button_data=["Continue"] ) self.loading_screen = LoadingScreenThread(text="Wiping MicroSD\n\n\n\n\n\n(This takes a while)") @@ -1403,7 +1403,7 @@ def run(self): status_headline=None, text=data.stderr, show_back_button=False, - button_data="Continue" + button_data=["Continue"] ) else: self.run_screen( @@ -1412,7 +1412,7 @@ def run(self): status_headline=None, text=f"MicroSD Wiped", show_back_button=False, - button_data="Continue" + button_data=["Continue"] ) return Destination(MainMenuView) \ No newline at end of file From 65e3889b646a9cf3ac225d9c648a1bbf03f26131 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Fri, 16 Feb 2024 23:22:10 -0500 Subject: [PATCH 48/55] Add ability to load passphrase from any password on the seedkeeeper card. Add additional seedkeeper functionality. --- src/seedsigner/views/seed_views.py | 92 ++++++++++++++++- src/seedsigner/views/tools_views.py | 152 +++++++++++++++++++++++++++- 2 files changed, 242 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index c133ae29e..38cd83920 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -382,7 +382,8 @@ def run(self): class SeedFinalizeView(View): FINALIZE = "Done" - PASSPHRASE = "BIP-39 Passphrase" + PASSPHRASE = "Enter BIP39 Passphrase" + LOAD_SEEDKEEPER = "Load BIP39 Passphrase" def __init__(self): super().__init__() @@ -395,6 +396,8 @@ def run(self): if self.settings.get_value(SettingsConstants.SETTING__PASSPHRASE) != SettingsConstants.OPTION__DISABLED: button_data.append(self.PASSPHRASE) + button_data.append(self.LOAD_SEEDKEEPER) + selected_menu_num = self.run_screen( seed_screens.SeedFinalizeScreen, fingerprint=self.fingerprint, @@ -408,6 +411,9 @@ def run(self): elif button_data[selected_menu_num] == self.PASSPHRASE: return Destination(SeedAddPassphraseView) + elif button_data[selected_menu_num] == self.LOAD_SEEDKEEPER: + return Destination(SeedLoadSeedKeeperPassphraseView) + class SeedAddPassphraseView(View): @@ -429,7 +435,91 @@ def run(self): else: return Destination(SeedFinalizeView) +class SeedLoadSeedKeeperPassphraseView(View): + def __init__(self): + super().__init__() + self.seed = self.controller.storage.get_pending_seed() + + def run(self): + try: + Satochip_Connector = seedkeeper_utils.init_satochip(self) + + if not Satochip_Connector: + return Destination(BackStackView) + + headers = Satochip_Connector.seedkeeper_list_secret_headers() + + headers_parsed = [] + button_data = [] + for header in headers: + sid = header['id'] + label = header['label'] + stype = SEEDKEEPER_DIC_TYPE.get(header['type'], hex(header['type'])) # hex(header['type']) + origin = SEEDKEEPER_DIC_ORIGIN.get(header['origin'], hex(header['origin'])) # hex(header['origin']) + export_rights = SEEDKEEPER_DIC_EXPORT_RIGHTS.get(header['export_rights'], + hex(header[ + 'export_rights'])) # str(header['export_rights']) + export_nbplain = str(header['export_nbplain']) + export_nbsecure = str(header['export_nbsecure']) + export_nbcounter = str(header['export_counter']) if header['type'] == 0x70 else 'N/A' + fingerprint = header['fingerprint'] + + if stype == "Password" and export_rights == 'Plaintext export allowed': + headers_parsed.append((sid, label)) + button_data.append(label) + print(headers_parsed) + if len(headers_parsed) < 1: + self.run_screen( + WarningScreen, + title="No Secrets to Load", + status_headline=None, + text=f"No Password Secrets to Load from Seedkeeper", + show_back_button=False, + ) + return Destination(BackStackView) + + selected_menu_num = self.run_screen( + ButtonListScreen, + title="Select Secret", + is_button_text_centered=False, + button_data=button_data, + show_back_button=True, + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + secret_dict = Satochip_Connector.seedkeeper_export_secret(headers_parsed[selected_menu_num][0], None) + + secret_dict['secret'] = unhexlify(secret_dict['secret'])[1:].decode().rstrip("\x00") + + secret = secret_dict['secret'] + + print(secret) + + secret_size = secret_dict['secret_list'][0] + + secret_passphrase = secret[:secret_size] + + + except Exception as e: + print(e) + self.run_screen( + WarningScreen, + title="Error", + status_headline=None, + text=str(e), + show_back_button=True, + ) + return Destination(BackStackView) + + # The new passphrase will be the return value; it might be empty. + self.seed.set_passphrase(secret) + if len(self.seed.passphrase) > 0: + return Destination(SeedReviewPassphraseView) + else: + return Destination(SeedFinalizeView) class SeedReviewPassphraseView(View): """ diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 15a5524ba..baeb084c9 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -27,6 +27,9 @@ from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen, DireWarningScreen, seed_screens, LargeIconStatusScreen) +from pysatochip.JCconstants import SEEDKEEPER_DIC_TYPE, SEEDKEEPER_DIC_ORIGIN, SEEDKEEPER_DIC_EXPORT_RIGHTS +from binascii import unhexlify + class ToolsMenuView(View): IMAGE = (" New seed", FontAwesomeIconConstants.CAMERA) DICE = ("New seed", FontAwesomeIconConstants.DICE) @@ -722,10 +725,11 @@ class ToolsSmartcardMenuView(View): CHANGE_PIN = ("Change PIN") CHANGE_LABEL = ("Change Label") SATOCHIP = ("Satochip Functions") + SEEDKEEPER = ("SeedKeeper Functions") Satochip_DIY = ("DIY Tools") def run(self): - button_data = [self.CHANGE_PIN, self.CHANGE_LABEL, self.SATOCHIP, self.Satochip_DIY] + button_data = [self.CHANGE_PIN, self.CHANGE_LABEL, self.SEEDKEEPER, self.SATOCHIP, self.Satochip_DIY] selected_menu_num = self.run_screen( ButtonListScreen, @@ -745,6 +749,9 @@ def run(self): elif button_data[selected_menu_num] == self.SATOCHIP: return Destination(ToolsSatochipView) + + elif button_data[selected_menu_num] == self.SEEDKEEPER: + return Destination(ToolsSeedkeeperView) elif button_data[selected_menu_num] == self.Satochip_DIY: return Destination(ToolsSatochipDIYView) @@ -825,6 +832,149 @@ def run(self): return Destination(MainMenuView) +class ToolsSeedkeeperView(View): + VIEW_SECRETS = ("View Secrets") + IMPORT_PASSWORD = ("Import Password") + + def run(self): + button_data = [self.VIEW_SECRETS, self.IMPORT_PASSWORD] + + selected_menu_num = self.run_screen( + ButtonListScreen, + title="SeedKeeper", + is_button_text_centered=False, + button_data=button_data + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + elif button_data[selected_menu_num] == self.VIEW_SECRETS: + return Destination(ToolsSeedkeeperViewSecretsView) + + elif button_data[selected_menu_num] == self.IMPORT_PASSWORD: + return Destination(ToolsSeedkeeperImportPasswordView) + +class ToolsSeedkeeperViewSecretsView(View): + def run(self): + try: + Satochip_Connector = seedkeeper_utils.init_satochip(self) + + if not Satochip_Connector: + return Destination(BackStackView) + + headers = Satochip_Connector.seedkeeper_list_secret_headers() + + headers_parsed = [] + button_data = [] + for header in headers: + sid = header['id'] + stype = SEEDKEEPER_DIC_TYPE.get(header['type'], hex(header['type'])) # hex(header['type']) + label = stype + if stype == "Password": + label = "Pass:" + header['label'] + elif stype == "BIP39 mnemonic": + label = "Seed:" + header['label'] + elif stype == "2FA secret": + label = "2FA:" + header['label'] + origin = SEEDKEEPER_DIC_ORIGIN.get(header['origin'], hex(header['origin'])) # hex(header['origin']) + export_rights = SEEDKEEPER_DIC_EXPORT_RIGHTS.get(header['export_rights'], + hex(header[ + 'export_rights'])) # str(header['export_rights']) + export_nbplain = str(header['export_nbplain']) + export_nbsecure = str(header['export_nbsecure']) + export_nbcounter = str(header['export_counter']) if header['type'] == 0x70 else 'N/A' + fingerprint = header['fingerprint'] + + if export_rights == 'Plaintext export allowed': + headers_parsed.append((sid, label)) + button_data.append(label) + + print(headers_parsed) + if len(headers_parsed) < 1: + self.run_screen( + WarningScreen, + title="No Secrets to Load", + status_headline=None, + text=f"No Secrets to Load from Seedkeeper", + show_back_button=False, + ) + return Destination(BackStackView) + + selected_menu_num = self.run_screen( + ButtonListScreen, + title="Select Secret", + is_button_text_centered=False, + button_data=button_data, + show_back_button=True, + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + secret_dict = Satochip_Connector.seedkeeper_export_secret(headers_parsed[selected_menu_num][0], None) + + stype = SEEDKEEPER_DIC_TYPE.get(secret_dict['type'], hex(secret_dict['type'])) # hex(header['type']) + print(stype) + + if 'mnemonic' in stype: + secret_dict['secret'] = unhexlify(secret_dict['secret'])[1:].decode().rstrip("\x00") + + bip39_secret = secret_dict['secret'] + + secret_size = secret_dict['secret_list'][0] + secret_mnemonic = bip39_secret[:secret_size] + secret_passphrase = bip39_secret[secret_size + 1:] + + secret_dict['secret'] = "Mnemonic:" + secret_mnemonic + " Passphrase:" + secret_passphrase + + elif stype == 'Password': + secret_dict['secret'] = unhexlify(secret_dict['secret'])[1:].decode() + else: + secret_dict['secret'] = secret_dict['secret'][2:] + + selected_menu_num = self.run_screen( + LargeIconStatusScreen, + title=secret_dict['label'], + status_headline=None, + text = secret_dict['secret'], + status_icon_size=0, + show_back_button=True, + allow_text_overflow=True, + button_data=["Show as QR"], + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + else: + from seedsigner.gui.screens.screen import QRDisplayScreen + qr_encoder = EncodeQR(qr_type=QRType.GENERIC_STRING, generic_string=secret_dict['secret']) + self.run_screen( + QRDisplayScreen, + qr_encoder=qr_encoder, + ) + + return Destination(BackStackView) + + + except Exception as e: + print(e) + self.run_screen( + WarningScreen, + title="Error", + status_headline=None, + text=str(e), + show_back_button=True, + ) + return Destination(BackStackView) + + # The new passphrase will be the return value; it might be empty. + self.seed.set_passphrase(secret) + if len(self.seed.passphrase) > 0: + return Destination(SeedReviewPassphraseView) + else: + return Destination(SeedFinalizeView) + class ToolsSatochipView(View): IMPORT_SEED = ("Import Seed") ENABLE_2FA = ("Enable 2FA") From df7e1cba148598a0b5584ef5c6890e4c7dacb50f Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Sat, 17 Feb 2024 11:28:54 -0500 Subject: [PATCH 49/55] Fix Always Wiping Multisig Descriptor at Main Menu --- src/seedsigner/controller.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 206ad2c0a..18cdfb72c 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -298,7 +298,6 @@ def run(self): # Home always wipes the back_stack/state of temp vars self.resume_main_flow = None - self.multisig_wallet_descriptor = None self.unverified_address = None self.address_explorer_data = None self.psbt = None From 39fc63af50f8ef8657b8b67c426614c2b88a2de2 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Sat, 17 Feb 2024 13:57:48 -0500 Subject: [PATCH 50/55] Add initial ability to save/load wallet descriptors to Seedkeeper --- src/seedsigner/views/seed_views.py | 2 - src/seedsigner/views/tools_views.py | 186 ++++++++++++++++++++++++++-- 2 files changed, 177 insertions(+), 11 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 38cd83920..7ef73e0e0 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -496,8 +496,6 @@ def run(self): secret = secret_dict['secret'] - print(secret) - secret_size = secret_dict['secret_list'][0] secret_passphrase = secret[:secret_size] diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index baeb084c9..69ba07cce 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -28,7 +28,7 @@ WarningScreen, DireWarningScreen, seed_screens, LargeIconStatusScreen) from pysatochip.JCconstants import SEEDKEEPER_DIC_TYPE, SEEDKEEPER_DIC_ORIGIN, SEEDKEEPER_DIC_EXPORT_RIGHTS -from binascii import unhexlify +from binascii import unhexlify, hexlify class ToolsMenuView(View): IMAGE = (" New seed", FontAwesomeIconConstants.CAMERA) @@ -835,9 +835,11 @@ def run(self): class ToolsSeedkeeperView(View): VIEW_SECRETS = ("View Secrets") IMPORT_PASSWORD = ("Import Password") + LOAD_DESCRIPTOR = "Load MultiSig Descriptor" + SAVE_DESCRIPTOR = "Save MultiSig Descriptor" def run(self): - button_data = [self.VIEW_SECRETS, self.IMPORT_PASSWORD] + button_data = [self.VIEW_SECRETS, self.IMPORT_PASSWORD, self.LOAD_DESCRIPTOR, self.SAVE_DESCRIPTOR] selected_menu_num = self.run_screen( ButtonListScreen, @@ -855,6 +857,12 @@ def run(self): elif button_data[selected_menu_num] == self.IMPORT_PASSWORD: return Destination(ToolsSeedkeeperImportPasswordView) + elif button_data[selected_menu_num] == self.LOAD_DESCRIPTOR: + return Destination(ToolsSeedkeeperLoadDescriptorView) + + elif button_data[selected_menu_num] == self.SAVE_DESCRIPTOR: + return Destination(ToolsSeedkeeperSaveDescriptorView) + class ToolsSeedkeeperViewSecretsView(View): def run(self): try: @@ -915,7 +923,6 @@ def run(self): secret_dict = Satochip_Connector.seedkeeper_export_secret(headers_parsed[selected_menu_num][0], None) stype = SEEDKEEPER_DIC_TYPE.get(secret_dict['type'], hex(secret_dict['type'])) # hex(header['type']) - print(stype) if 'mnemonic' in stype: secret_dict['secret'] = unhexlify(secret_dict['secret'])[1:].decode().rstrip("\x00") @@ -956,6 +963,135 @@ def run(self): return Destination(BackStackView) + except Exception as e: + print(e) + self.run_screen( + WarningScreen, + title="Error", + status_headline=None, + text=str(e), + show_back_button=True, + ) + return Destination(BackStackView) + + + +class ToolsSeedkeeperImportPasswordView(View): + def run(self): + secret_label = seed_screens.SeedAddPassphraseScreen(title="Secret Label").display() + if secret_label == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + secret_text = seed_screens.SeedAddPassphraseScreen(title="Secret Text").display() + if secret_text == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + Satochip_Connector = seedkeeper_utils.init_satochip(self) + if not Satochip_Connector: + return Destination(BackStackView) + + header = Satochip_Connector.make_header("Password", "Plaintext export allowed", secret_label) + secret_text_list = list(bytes(secret_text, 'utf-8')) + secret_list = [len(secret_text_list)] + secret_text_list + secret_dic = {'header': header, 'secret_list': secret_list} + try: + (sid, fingerprint) = Satochip_Connector.seedkeeper_import_secret(secret_dic) + print("Imported - SID:", sid, " Fingerprint:", fingerprint) + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text=f"Password Imported", + show_back_button=False, + ) + except Exception as e: + print(e) + self.run_screen( + WarningScreen, + title="Failed", + status_headline=None, + text=f"Password Import Failed", + show_back_button=False, + ) + + return Destination(BackStackView) + +class ToolsSeedkeeperLoadDescriptorView(View): + def run(self): + from seedsigner.views.seed_views import MultisigWalletDescriptorView + try: + Satochip_Connector = seedkeeper_utils.init_satochip(self) + + if not Satochip_Connector: + return Destination(BackStackView) + + headers = Satochip_Connector.seedkeeper_list_secret_headers() + + multisig_descriptor_secrets = [] + xpub_secrets = [] + button_data = [] + for header in headers: + sid = header['id'] + stype = SEEDKEEPER_DIC_TYPE.get(header['type'], hex(header['type'])) # hex(header['type']) + label = header['label'] + origin = SEEDKEEPER_DIC_ORIGIN.get(header['origin'], hex(header['origin'])) # hex(header['origin']) + export_rights = SEEDKEEPER_DIC_EXPORT_RIGHTS.get(header['export_rights'], + hex(header[ + 'export_rights'])) # str(header['export_rights']) + export_nbplain = str(header['export_nbplain']) + export_nbsecure = str(header['export_nbsecure']) + export_nbcounter = str(header['export_counter']) if header['type'] == 0x70 else 'N/A' + fingerprint = header['fingerprint'] + + if export_rights == 'Plaintext export allowed': + if "msig_desc_" in label: + multisig_descriptor_secrets.append((sid, label.replace("msig_desc_", ""))) + button_data.append(label.replace("msig_desc_", "")) + + if "xpub_" in label: + xpub_secrets.append((sid, label)) + + print("Multisig Descriptor Secrets:", multisig_descriptor_secrets) + print("Xpub Secrets:",xpub_secrets) + + if len(multisig_descriptor_secrets) < 1: + self.run_screen( + WarningScreen, + title="No Descriptors", + status_headline=None, + text=f"No Multisig Descriptors to Load from Seedkeeper", + show_back_button=False, + ) + return Destination(BackStackView) + + selected_menu_num = self.run_screen( + ButtonListScreen, + title="Select Descriptor", + is_button_text_centered=False, + button_data=button_data, + show_back_button=True, + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + secret_dict = Satochip_Connector.seedkeeper_export_secret(multisig_descriptor_secrets[selected_menu_num][0], None) + + secret_dict['secret'] = unhexlify(secret_dict['secret'])[1:].decode() + + secret_template = secret_dict['secret'] + + for xpub_secret_id, xpub_secret_label in xpub_secrets: + if xpub_secret_label in secret_template: + print("Matched on:", xpub_secret_label) + secret_dict = Satochip_Connector.seedkeeper_export_secret(xpub_secret_id, None) + secret_dict['secret'] = unhexlify(secret_dict['secret'])[1:].decode() + secret_template = secret_template.replace(xpub_secret_label, secret_dict['secret']) + + self.controller.multisig_wallet_descriptor = Descriptor.from_string(secret_template) + + return Destination(MultisigWalletDescriptorView, skip_current_view=True) + except Exception as e: print(e) @@ -967,13 +1103,45 @@ def run(self): show_back_button=True, ) return Destination(BackStackView) + + +class ToolsSeedkeeperSaveDescriptorView(View): + def run(self): + descriptor = self.controller.multisig_wallet_descriptor + + descriptor_string = descriptor.to_string() + + print(descriptor_string) + + key_strings = [] + + for key in descriptor.keys: + key_string = key.to_string() + key_name = "xpub_" + hexlify(key.fingerprint).decode() + + descriptor_string = descriptor_string.replace(key_string, key_name) + key_strings.append((key_name, key_string)) + + ret = seed_screens.SeedAddPassphraseScreen(title="Descriptor Label").display() + + if ret == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) - # The new passphrase will be the return value; it might be empty. - self.seed.set_passphrase(secret) - if len(self.seed.passphrase) > 0: - return Destination(SeedReviewPassphraseView) - else: - return Destination(SeedFinalizeView) + key_strings.append(("msig_desc_" + ret, descriptor_string)) + + Satochip_Connector = seedkeeper_utils.init_satochip(self) + + if not Satochip_Connector: + return Destination(BackStackView) + + for secret_label, secret_text in key_strings: + header = Satochip_Connector.make_header("Password", "Plaintext export allowed", secret_label) + secret_text_list = list(bytes(secret_text, 'utf-8')) + secret_list = [len(secret_text_list)] + secret_text_list + secret_dic = {'header': header, 'secret_list': secret_list} + (sid, fingerprint) = Satochip_Connector.seedkeeper_import_secret(secret_dic) + print("Imported - SID:", sid, " Fingerprint:", fingerprint) + class ToolsSatochipView(View): IMPORT_SEED = ("Import Seed") From d4be7ec958a4ed39e19cb721a3aa03981927207a Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Sat, 17 Feb 2024 13:59:13 -0500 Subject: [PATCH 51/55] fix bug that wipes multisig descriptor on return to main menu --- src/seedsigner/controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 6c26e001a..9c770a09b 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -292,7 +292,7 @@ def run(self): # Home always wipes the back_stack/state of temp vars self.resume_main_flow = None - self.multisig_wallet_descriptor = None + # self.multisig_wallet_descriptor = None self.unverified_address = None self.address_explorer_data = None self.psbt = None From 51230f2e8016ef7e7e56179b494b5215c17dee02 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Sat, 17 Feb 2024 14:24:17 -0500 Subject: [PATCH 52/55] improve wallet descriptor export --- src/seedsigner/views/tools_views.py | 119 ++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 25 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 69ba07cce..1c9bda0f7 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -1107,41 +1107,110 @@ def run(self): class ToolsSeedkeeperSaveDescriptorView(View): def run(self): - descriptor = self.controller.multisig_wallet_descriptor + try: + descriptor = self.controller.multisig_wallet_descriptor + + descriptor_string = descriptor.to_string() - descriptor_string = descriptor.to_string() + print(descriptor_string) - print(descriptor_string) + key_strings = [] - key_strings = [] + for key in descriptor.keys: + key_string = key.to_string() + key_name = "xpub_" + hexlify(key.fingerprint).decode() + + descriptor_string = descriptor_string.replace(key_string, key_name) + key_strings.append((key_name, key_string)) - for key in descriptor.keys: - key_string = key.to_string() - key_name = "xpub_" + hexlify(key.fingerprint).decode() + ret = seed_screens.SeedAddPassphraseScreen(title="Descriptor Label").display() + + if ret == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) - descriptor_string = descriptor_string.replace(key_string, key_name) - key_strings.append((key_name, key_string)) + key_strings.append(("msig_desc_" + ret, descriptor_string)) - ret = seed_screens.SeedAddPassphraseScreen(title="Descriptor Label").display() + Satochip_Connector = seedkeeper_utils.init_satochip(self) - if ret == RET_CODE__BACK_BUTTON: - return Destination(BackStackView) - - key_strings.append(("msig_desc_" + ret, descriptor_string)) + if not Satochip_Connector: + return Destination(BackStackView) + + # Check for existing secrest on the Seedkeeper (Related to this descriptor) + headers = Satochip_Connector.seedkeeper_list_secret_headers() - Satochip_Connector = seedkeeper_utils.init_satochip(self) + multisig_descriptor_secrets = [] + xpub_labels = [] + button_data = [] + for header in headers: + sid = header['id'] + stype = SEEDKEEPER_DIC_TYPE.get(header['type'], hex(header['type'])) # hex(header['type']) + label = header['label'] + origin = SEEDKEEPER_DIC_ORIGIN.get(header['origin'], hex(header['origin'])) # hex(header['origin']) + export_rights = SEEDKEEPER_DIC_EXPORT_RIGHTS.get(header['export_rights'], + hex(header[ + 'export_rights'])) # str(header['export_rights']) + export_nbplain = str(header['export_nbplain']) + export_nbsecure = str(header['export_nbsecure']) + export_nbcounter = str(header['export_counter']) if header['type'] == 0x70 else 'N/A' + fingerprint = header['fingerprint'] - if not Satochip_Connector: - return Destination(BackStackView) - - for secret_label, secret_text in key_strings: - header = Satochip_Connector.make_header("Password", "Plaintext export allowed", secret_label) - secret_text_list = list(bytes(secret_text, 'utf-8')) - secret_list = [len(secret_text_list)] + secret_text_list - secret_dic = {'header': header, 'secret_list': secret_list} - (sid, fingerprint) = Satochip_Connector.seedkeeper_import_secret(secret_dic) - print("Imported - SID:", sid, " Fingerprint:", fingerprint) + if export_rights == 'Plaintext export allowed': + if "msig_desc_" in label: + multisig_descriptor_secrets.append((sid, label.replace("msig_desc_", ""))) + button_data.append(label.replace("msig_desc_", "")) + + if "xpub_" in label: + xpub_labels.append(label) + + print("Multisig Descriptor Secrets:", multisig_descriptor_secrets) + print("Xpub Secrets:",xpub_labels) + + multisig_descriptor_templates = [] + + for secret_id, secret_label in multisig_descriptor_secrets: + secret_dict = Satochip_Connector.seedkeeper_export_secret(secret_id, None) + + secret_dict['secret'] = unhexlify(secret_dict['secret'])[1:].decode() + + multisig_descriptor_templates.append(secret_dict['secret']) + + print(multisig_descriptor_templates) + + secrets_imported = 0 + secrets_skipped = 0 + # Add required secrets to seedkeeper + for secret_label, secret_text in key_strings: + if secret_text in multisig_descriptor_templates or secret_label in xpub_labels: + print("Mached Existing Secret, skipping:", secret_label) + secrets_skipped += 1 + continue + header = Satochip_Connector.make_header("Password", "Plaintext export allowed", secret_label) + secret_text_list = list(bytes(secret_text, 'utf-8')) + secret_list = [len(secret_text_list)] + secret_text_list + secret_dic = {'header': header, 'secret_list': secret_list} + (sid, fingerprint) = Satochip_Connector.seedkeeper_import_secret(secret_dic) + print("Imported - SID:", sid, " Fingerprint:", fingerprint) + secrets_imported += 1 + + self.run_screen( + LargeIconStatusScreen, + title="Success", + status_headline=None, + text="Multisig Descriptor Imported." + "\nImported:" + str(secrets_imported) + "\nSkipped:" + str(secrets_skipped), + show_back_button=False, + ) + except Exception as e: + print(e) + self.run_screen( + WarningScreen, + title="Error", + status_headline=None, + text=str(e), + show_back_button=True, + ) + + return Destination(BackStackView) class ToolsSatochipView(View): IMPORT_SEED = ("Import Seed") From fca41a27063c46d02901073821aad94290c48e69 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Sun, 18 Feb 2024 13:44:27 -0500 Subject: [PATCH 53/55] Change text & add warning when signing a Multisig transaction without a descriptor loaded. --- src/seedsigner/views/psbt_views.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 9c0d9c60f..7d99a8af1 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -285,6 +285,7 @@ def run(self): class PSBTChangeDetailsView(View): NEXT = "Next" + SKIP_VERIFICATION = "Skip Verificiation" VERIFY_MULTISIG = "Verify Multisig Change" @@ -330,9 +331,11 @@ def run(self): if is_change_derivation_path: title = "Your Change" self.VERIFY_MULTISIG = "Verify Multisig Change" + unverified_warning_text = "Can't Verify that Change Outputs Belong to your Wallet" else: title = "Self-Transfer" self.VERIFY_MULTISIG = "Verify Multisig Addr" + unverified_warning_text = "Can't Verify that Self-Transfer Outputs Belong to your Wallet" # if psbt_parser.num_change_outputs > 1: # title += f" (#{self.change_address_num + 1})" @@ -345,7 +348,7 @@ def run(self): else: # Have the Screen offer to load in the multisig descriptor. - button_data = [self.VERIFY_MULTISIG, self.NEXT] + button_data = [self.VERIFY_MULTISIG, self.SKIP_VERIFICATION] else: # Single sig @@ -413,7 +416,19 @@ def run(self): if selected_menu_num == RET_CODE__BACK_BUTTON: return Destination(BackStackView) - elif button_data[selected_menu_num] == self.NEXT: + elif button_data[selected_menu_num] == self.NEXT or button_data[selected_menu_num] == self.SKIP_VERIFICATION: + if button_data[selected_menu_num] == self.SKIP_VERIFICATION: + self.run_screen( + DireWarningScreen, + title="Security Warning", + status_icon_name=SeedSignerIconConstants.WARNING, + status_headline="Potential Loss of Funds", + text=unverified_warning_text, + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + if self.change_address_num < psbt_parser.num_change_outputs - 1: return Destination(PSBTChangeDetailsView, view_args={"change_address_num": self.change_address_num + 1}) else: From b29f1114c03e379aa4d23db4b94853951d418385 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Sun, 18 Feb 2024 13:53:13 -0500 Subject: [PATCH 54/55] Fix menu navigation --- src/seedsigner/views/psbt_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 7d99a8af1..d6a751868 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -418,7 +418,7 @@ def run(self): elif button_data[selected_menu_num] == self.NEXT or button_data[selected_menu_num] == self.SKIP_VERIFICATION: if button_data[selected_menu_num] == self.SKIP_VERIFICATION: - self.run_screen( + selected_menu_num = self.run_screen( DireWarningScreen, title="Security Warning", status_icon_name=SeedSignerIconConstants.WARNING, From 66d68047fb55bfecf6ea90dd757740f6c7074e93 Mon Sep 17 00:00:00 2001 From: CryptoGuide Date: Sun, 18 Feb 2024 14:28:22 -0500 Subject: [PATCH 55/55] Fix up CI tests to handle new warning screen --- tests/test_flows_psbt.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_flows_psbt.py b/tests/test_flows_psbt.py index 02b714463..c5e0f72a1 100644 --- a/tests/test_flows_psbt.py +++ b/tests/test_flows_psbt.py @@ -73,7 +73,8 @@ def load_seed_into_decoder(view: scan_views.ScanView): FlowStep(psbt_views.PSBTOverviewView), FlowStep(psbt_views.PSBTMathView), FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.SKIP_VERIFICATION), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=0), FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), FlowStep(psbt_views.PSBTSigningErrorView, button_data_selection=psbt_views.PSBTSigningErrorView.SELECT_DIFF_SEED), FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), @@ -85,7 +86,8 @@ def load_seed_into_decoder(view: scan_views.ScanView): FlowStep(psbt_views.PSBTOverviewView), FlowStep(psbt_views.PSBTMathView), FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.SKIP_VERIFICATION), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=0), FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), FlowStep(psbt_views.PSBTSignedQRDisplayView), FlowStep(MainMenuView),