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

fixes #5759
This commit is contained in:
SomberNight 2020-04-08 14:43:01 +02:00
parent 7dabbdd082
commit 9d0bb295e6
No known key found for this signature in database
GPG key ID: B33B5F232C6271E9
8 changed files with 50 additions and 6 deletions

View file

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

View file

@ -724,6 +724,7 @@ class Hardware_KeyStore(Xpub, KeyStore):
# device reconnects
self.xpub = d.get('xpub')
self.label = d.get('label')
self.soft_device_id = d.get('soft_device_id') # type: Optional[str]
self.handler = None # type: Optional[HardwareHandlerBase]
run_hook('init_keystore', self)
@ -747,6 +748,7 @@ class Hardware_KeyStore(Xpub, KeyStore):
'derivation': self.get_derivation_prefix(),
'root_fingerprint': self.get_root_fingerprint(),
'label':self.label,
'soft_device_id': self.soft_device_id,
}
def unpaired(self):
@ -788,6 +790,9 @@ class Hardware_KeyStore(Xpub, KeyStore):
if self.label != client.label():
self.label = client.label()
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...

View file

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

View file

@ -167,7 +167,7 @@ class HW_PluginBase(BasePlugin):
class HardwareClientBase:
plugin: 'HW_PluginBase'
handler: Optional['HardwareHandlerBase']
handler = None # type: Optional['HardwareHandlerBase']
def is_pairable(self) -> bool:
raise NotImplementedError()
@ -191,6 +191,16 @@ class HardwareClientBase:
"""
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:
raise NotImplementedError()

View file

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

View file

@ -66,6 +66,7 @@ class Ledger_Client(HardwareClientBase):
self.dongleObject = btchip(hidDevice)
self.preflightDone = False
self._is_hw1 = is_hw1
self._soft_device_id = None
def is_pairable(self):
return True
@ -82,6 +83,14 @@ class Ledger_Client(HardwareClientBase):
def label(self):
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:
return self._is_hw1
@ -176,7 +185,8 @@ class Ledger_Client(HardwareClientBase):
# Acquire the new client on the next run
else:
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()
if remaining_attempts != 1:
msg = "Enter your Ledger PIN - remaining attempts : " + str(remaining_attempts)

View file

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

View file

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