From 7513a65f9ad4ec61b4eb447dd8907eab91f22697 Mon Sep 17 00:00:00 2001 From: matejcik Date: Wed, 29 Jan 2020 15:19:44 +0100 Subject: [PATCH] python: move passphrase-on-host logic to UI class --- python/src/trezorlib/cli/trezorctl.py | 4 +-- python/src/trezorlib/client.py | 49 +++++++++++++-------------- python/src/trezorlib/ui.py | 9 +++-- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/python/src/trezorlib/cli/trezorctl.py b/python/src/trezorlib/cli/trezorctl.py index 95b0b235c..533f13331 100755 --- a/python/src/trezorlib/cli/trezorctl.py +++ b/python/src/trezorlib/cli/trezorctl.py @@ -160,14 +160,14 @@ def cli(ctx, path, verbose, is_json, passphrase_on_host): click.echo("Using path: {}".format(path)) sys.exit(1) return TrezorClient( - transport=device, ui=ui.ClickUI(), passphrase_on_host=passphrase_on_host + transport=device, ui=ui.ClickUI(passphrase_on_host=passphrase_on_host) ) ctx.obj = get_device @cli.resultcallback() -def print_result(res, path, verbose, is_json, passphrase_on_host): +def print_result(res, is_json, **kwargs): if is_json: if isinstance(res, protobuf.MessageType): click.echo(json.dumps({res.__class__.__name__: res.__dict__})) diff --git a/python/src/trezorlib/client.py b/python/src/trezorlib/client.py index 17a979564..dcaa06050 100644 --- a/python/src/trezorlib/client.py +++ b/python/src/trezorlib/client.py @@ -23,6 +23,7 @@ from types import SimpleNamespace from mnemonic import Mnemonic from . import MINIMUM_FIRMWARE_VERSION, exceptions, messages, tools +from .messages import Capability if sys.version_info.major < 3: raise Exception("Trezorlib does not support Python 2 anymore.") @@ -32,6 +33,8 @@ LOG = logging.getLogger(__name__) VENDORS = ("bitcointrezor.com", "trezor.io") MAX_PASSPHRASE_LENGTH = 50 +PASSPHRASE_ON_DEVICE = object() + DEPRECATION_ERROR = """ Incompatible Trezor library detected. @@ -109,19 +112,15 @@ class TrezorClient: You can supply a `session_id` you might have saved in the previous session. If you do, the user might not need to enter their passphrase again. - - Set `passphrase_on_host` to True if you want to enter passphrase on host directly - instead of on Trezor. """ def __init__( - self, transport, ui=_NO_UI_OBJECT, session_id=None, passphrase_on_host=False + self, transport, ui=_NO_UI_OBJECT, session_id=None, ): LOG.info("creating client instance for device: {}".format(transport.get_path())) self.transport = transport self.ui = ui self.session_id = session_id - self.passphrase_on_host = passphrase_on_host # XXX remove when old Electrum has been cycled out. # explanation: We changed the API in 0.11 and this broke older versions @@ -136,9 +135,6 @@ class TrezorClient: self.session_counter = 0 self.init_device() - if self.features.model == "1": # TODO @matejcik: move this to the UI object - self.passphrase_on_host = True - def open(self): if self.session_counter == 0: self.transport.begin_session() @@ -187,24 +183,28 @@ class TrezorClient: return resp def _callback_passphrase(self, msg: messages.PassphraseRequest): - if self.passphrase_on_host: - on_device = False - try: - passphrase = self.ui.get_passphrase() - except exceptions.Cancelled: - self.call_raw(messages.Cancel()) - raise + available_on_device = Capability.PassphraseEntry in self.features.capabilities + try: + passphrase = self.ui.get_passphrase(available_on_device=available_on_device) + except exceptions.Cancelled: + self.call_raw(messages.Cancel()) + raise - passphrase = Mnemonic.normalize_string(passphrase) - if len(passphrase) > MAX_PASSPHRASE_LENGTH: + if passphrase is PASSPHRASE_ON_DEVICE: + if not available_on_device: self.call_raw(messages.Cancel()) - raise ValueError("Passphrase too long") - else: - on_device = True - passphrase = None + raise RuntimeError("Device is not capable of entering passphrase") + else: + return self.call_raw(messages.PassphraseAck(on_device=True)) + + # else process host-entered passphrase + passphrase = Mnemonic.normalize_string(passphrase) + if len(passphrase) > MAX_PASSPHRASE_LENGTH: + self.call_raw(messages.Cancel()) + raise ValueError("Passphrase too long") return self.call_raw( - messages.PassphraseAck(passphrase=passphrase, on_device=on_device) + messages.PassphraseAck(passphrase=passphrase, on_device=False) ) def _callback_button(self, msg): @@ -250,10 +250,7 @@ class TrezorClient: self.features.patch_version, ) self.check_firmware_version(warn_only=True) - if (self.version[0] == 1 and self.version >= (1, 9, 0)) or ( - self.version[0] == 2 and self.version >= (2, 3, 0) - ): - self.session_id = self.features.session_id + self.session_id = self.features.session_id def is_outdated(self): if self.features.bootloader_mode: diff --git a/python/src/trezorlib/ui.py b/python/src/trezorlib/ui.py index 9d486b67b..55e0e349c 100644 --- a/python/src/trezorlib/ui.py +++ b/python/src/trezorlib/ui.py @@ -20,6 +20,7 @@ import click from mnemonic import Mnemonic from . import device +from .client import PASSPHRASE_ON_DEVICE from .exceptions import Cancelled from .messages import PinMatrixRequestType, WordRequestType @@ -58,10 +59,11 @@ def prompt(*args, **kwargs): class ClickUI: - def __init__(self, always_prompt=False): + def __init__(self, always_prompt=False, passphrase_on_host=False): self.pinmatrix_shown = False self.prompt_shown = False self.always_prompt = always_prompt + self.passphrase_on_host = passphrase_on_host def button_request(self, code): if not self.prompt_shown: @@ -98,7 +100,10 @@ class ClickUI: else: return pin - def get_passphrase(self): + def get_passphrase(self, available_on_device): + if available_on_device and not self.passphrase_on_host: + return PASSPHRASE_ON_DEVICE + if os.getenv("PASSPHRASE") is not None: echo("Passphrase required. Using PASSPHRASE environment variable.") return os.getenv("PASSPHRASE")