From bb512ff96f5237c44fc6680d29b766315df3d26f Mon Sep 17 00:00:00 2001 From: M1nd3r Date: Wed, 12 Mar 2025 15:35:43 +0100 Subject: [PATCH] feat(core): improve pairing screens --- core/src/apps/thp/pairing.py | 25 +++++++++- core/src/trezor/wire/thp/pairing_context.py | 48 ++++++++++++++----- .../trezorlib/transport/thp/protocol_v2.py | 2 +- tests/device_tests/thp/test_thp.py | 8 +++- 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/core/src/apps/thp/pairing.py b/core/src/apps/thp/pairing.py index cf4e017016..bb0fe0fe83 100644 --- a/core/src/apps/thp/pairing.py +++ b/core/src/apps/thp/pairing.py @@ -93,8 +93,10 @@ async def handle_pairing_request( if not ThpPairingRequest.is_type_of(message): raise UnexpectedMessage("Unexpected message") - ctx.host_name = message.host_name or "" + if not message.host_name: + raise Exception("Missing host_name.") + ctx.host_name = message.host_name await ctx.show_pairing_dialogue() assert ThpSelectMethod.MESSAGE_WIRE_TYPE is not None select_method_msg = await ctx.read( @@ -385,6 +387,27 @@ async def _handle_credential_request( if message.autoconnect is not None: autoconnect = message.autoconnect + if autoconnect: + # Cannot ask for autoconnect=True credential directly after pairing + if ctx.channel_ctx.credential is None: + raise Exception("Cannot ask for autoconnect credential after pairing!") + from storage.cache_common import CHANNEL_HOST_STATIC_PUBKEY + + from .credential_manager import validate_credential + + host_static_pubkey = ctx.channel_ctx.channel_cache.get( + CHANNEL_HOST_STATIC_PUBKEY + ) + + if not host_static_pubkey or not validate_credential( + credential=ctx.channel_ctx.credential, host_static_pubkey=host_static_pubkey + ): + raise Exception( + "Cannot ask for autoconnect credential without a valid credential!" + ) + + await ctx.show_autoconnec_credential_confirmation_screen() + trezor_static_pubkey = crypto.get_trezor_static_pubkey() credential_metadata = ThpCredentialMetadata( host_name=ctx.host_name, diff --git a/core/src/trezor/wire/thp/pairing_context.py b/core/src/trezor/wire/thp/pairing_context.py index 6748daabee..533e2b0561 100644 --- a/core/src/trezor/wire/thp/pairing_context.py +++ b/core/src/trezor/wire/thp/pairing_context.py @@ -142,15 +142,20 @@ class PairingContext(Context): raise Exception("Not allowed to set this method") self.selected_method = selected_method - async def show_pairing_dialogue(self) -> None: + async def show_pairing_dialogue(self, device_name: str | None = None) -> None: from trezor.messages import ThpPairingRequestApproved from trezor.ui.layouts.common import interact + if not device_name: + action_string = f"Allow {self.host_name} to pair with this Trezor?" + else: + action_string = ( + f"Allow {self.host_name} on {device_name} to pair with this Trezor?" + ) + result = await interact( trezorui_api.confirm_action( - title="Pairing dialogue", - action="Do you want to start pairing?", - description="Choose wisely!", + title="Before you continue", action=action_string, description=None ), br_name="pairing_request", br_code=ButtonRequestType.Other, @@ -158,16 +163,33 @@ class PairingContext(Context): if result == trezorui_api.CONFIRMED: await self.write(ThpPairingRequestApproved()) - async def show_connection_dialogue(self) -> None: + async def show_connection_dialogue(self, device_name: str | None = None) -> None: + from trezor.ui.layouts.common import interact + + if not device_name: + action_string = f"Allow {self.host_name} to pair with this Trezor?" + else: + action_string = ( + f"Allow {self.host_name} on {device_name} to pair with this Trezor?" + ) + await interact( + trezorui_api.confirm_action( + title="Connection dialogue", action=action_string, description=None + ), + br_name="connection_request", + br_code=ButtonRequestType.Other, + ) + + async def show_autoconnec_credential_confirmation_screen(self) -> None: from trezor.ui.layouts.common import interact await interact( trezorui_api.confirm_action( - title="Connection dialogue", - action="Do you want previously connected device to connect?", - description="Choose wisely! (or not)", + title="Autoconnect credential", + action=f"Do you want to pair with {self.host_name} without confirmation?", + description=None, ), - br_name="connection_request", + br_name="autoconnect_credential_request", br_code=ButtonRequestType.Other, ) @@ -192,8 +214,8 @@ class PairingContext(Context): return await interact( trezorui_api.show_simple( - title="Copy the following", - text=self._get_code_code_entry_str(), + title="One more step", + text=f"Enter this one-time security code on {self.host_name}\n{self._get_code_code_entry_str()}", button="Cancel", ), br_name=None, @@ -204,8 +226,8 @@ class PairingContext(Context): return await interact( trezorui_api.show_simple( - title="NFC Pairing", - text="Move your device close to Trezor", + title=None, + text="Keep your Trezor near your phone to complete the setup.", button="Cancel", ), br_name=None, diff --git a/python/src/trezorlib/transport/thp/protocol_v2.py b/python/src/trezorlib/transport/thp/protocol_v2.py index 8d382f5deb..8b3a2a3828 100644 --- a/python/src/trezorlib/transport/thp/protocol_v2.py +++ b/python/src/trezorlib/transport/thp/protocol_v2.py @@ -266,7 +266,7 @@ class ProtocolV2Channel(Channel): def _do_pairing(self, helper_debug: DebugLink | None): - self._send_message(messages.ThpPairingRequest()) + self._send_message(messages.ThpPairingRequest(host_name="Trezorlib")) self._read_message(messages.ButtonRequest) self._send_message(messages.ButtonAck()) diff --git a/tests/device_tests/thp/test_thp.py b/tests/device_tests/thp/test_thp.py index 61fe26acb1..602db4fcf9 100644 --- a/tests/device_tests/thp/test_thp.py +++ b/tests/device_tests/thp/test_thp.py @@ -317,11 +317,17 @@ def test_credential_phase(client: Client) -> None: protocol._send_message( ThpCredentialRequest(host_static_pubkey=host_static_pubkey, autoconnect=True) ) - # Confirmation dialog is shown. (Channel replacement is not triggered.) + # Connection confirmation dialog is shown. (Channel replacement is not triggered.) button_req = protocol._read_message(ButtonRequest) assert button_req.name == "connection_request" protocol._send_message(ButtonAck()) client.debug.press_yes() + # Autoconnect issuance confirmation dialog is shown. + button_req = protocol._read_message(ButtonRequest) + assert button_req.name == "autoconnect_credential_request" + protocol._send_message(ButtonAck()) + client.debug.press_yes() + # Autoconnect credential is received credential_response_2 = protocol._read_message(ThpCredentialResponse) assert credential_response_2.credential is not None credential_auto = credential_response_2.credential