Browse Source

hw: allow bypassing "too old firmware" error when using hw wallets

The framework here is generic enough that it can be used for any hw plugin,
however atm only Trezor is implemented.

closes #5391
dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
SomberNight 6 years ago
parent
commit
371e1a6ebf
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 11
      electrum/base_wizard.py
  2. 12
      electrum/gui/qt/util.py
  3. 2
      electrum/plugin.py
  4. 20
      electrum/plugins/hw_wallet/plugin.py
  5. 20
      electrum/plugins/hw_wallet/qt.py
  6. 19
      electrum/plugins/trezor/clientbase.py
  7. 4
      electrum/plugins/trezor/trezor.py

11
electrum/base_wizard.py

@ -44,6 +44,7 @@ from .util import UserCancelled, InvalidPassword, WalletFileException
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
from .plugin import Plugins, HardwarePluginLibraryUnavailable from .plugin import Plugins, HardwarePluginLibraryUnavailable
from .logging import Logger from .logging import Logger
from .plugins.hw_wallet.plugin import OutdatedHwFirmwareException, HW_PluginBase
if TYPE_CHECKING: if TYPE_CHECKING:
from .plugin import DeviceInfo from .plugin import DeviceInfo
@ -323,7 +324,7 @@ class BaseWizard(Logger):
run_next=lambda *args: self.on_device(*args, purpose=purpose, storage=storage)) run_next=lambda *args: self.on_device(*args, purpose=purpose, storage=storage))
def on_device(self, name, device_info, *, purpose, storage=None): def on_device(self, name, device_info, *, purpose, storage=None):
self.plugin = self.plugins.get_plugin(name) self.plugin = self.plugins.get_plugin(name) # type: HW_PluginBase
try: try:
self.plugin.setup_device(device_info, self, purpose) self.plugin.setup_device(device_info, self, purpose)
except OSError as e: except OSError as e:
@ -335,6 +336,14 @@ class BaseWizard(Logger):
devmgr.unpair_id(device_info.device.id_) devmgr.unpair_id(device_info.device.id_)
self.choose_hw_device(purpose, storage=storage) self.choose_hw_device(purpose, storage=storage)
return return
except OutdatedHwFirmwareException as e:
if self.question(e.text_ignore_old_fw_and_continue(), title=_("Outdated device firmware")):
self.plugin.set_ignore_outdated_fw()
# will need to re-pair
devmgr = self.plugins.device_manager
devmgr.unpair_id(device_info.device.id_)
self.choose_hw_device(purpose, storage=storage)
return
except (UserCancelled, GoBack): except (UserCancelled, GoBack):
self.choose_hw_device(purpose, storage=storage) self.choose_hw_device(purpose, storage=storage)
return return

12
electrum/gui/qt/util.py

@ -206,11 +206,15 @@ class MessageBoxMixin(object):
def top_level_window(self, test_func=None): def top_level_window(self, test_func=None):
return self.top_level_window_recurse(test_func) return self.top_level_window_recurse(test_func)
def question(self, msg, parent=None, title=None, icon=None): def question(self, msg, parent=None, title=None, icon=None, **kwargs) -> bool:
Yes, No = QMessageBox.Yes, QMessageBox.No Yes, No = QMessageBox.Yes, QMessageBox.No
return self.msg_box(icon or QMessageBox.Question, return Yes == self.msg_box(icon=icon or QMessageBox.Question,
parent, title or '', parent=parent,
msg, buttons=Yes|No, defaultButton=No) == Yes title=title or '',
text=msg,
buttons=Yes|No,
defaultButton=No,
**kwargs)
def show_warning(self, msg, parent=None, title=None, **kwargs): def show_warning(self, msg, parent=None, title=None, **kwargs):
return self.msg_box(QMessageBox.Warning, parent, return self.msg_box(QMessageBox.Warning, parent,

2
electrum/plugin.py

@ -403,7 +403,7 @@ class DeviceMgr(ThreadJob):
def unpair_xpub(self, xpub): def unpair_xpub(self, xpub):
with self.lock: with self.lock:
if not xpub in self.xpub_ids: if xpub not in self.xpub_ids:
return return
_id = self.xpub_ids.pop(xpub) _id = self.xpub_ids.pop(xpub)
self._close_client(_id) self._close_client(_id)

20
electrum/plugins/hw_wallet/plugin.py

@ -44,6 +44,7 @@ class HW_PluginBase(BasePlugin):
BasePlugin.__init__(self, parent, config, name) BasePlugin.__init__(self, parent, config, name)
self.device = self.keystore_class.device self.device = self.keystore_class.device
self.keystore_class.plugin = self self.keystore_class.plugin = self
self._ignore_outdated_fw = False
def is_enabled(self): def is_enabled(self):
return True return True
@ -124,6 +125,12 @@ class HW_PluginBase(BasePlugin):
message += '\n' + _("Make sure you install it with python3") message += '\n' + _("Make sure you install it with python3")
return message return message
def set_ignore_outdated_fw(self):
self._ignore_outdated_fw = True
def is_outdated_fw_ignored(self) -> bool:
return self._ignore_outdated_fw
def is_any_tx_output_on_change_branch(tx: Transaction): def is_any_tx_output_on_change_branch(tx: Transaction):
if not tx.output_info: if not tx.output_info:
@ -160,3 +167,16 @@ def only_hook_if_libraries_available(func):
class LibraryFoundButUnusable(Exception): class LibraryFoundButUnusable(Exception):
def __init__(self, library_version='unknown'): def __init__(self, library_version='unknown'):
self.library_version = library_version self.library_version = library_version
class OutdatedHwFirmwareException(UserFacingException):
def text_ignore_old_fw_and_continue(self) -> str:
suffix = (_("The firmware of your hardware device is too old. "
"If possible, you should upgrade it. "
"You can ignore this error and try to continue, however things are likely to break.") + "\n\n" +
_("Ignore and continue?"))
if str(self):
return str(self) + "\n\n" + suffix
else:
return suffix

20
electrum/plugins/hw_wallet/qt.py

@ -37,6 +37,8 @@ from electrum.gui.qt.util import (read_QIcon, WWLabel, OkButton, WindowModalDial
from electrum.i18n import _ from electrum.i18n import _
from electrum.logging import Logger from electrum.logging import Logger
from .plugin import OutdatedHwFirmwareException
# The trickiest thing about this handler was getting windows properly # The trickiest thing about this handler was getting windows properly
# parented on macOS. # parented on macOS.
@ -212,11 +214,27 @@ class QtPluginBase(object):
handler = self.create_handler(window) handler = self.create_handler(window)
handler.button = button handler.button = button
keystore.handler = handler keystore.handler = handler
keystore.thread = TaskThread(window, window.on_error) keystore.thread = TaskThread(window, on_error=partial(self.on_task_thread_error, window, keystore))
self.add_show_address_on_hw_device_button_for_receive_addr(wallet, keystore, window) self.add_show_address_on_hw_device_button_for_receive_addr(wallet, keystore, window)
# Trigger a pairing # Trigger a pairing
keystore.thread.add(partial(self.get_client, keystore)) keystore.thread.add(partial(self.get_client, keystore))
def on_task_thread_error(self, window, keystore, exc_info):
e = exc_info[1]
if isinstance(e, OutdatedHwFirmwareException):
if window.question(e.text_ignore_old_fw_and_continue(), title=_("Outdated device firmware")):
self.set_ignore_outdated_fw()
# will need to re-pair
devmgr = self.device_manager()
def re_pair_device():
device_id = self.choose_device(window, keystore)
devmgr.unpair_id(device_id)
self.get_client(keystore)
keystore.thread.add(re_pair_device)
return
else:
window.on_error(exc_info)
def choose_device(self, window, keystore): def choose_device(self, window, keystore):
'''This dialog box should be usable even if the user has '''This dialog box should be usable even if the user has
forgotten their PIN or it is in bootloader mode.''' forgotten their PIN or it is in bootloader mode.'''

19
electrum/plugins/trezor/clientbase.py

@ -7,6 +7,7 @@ from electrum.util import UserCancelled, UserFacingException
from electrum.keystore import bip39_normalize_passphrase from electrum.keystore import bip39_normalize_passphrase
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path
from electrum.logging import Logger from electrum.logging import Logger
from electrum.plugins.hw_wallet.plugin import OutdatedHwFirmwareException
from trezorlib.client import TrezorClient from trezorlib.client import TrezorClient
from trezorlib.exceptions import TrezorFailure, Cancelled, OutdatedFirmwareError from trezorlib.exceptions import TrezorFailure, Cancelled, OutdatedFirmwareError
@ -29,6 +30,8 @@ MESSAGES = {
class TrezorClientBase(Logger): class TrezorClientBase(Logger):
def __init__(self, transport, handler, plugin): def __init__(self, transport, handler, plugin):
if plugin.is_outdated_fw_ignored():
TrezorClient.is_outdated = lambda *args, **kwargs: False
self.client = TrezorClient(transport, ui=self) self.client = TrezorClient(transport, ui=self)
self.plugin = plugin self.plugin = plugin
self.device = plugin.device self.device = plugin.device
@ -62,15 +65,15 @@ class TrezorClientBase(Logger):
def __enter__(self): def __enter__(self):
return self return self
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, e, traceback):
self.end_flow() self.end_flow()
if exc_value is not None: if e is not None:
if issubclass(exc_type, Cancelled): if isinstance(e, Cancelled):
raise UserCancelled from exc_value raise UserCancelled from e
elif issubclass(exc_type, TrezorFailure): elif isinstance(e, TrezorFailure):
raise RuntimeError(str(exc_value)) from exc_value raise RuntimeError(str(e)) from e
elif issubclass(exc_type, OutdatedFirmwareError): elif isinstance(e, OutdatedFirmwareError):
raise UserFacingException(exc_value) from exc_value raise OutdatedHwFirmwareException(e) from e
else: else:
return False return False
return True return True

4
electrum/plugins/trezor/trezor.py

@ -15,7 +15,7 @@ from electrum.logging import get_logger
from ..hw_wallet import HW_PluginBase from ..hw_wallet import HW_PluginBase
from ..hw_wallet.plugin import (is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data, from ..hw_wallet.plugin import (is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data,
LibraryFoundButUnusable) LibraryFoundButUnusable, OutdatedHwFirmwareException)
_logger = get_logger(__name__) _logger = get_logger(__name__)
@ -275,7 +275,7 @@ class TrezorPlugin(HW_PluginBase):
msg = (_('Outdated {} firmware for device labelled {}. Please ' msg = (_('Outdated {} firmware for device labelled {}. Please '
'download the updated firmware from {}') 'download the updated firmware from {}')
.format(self.device, client.label(), self.firmware_URL)) .format(self.device, client.label(), self.firmware_URL))
raise UserFacingException(msg) raise OutdatedHwFirmwareException(msg)
# fixme: we should use: client.handler = wizard # fixme: we should use: client.handler = wizard
client.handler = self.create_handler(wizard) client.handler = self.create_handler(wizard)

Loading…
Cancel
Save