Browse Source

hww: distinguish devices based on "soft device id" (not just labels)

fixes #5759
hard-fail-on-bad-server-string
SomberNight 5 years ago
parent
commit
9d0bb295e6
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 4
      electrum/base_wizard.py
  2. 5
      electrum/keystore.py
  3. 14
      electrum/plugin.py
  4. 12
      electrum/plugins/hw_wallet/plugin.py
  5. 3
      electrum/plugins/keepkey/clientbase.py
  6. 12
      electrum/plugins/ledger/ledger.py
  7. 3
      electrum/plugins/safe_t/clientbase.py
  8. 3
      electrum/plugins/trezor/clientbase.py

4
electrum/base_wizard.py

@ -438,6 +438,7 @@ class BaseWizard(Logger):
if not client: raise Exception("failed to find client for device id") if not client: raise Exception("failed to find client for device id")
root_fingerprint = client.request_root_fingerprint_from_device() root_fingerprint = client.request_root_fingerprint_from_device()
label = client.label() # use this as device_info.label might be outdated! label = client.label() # use this as device_info.label might be outdated!
soft_device_id = client.get_soft_device_id() # use this as device_info.device_id might be outdated!
except ScriptTypeNotSupported: except ScriptTypeNotSupported:
raise # this is handled in derivation_dialog raise # this is handled in derivation_dialog
except BaseException as e: except BaseException as e:
@ -451,6 +452,7 @@ class BaseWizard(Logger):
'root_fingerprint': root_fingerprint, 'root_fingerprint': root_fingerprint,
'xpub': xpub, 'xpub': xpub,
'label': label, 'label': label,
'soft_device_id': soft_device_id,
} }
k = hardware_keystore(d) k = hardware_keystore(d)
self.on_keystore(k) self.on_keystore(k)
@ -612,7 +614,7 @@ class BaseWizard(Logger):
if os.path.exists(path): if os.path.exists(path):
raise Exception('file already exists at path') raise Exception('file already exists at path')
if not self.pw_args: if not self.pw_args:
return return # FIXME
pw_args = self.pw_args pw_args = self.pw_args
self.pw_args = None # clean-up so that it can get GC-ed self.pw_args = None # clean-up so that it can get GC-ed
storage = WalletStorage(path) storage = WalletStorage(path)

5
electrum/keystore.py

@ -724,6 +724,7 @@ class Hardware_KeyStore(Xpub, KeyStore):
# device reconnects # device reconnects
self.xpub = d.get('xpub') self.xpub = d.get('xpub')
self.label = d.get('label') self.label = d.get('label')
self.soft_device_id = d.get('soft_device_id') # type: Optional[str]
self.handler = None # type: Optional[HardwareHandlerBase] self.handler = None # type: Optional[HardwareHandlerBase]
run_hook('init_keystore', self) run_hook('init_keystore', self)
@ -747,6 +748,7 @@ class Hardware_KeyStore(Xpub, KeyStore):
'derivation': self.get_derivation_prefix(), 'derivation': self.get_derivation_prefix(),
'root_fingerprint': self.get_root_fingerprint(), 'root_fingerprint': self.get_root_fingerprint(),
'label':self.label, 'label':self.label,
'soft_device_id': self.soft_device_id,
} }
def unpaired(self): def unpaired(self):
@ -788,6 +790,9 @@ class Hardware_KeyStore(Xpub, KeyStore):
if self.label != client.label(): if self.label != client.label():
self.label = client.label() self.label = client.label()
self.is_requesting_to_be_rewritten_to_wallet_file = True self.is_requesting_to_be_rewritten_to_wallet_file = True
if self.soft_device_id != client.get_soft_device_id():
self.soft_device_id = client.get_soft_device_id()
self.is_requesting_to_be_rewritten_to_wallet_file = True
KeyStoreWithMPK = Union[KeyStore, MasterPublicKeyMixin] # intersection really... KeyStoreWithMPK = Union[KeyStore, MasterPublicKeyMixin] # intersection really...

14
electrum/plugin.py

@ -306,6 +306,7 @@ class DeviceInfo(NamedTuple):
initialized: Optional[bool] = None initialized: Optional[bool] = None
exception: Optional[Exception] = None exception: Optional[Exception] = None
plugin_name: Optional[str] = None # manufacturer, e.g. "trezor" plugin_name: Optional[str] = None # manufacturer, e.g. "trezor"
soft_device_id: Optional[str] = None # if available, used to distinguish same-type hw devices
class HardwarePluginToScan(NamedTuple): class HardwarePluginToScan(NamedTuple):
@ -548,7 +549,8 @@ class DeviceMgr(ThreadJob):
infos.append(DeviceInfo(device=device, infos.append(DeviceInfo(device=device,
label=client.label(), label=client.label(),
initialized=client.is_initialized(), initialized=client.is_initialized(),
plugin_name=plugin.name)) plugin_name=plugin.name,
soft_device_id=client.get_soft_device_id()))
return infos return infos
@ -575,6 +577,11 @@ class DeviceMgr(ThreadJob):
devices = None devices = None
if len(infos) == 1: if len(infos) == 1:
return infos[0] return infos[0]
# select device by id
if keystore.soft_device_id:
for info in infos:
if info.soft_device_id == keystore.soft_device_id:
return info
# select device by label automatically; # select device by label automatically;
# but only if not a placeholder label and only if there is no collision # but only if not a placeholder label and only if there is no collision
device_labels = [info.label for info in infos] device_labels = [info.label for info in infos]
@ -583,7 +590,7 @@ class DeviceMgr(ThreadJob):
for info in infos: for info in infos:
if info.label == keystore.label: if info.label == keystore.label:
return info return info
# ask user to select device # ask user to select device manually
msg = _("Please select which {} device to use:").format(plugin.device) msg = _("Please select which {} device to use:").format(plugin.device)
descriptions = ["{label} ({init}, {transport})" descriptions = ["{label} ({init}, {transport})"
.format(label=info.label or _("An unnamed {}").format(info.plugin_name), .format(label=info.label or _("An unnamed {}").format(info.plugin_name),
@ -594,8 +601,9 @@ class DeviceMgr(ThreadJob):
if c is None: if c is None:
raise UserCancelled() raise UserCancelled()
info = infos[c] info = infos[c]
# save new label # save new label / soft_device_id
keystore.set_label(info.label) keystore.set_label(info.label)
keystore.soft_device_id = info.soft_device_id
wallet = handler.get_wallet() wallet = handler.get_wallet()
if wallet is not None: if wallet is not None:
wallet.save_keystore() wallet.save_keystore()

12
electrum/plugins/hw_wallet/plugin.py

@ -167,7 +167,7 @@ class HW_PluginBase(BasePlugin):
class HardwareClientBase: class HardwareClientBase:
plugin: 'HW_PluginBase' plugin: 'HW_PluginBase'
handler: Optional['HardwareHandlerBase'] handler = None # type: Optional['HardwareHandlerBase']
def is_pairable(self) -> bool: def is_pairable(self) -> bool:
raise NotImplementedError() raise NotImplementedError()
@ -191,6 +191,16 @@ class HardwareClientBase:
""" """
raise NotImplementedError() raise NotImplementedError()
def get_soft_device_id(self) -> Optional[str]:
"""An id-like string that is used to distinguish devices programmatically.
This is a long term id for the device, that does not change between reconnects.
This method should not prompt the user, i.e. no user interaction, as it is used
during USB device enumeration (called for each unpaired device).
Stored in the wallet file.
"""
# This functionality is optional. If not implemented just return None:
return None
def has_usable_connection_with_device(self) -> bool: def has_usable_connection_with_device(self) -> bool:
raise NotImplementedError() raise NotImplementedError()

3
electrum/plugins/keepkey/clientbase.py

@ -119,6 +119,9 @@ class KeepKeyClientBase(HardwareClientBase, GuiMixin, Logger):
def label(self): def label(self):
return self.features.label return self.features.label
def get_soft_device_id(self):
return self.features.device_id
def is_initialized(self): def is_initialized(self):
return self.features.initialized return self.features.initialized

12
electrum/plugins/ledger/ledger.py

@ -66,6 +66,7 @@ class Ledger_Client(HardwareClientBase):
self.dongleObject = btchip(hidDevice) self.dongleObject = btchip(hidDevice)
self.preflightDone = False self.preflightDone = False
self._is_hw1 = is_hw1 self._is_hw1 = is_hw1
self._soft_device_id = None
def is_pairable(self): def is_pairable(self):
return True return True
@ -82,6 +83,14 @@ class Ledger_Client(HardwareClientBase):
def label(self): def label(self):
return "" return ""
def get_soft_device_id(self):
if self._soft_device_id is None:
# modern ledger can provide xpub without user interaction
# (hw1 would prompt for PIN)
if not self.is_hw1():
self._soft_device_id = self.request_root_fingerprint_from_device()
return self._soft_device_id
def is_hw1(self) -> bool: def is_hw1(self) -> bool:
return self._is_hw1 return self._is_hw1
@ -176,7 +185,8 @@ class Ledger_Client(HardwareClientBase):
# Acquire the new client on the next run # Acquire the new client on the next run
else: else:
raise e raise e
if self.has_detached_pin_support(self.dongleObject) and not self.is_pin_validated(self.dongleObject) and (self.handler is not None): if self.has_detached_pin_support(self.dongleObject) and not self.is_pin_validated(self.dongleObject):
assert self.handler, "no handler for client"
remaining_attempts = self.dongleObject.getVerifyPinRemainingAttempts() remaining_attempts = self.dongleObject.getVerifyPinRemainingAttempts()
if remaining_attempts != 1: if remaining_attempts != 1:
msg = "Enter your Ledger PIN - remaining attempts : " + str(remaining_attempts) msg = "Enter your Ledger PIN - remaining attempts : " + str(remaining_attempts)

3
electrum/plugins/safe_t/clientbase.py

@ -121,6 +121,9 @@ class SafeTClientBase(HardwareClientBase, GuiMixin, Logger):
def label(self): def label(self):
return self.features.label return self.features.label
def get_soft_device_id(self):
return self.features.device_id
def is_initialized(self): def is_initialized(self):
return self.features.initialized return self.features.initialized

3
electrum/plugins/trezor/clientbase.py

@ -98,6 +98,9 @@ class TrezorClientBase(HardwareClientBase, Logger):
def label(self): def label(self):
return self.features.label return self.features.label
def get_soft_device_id(self):
return self.features.device_id
def is_initialized(self): def is_initialized(self):
return self.features.initialized return self.features.initialized

Loading…
Cancel
Save