diff --git a/core/src/apps/webauthn/credential.py b/core/src/apps/webauthn/credential.py index 26ba28b44f..2265e0ea27 100644 --- a/core/src/apps/webauthn/credential.py +++ b/core/src/apps/webauthn/credential.py @@ -278,9 +278,11 @@ def app_name(self) -> str: return self.rp_id def account_name(self) -> str | None: - if self.user_name: + # Some relying parties send a blank/whitespace-only "name" (e.g. " "); + # treat those as absent and fall back to displayName, then user id. + if self.user_name and self.user_name.strip(): return self.user_name - elif self.user_display_name: + elif self.user_display_name and self.user_display_name.strip(): return self.user_display_name elif self.user_id: return hexlify(self.user_id).decode() diff --git a/core/src/apps/webauthn/fido2.py b/core/src/apps/webauthn/fido2.py index bd13aaf1ff..69d98381b3 100644 --- a/core/src/apps/webauthn/fido2.py +++ b/core/src/apps/webauthn/fido2.py @@ -13,7 +13,11 @@ from trezor.lvglui.i18n import gettext as _, keys as i18n_keys from trezor.ui.components.common.confirm import Pageable from trezor.ui.components.common.webauthn import ConfirmInfo -from trezor.ui.layouts.lvgl.webauthn import confirm_webauthn, confirm_webauthn_reset +from trezor.ui.layouts.lvgl.webauthn import ( + confirm_webauthn, + confirm_webauthn_reset, + select_webauthn_account, +) from apps.base import device_is_unlocked, set_homescreen from apps.common import cbor @@ -176,7 +180,7 @@ _FIDO_ATT_PRIV_KEY = b"q&\xac+\xf6D\xdca\x86\xad\x83\xef\x1f\xcd\xf1*W\xb5\xcf\xa2\x00\x0b\x8a\xd0'\xe9V\xe8T\xc5\n\x8b" _FIDO_ATT_CERT = b"0\x82\x01\xcd0\x82\x01s\xa0\x03\x02\x01\x02\x02\x04\x03E`\xc40\n\x06\x08*\x86H\xce=\x04\x03\x020.1,0*\x06\x03U\x04\x03\x0c#Trezor FIDO Root CA Serial 841513560 \x17\r200406100417Z\x18\x0f20500406100417Z0x1\x0b0\t\x06\x03U\x04\x06\x13\x02CZ1\x1c0\x1a\x06\x03U\x04\n\x0c\x13SatoshiLabs, s.r.o.1\"0 \x06\x03U\x04\x0b\x0c\x19Authenticator Attestation1'0%\x06\x03U\x04\x03\x0c\x1eTrezor FIDO EE Serial 548784040Y0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07\x03B\x00\x04\xd9\x18\xbd\xfa\x8aT\xac\x92\xe9\r\xa9\x1f\xcaz\xa2dT\xc0\xd1s61M\xde\x83\xa5K\x86\xb5\xdfN\xf0Re\x9a\x1do\xfc\xb7F\x7f\x1a\xcd\xdb\x8a3\x08\x0b^\xed\x91\x89\x13\xf4C\xa5&\x1b\xc7{h`o\xc1\xa33010!\x06\x0b+\x06\x01\x04\x01\x82\xe5\x1c\x01\x01\x04\x04\x12\x04\x10\xd6\xd0\xbd\xc3b\xee\xc4\xdb\xde\x8dzenJD\x870\x0c\x06\x03U\x1d\x13\x01\x01\xff\x04\x020\x000\n\x06\x08*\x86H\xce=\x04\x03\x02\x03H\x000E\x02 \x0b\xce\xc4R\xc3\n\x11'\xe5\xd5\xf5\xfc\xf5\xd6Wy\x11+\xe50\xad\x9d-TXJ\xbeE\x86\xda\x93\xc6\x02!\x00\xaf\xca=\xcf\xd8A\xb0\xadz\x9e$}\x0ff\xf4L,\x83\xf9T\xab\x95O\x896\xc15\x08\x7fX\xf1\x95" else: - _FIDO_ATT_CERT = b"\x30\x82\x02\x65\x30\x82\x02\x0C\xA0\x03\x02\x01\x02\x02\x08\x2F\x1F\xAB\x58\x0B\xEB\xE5\xF0\x30\x0A\x06\x08\x2A\x86\x48\xCE\x3D\x04\x03\x02\x30\x81\x97\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x10\x30\x0E\x06\x03\x55\x04\x08\x13\x07\x42\x45\x49\x4A\x49\x4E\x47\x31\x10\x30\x0E\x06\x03\x55\x04\x07\x13\x07\x48\x41\x49\x44\x49\x41\x4E\x31\x1F\x30\x1D\x06\x03\x55\x04\x0A\x13\x16\x4F\x4E\x45\x4B\x45\x59\x20\x47\x4C\x4F\x42\x41\x4C\x20\x43\x4F\x2E\x2C\x20\x4C\x54\x44\x31\x0F\x30\x0D\x06\x03\x55\x04\x0B\x13\x06\x4F\x4E\x45\x4B\x45\x59\x31\x14\x30\x12\x06\x03\x55\x04\x03\x13\x0B\x4F\x4E\x45\x4B\x45\x59\x20\x52\x4F\x4F\x54\x31\x1C\x30\x1A\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01\x16\x0D\x64\x65\x76\x40\x6F\x6E\x65\x6B\x65\x79\x2E\x73\x6F\x30\x1E\x17\x0D\x32\x34\x31\x30\x31\x38\x30\x32\x30\x37\x30\x30\x5A\x17\x0D\x32\x39\x31\x30\x31\x38\x30\x32\x30\x37\x30\x30\x5A\x30\x81\xAA\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x10\x30\x0E\x06\x03\x55\x04\x08\x13\x07\x42\x45\x49\x4A\x49\x4E\x47\x31\x10\x30\x0E\x06\x03\x55\x04\x07\x13\x07\x48\x41\x49\x44\x49\x41\x4E\x31\x1F\x30\x1D\x06\x03\x55\x04\x0A\x13\x16\x4F\x4E\x45\x4B\x45\x59\x20\x47\x4C\x4F\x42\x41\x4C\x20\x43\x4F\x2E\x2C\x20\x4C\x54\x44\x31\x22\x30\x20\x06\x03\x55\x04\x0B\x13\x19\x41\x75\x74\x68\x65\x6E\x74\x69\x63\x61\x74\x6F\x72\x20\x41\x74\x74\x65\x73\x74\x61\x74\x69\x6F\x6E\x31\x14\x30\x12\x06\x03\x55\x04\x03\x13\x0B\x4F\x4E\x45\x4B\x45\x59\x20\x46\x49\x44\x4F\x31\x1C\x30\x1A\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01\x16\x0D\x64\x65\x76\x40\x6F\x6E\x65\x6B\x65\x79\x2E\x73\x6F\x30\x59\x30\x13\x06\x07\x2A\x86\x48\xCE\x3D\x02\x01\x06\x08\x2A\x86\x48\xCE\x3D\x03\x01\x07\x03\x42\x00\x04\x20\xC4\xC2\xCA\x28\x36\x66\xB2\xD7\xA0\x7C\x25\xB7\x2C\x5F\xC3\xAC\xFE\xB4\x9C\x64\xB0\x27\xC1\x84\xA3\xEA\x10\xE8\xD0\x3D\x48\xA4\xA4\x12\x6C\x3D\xBC\xC6\x1F\x9F\x54\xDA\xB5\xDE\x30\x85\xB7\x30\x9F\x28\x2A\xC7\x63\xAF\x6C\x0B\xF2\xFA\xA2\x33\x88\x0F\x75\xA3\x2D\x30\x2B\x30\x09\x06\x03\x55\x1D\x13\x04\x02\x30\x00\x30\x1E\x06\x09\x60\x86\x48\x01\x86\xF8\x42\x01\x0D\x04\x11\x16\x0F\x78\x63\x61\x20\x63\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x30\x0A\x06\x08\x2A\x86\x48\xCE\x3D\x04\x03\x02\x03\x47\x00\x30\x44\x02\x20\x2F\x73\x6A\x80\xBC\x4F\x38\x0D\xDE\x21\xC1\x35\x40\x59\x09\x8E\x4C\x81\x9D\x3E\xA9\x6A\x51\x2F\xB3\x54\xEE\xEF\x5B\x84\xA5\xF9\x02\x20\x04\xD8\x37\x35\x88\x76\xED\x71\x02\x84\x82\x6E\x26\x3A\xB8\x8F\x82\xA4\xF8\xD4\x2E\x15\x94\xB6\xE2\x7E\xDD\xC3\x7D\xE1\xA5\x63" + _FIDO_ATT_CERT = b"\x30\x82\x02\x7C\x30\x82\x02\x21\xA0\x03\x02\x01\x02\x02\x08\x2F\x1F\xAB\x58\x0B\xEB\xE5\xF0\x30\x0A\x06\x08\x2A\x86\x48\xCE\x3D\x04\x03\x02\x30\x81\x97\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x10\x30\x0E\x06\x03\x55\x04\x08\x13\x07\x42\x45\x49\x4A\x49\x4E\x47\x31\x10\x30\x0E\x06\x03\x55\x04\x07\x13\x07\x48\x41\x49\x44\x49\x41\x4E\x31\x1F\x30\x1D\x06\x03\x55\x04\x0A\x13\x16\x4F\x4E\x45\x4B\x45\x59\x20\x47\x4C\x4F\x42\x41\x4C\x20\x43\x4F\x2E\x2C\x20\x4C\x54\x44\x31\x0F\x30\x0D\x06\x03\x55\x04\x0B\x13\x06\x4F\x4E\x45\x4B\x45\x59\x31\x14\x30\x12\x06\x03\x55\x04\x03\x13\x0B\x4F\x4E\x45\x4B\x45\x59\x20\x52\x4F\x4F\x54\x31\x1C\x30\x1A\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01\x16\x0D\x64\x65\x76\x40\x6F\x6E\x65\x6B\x65\x79\x2E\x73\x6F\x30\x1E\x17\x0D\x32\x34\x31\x30\x31\x38\x30\x32\x30\x37\x30\x30\x5A\x17\x0D\x34\x34\x31\x30\x31\x38\x30\x32\x30\x37\x30\x30\x5A\x30\x81\xAA\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x10\x30\x0E\x06\x03\x55\x04\x08\x13\x07\x42\x45\x49\x4A\x49\x4E\x47\x31\x10\x30\x0E\x06\x03\x55\x04\x07\x13\x07\x48\x41\x49\x44\x49\x41\x4E\x31\x1F\x30\x1D\x06\x03\x55\x04\x0A\x13\x16\x4F\x4E\x45\x4B\x45\x59\x20\x47\x4C\x4F\x42\x41\x4C\x20\x43\x4F\x2E\x2C\x20\x4C\x54\x44\x31\x22\x30\x20\x06\x03\x55\x04\x0B\x13\x19\x41\x75\x74\x68\x65\x6E\x74\x69\x63\x61\x74\x6F\x72\x20\x41\x74\x74\x65\x73\x74\x61\x74\x69\x6F\x6E\x31\x14\x30\x12\x06\x03\x55\x04\x03\x13\x0B\x4F\x4E\x45\x4B\x45\x59\x20\x46\x49\x44\x4F\x31\x1C\x30\x1A\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01\x16\x0D\x64\x65\x76\x40\x6F\x6E\x65\x6B\x65\x79\x2E\x73\x6F\x30\x59\x30\x13\x06\x07\x2A\x86\x48\xCE\x3D\x02\x01\x06\x08\x2A\x86\x48\xCE\x3D\x03\x01\x07\x03\x42\x00\x04\x20\xC4\xC2\xCA\x28\x36\x66\xB2\xD7\xA0\x7C\x25\xB7\x2C\x5F\xC3\xAC\xFE\xB4\x9C\x64\xB0\x27\xC1\x84\xA3\xEA\x10\xE8\xD0\x3D\x48\xA4\xA4\x12\x6C\x3D\xBC\xC6\x1F\x9F\x54\xDA\xB5\xDE\x30\x85\xB7\x30\x9F\x28\x2A\xC7\x63\xAF\x6C\x0B\xF2\xFA\xA2\x33\x88\x0F\x75\xA3\x42\x30\x40\x30\x09\x06\x03\x55\x1D\x13\x04\x02\x30\x00\x30\x13\x06\x0B\x2B\x06\x01\x04\x01\x82\xE5\x1C\x02\x01\x01\x04\x04\x03\x02\x05\x60\x30\x1E\x06\x09\x60\x86\x48\x01\x86\xF8\x42\x01\x0D\x04\x11\x16\x0F\x78\x63\x61\x20\x63\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x30\x0A\x06\x08\x2A\x86\x48\xCE\x3D\x04\x03\x02\x03\x49\x00\x30\x46\x02\x21\x00\xF8\xCD\xE4\x2F\x83\xBB\x8A\x97\xF4\x58\x64\x8F\x49\x46\x08\x31\x0F\x09\xBB\xB3\xBF\x54\x83\xFF\x27\x07\x25\xEB\x3D\xC4\x01\x08\x02\x21\x00\xF9\xA2\x7C\x63\x94\x2B\xB6\x66\x46\xC6\x8A\x87\x5E\x39\x23\x11\xEC\x39\x83\x10\xDD\x6F\x06\x74\x22\x9F\x26\x9D\x11\x04\x3B\x17" _BOGUS_APPID_CHROME = b"A" * 32 _BOGUS_APPID_FIREFOX = b"\0" * 32 _BOGUS_APPIDS = (_BOGUS_APPID_CHROME, _BOGUS_APPID_FIREFOX) @@ -212,9 +216,6 @@ _ALLOW_RESIDENT_CREDENTIALS = storage.device.get_se01_version() >= "1.1.5" _ALLOW_WINK = False -# The default attestation type to use in MakeCredential responses. If false, then basic attestation will be used by default. -_DEFAULT_USE_SELF_ATTESTATION = True - # The default value of the use_sign_count flag for newly created credentials. _DEFAULT_USE_SIGN_COUNT = True @@ -620,6 +621,11 @@ async def handle_reports(usb_face: io.HID, spi_iface: io.SPI) -> None: if req is None: continue + if dialog_mgr.is_busy() and dialog_mgr.state is not None: + active_iface = dialog_mgr.state.iface + if dialog_mgr.iface is not active_iface: + dialog_mgr.set_iface(active_iface) + continue if dialog_mgr.is_busy() and ( req.cid not in ( @@ -860,8 +866,9 @@ async def on_timeout(self) -> None: await self.on_decline() async def on_cancel(self) -> None: - cmd = cbor_error(self.cid, _ERR_KEEPALIVE_CANCEL) - await send_cmd(cmd, self.iface) + # The CTAP2 cancel response (_ERR_KEEPALIVE_CANCEL) is sent synchronously + # from the _CMD_CANCEL dispatch path, because this coroutine is torn down + # by DialogManager.reset() and can no longer await a send here. self.finished = True @@ -1021,10 +1028,29 @@ def app_name(self) -> str: def account_name(self) -> str | None: return self._creds[self.page()].account_name() + def _single_line_label(self, label: str) -> str: + return label.replace("\r\n", " ").replace("\n", " ").replace("\r", " ") + + def account_names(self) -> list[str]: + return [ + self._single_line_label( + cred.account_name() or f"{cred.app_name()} #{i + 1}" + ) + for i, cred in enumerate(self._creds) + ] + def page_count(self) -> int: return len(self._creds) + def select_account(self, index: int) -> None: + self._page = min(max(index, 0), self.page_count() - 1) + async def confirm_dialog(self) -> bool: + if self.page_count() > 1: + selected = await select_webauthn_account(None, self) + if selected is None: + return False + self.select_account(selected) if not await confirm_webauthn(None, self): return False if self._user_verification: @@ -1225,6 +1251,39 @@ async def dialog_workflow(self) -> None: await self.state.on_decline() +def cmd_cancel(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: + if __debug__: + log.debug(__name__, "_CMD_CANCEL") + state = dialog_mgr.state + # Was a FIDO2/CTAP2 request pending? Only those get a CBOR cancel response. + is_fido2 = isinstance(state, Fido2State) + pending_cid = state.cid if is_fido2 else req.cid + pending_iface = state.iface if is_fido2 else dialog_mgr.iface + if is_fido2 and dialog_mgr.iface is not pending_iface: + dialog_mgr.set_iface(pending_iface) + return None + # Dismiss the on-screen overlay. lvgl windows are retained and are not removed + # when reset() closes the workflow coroutine, so destroy them explicitly here, + # otherwise the confirmation / PIN page stays up after the host cancels. + if state is not None: + # The register / authenticate confirmation window. + if isinstance(state, ConfirmInfo) and state.screen is not None: + state.screen.destroy() + state.screen = None + # The user-verification PIN window shown during verify_user(), if any. + from trezor.lvglui.scrs.pinscreen import InputPin + + pin_wind = InputPin.get_window_if_visible() + if pin_wind is not None: + pin_wind.destroy() + dialog_mgr.result = _RESULT_CANCEL + dialog_mgr.reset() + if is_fido2: + dialog_mgr.set_iface(pending_iface) + return cbor_error(pending_cid, _ERR_KEEPALIVE_CANCEL) + return None + + def dispatch_cmd_hid(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: if req.cmd == _CMD_MSG: try: @@ -1304,11 +1363,7 @@ def dispatch_cmd_hid(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: return cbor_error(req.cid, _ERR_INVALID_CMD) elif req.cmd == _CMD_CANCEL: - if __debug__: - log.debug(__name__, "_CMD_CANCEL") - dialog_mgr.result = _RESULT_CANCEL - dialog_mgr.reset() - return None + return cmd_cancel(req, dialog_mgr) else: if __debug__: log.warning(__name__, "_ERR_INVALID_CMD: %d", req.cmd) @@ -1394,11 +1449,7 @@ def dispatch_cmd_ble(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: log.debug(__name__, "_CMD_WINK") return cmd_wink(req) elif req.cmd == _CMD_CANCEL: - if __debug__: - log.debug(__name__, "_CMD_CANCEL") - dialog_mgr.result = _RESULT_CANCEL - dialog_mgr.reset() - return None + return cmd_cancel(req, dialog_mgr) else: if __debug__: log.warning(__name__, "_ERR_INVALID_CMD: %d", req.cmd) @@ -1827,16 +1878,6 @@ def cbor_make_credential_process(req: Cmd, dialog_mgr: DialogManager) -> State | ) -def use_self_attestation(rp_id_hash: bytes) -> bool: - from . import knownapps - - app = knownapps.by_rp_id_hash(rp_id_hash) - if app is not None and app.use_self_attestation is not None: - return app.use_self_attestation - else: - return _DEFAULT_USE_SELF_ATTESTATION - - def cbor_make_credential_sign( client_data_hash: bytes, cred: Fido2Credential, user_verification: bool ) -> bytes: @@ -1864,16 +1905,12 @@ def cbor_make_credential_sign( + extensions ) - if use_self_attestation(cred.rp_id_hash): - sig = cred.sign((authenticator_data, client_data_hash)) - attestation_statement = {"alg": cred.algorithm, "sig": sig} - else: - sig = basic_attestation_sign((authenticator_data, client_data_hash)) - attestation_statement = { - "alg": common.COSE_ALG_ES256, - "sig": sig, - "x5c": [_FIDO_ATT_CERT], - } + sig = basic_attestation_sign((authenticator_data, client_data_hash)) + attestation_statement = { + "alg": common.COSE_ALG_ES256, + "sig": sig, + "x5c": [_FIDO_ATT_CERT], + } # Encode the authenticatorMakeCredential response data. return cbor.encode( diff --git a/core/src/apps/webauthn/knownapps.py b/core/src/apps/webauthn/knownapps.py index 234115fc01..056164e46a 100644 --- a/core/src/apps/webauthn/knownapps.py +++ b/core/src/apps/webauthn/knownapps.py @@ -9,12 +9,10 @@ def __init__( label: str, icon: str | None, use_sign_count: bool | None, - use_self_attestation: bool | None, ) -> None: self.label = label self.icon = icon self.use_sign_count = use_sign_count - self.use_self_attestation = use_self_attestation # fmt: off @@ -25,7 +23,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="aws.amazon.com", icon="A:/res/icon-aws.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xc3\x40\x8c\x04\x47\x88\xae\xa5\xb3\xdf\x30\x89\x52\xfd\x8c\xa3\xc7\x0e\x21\xfe\xf4\xf6\xc1\xc2\x37\x4c\xaa\x1d\xf9\xb2\x8d\xdd": # WebAuthn key for Binance @@ -33,7 +30,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="www.binance.com", icon="A:/res/icon-binance.png", use_sign_count=False, - use_self_attestation=True, ) if rp_id_hash == b"\x20\xf6\x61\xb1\x94\x0c\x34\x70\xac\x54\xfa\x2e\xb4\x99\x90\xfd\x33\xb5\x6d\xe8\xde\x60\x18\x70\xff\x02\xa8\x06\x0f\x3b\x7c\x58": # WebAuthn key for Binance @@ -41,7 +37,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="binance.com", icon="A:/res/icon-binance.png", use_sign_count=False, - use_self_attestation=True, ) if rp_id_hash == b"\x12\x74\x3b\x92\x12\x97\xb7\x7f\x11\x35\xe4\x1f\xde\xdd\x4a\x84\x6a\xfe\x82\xe1\xf3\x69\x32\xa9\x91\x2f\x3b\x0d\x8d\xfb\x7d\x0e": # U2F key for Bitbucket @@ -49,7 +44,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="bitbucket.org", icon="A:/res/icon-bitbucket.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x30\x2f\xd5\xb4\x49\x2a\x07\xb9\xfe\xbb\x30\xe7\x32\x69\xec\xa5\x01\x20\x5c\xcf\xe0\xc2\x0b\xf7\xb4\x72\xfa\x2d\x31\xe2\x1e\x63": # U2F key for Bitfinex @@ -57,7 +51,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="www.bitfinex.com", icon="A:/res/icon-bitfinex.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xa3\x4d\x30\x9f\xfa\x28\xc1\x24\x14\xb8\xba\x6c\x07\xee\x1e\xfa\xe1\xa8\x5e\x8a\x04\x61\x48\x59\xa6\x7c\x04\x93\xb6\x95\x61\x90": # U2F key for Bitwarden @@ -65,7 +58,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="vault.bitwarden.com", icon="A:/res/icon-bitwarden.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x19\x81\x5c\xb9\xa5\xfb\x25\xd8\x05\xde\xbd\x7b\x32\x53\x7e\xd5\x78\x63\x9b\x3e\xd1\x08\xec\x7c\x5b\xb9\xe8\xf0\xdf\xb1\x68\x73": # WebAuthn key for Cloudflare @@ -73,7 +65,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="dash.cloudflare.com", icon="A:/res/icon-cloudflare.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xe2\x7d\x61\xb4\xe9\x9d\xe0\xed\x98\x16\x3c\xb3\x8b\x7a\xf9\x33\xc6\x66\x5e\x55\x09\xe8\x49\x08\x37\x05\x58\x13\x77\x8e\x23\x6a": # WebAuthn key for Coinbase @@ -81,7 +72,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="coinbase.com", icon="A:/res/icon-coinbase.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x68\x20\x19\x15\xd7\x4c\xb4\x2a\xf5\xb3\xcc\x5c\x95\xb9\x55\x3e\x3e\x3a\x83\xb4\xd2\xa9\x3b\x45\xfb\xad\xaa\x84\x69\xff\x8e\x6e": # U2F key for Dashlane @@ -89,7 +79,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="www.dashlane.com", icon="A:/res/icon-dashlane.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xc5\x0f\x8a\x7b\x70\x8e\x92\xf8\x2e\x7a\x50\xe2\xbd\xc5\x5d\x8f\xd9\x1a\x22\xfe\x6b\x29\xc0\xcd\xf7\x80\x55\x30\x84\x2a\xf5\x81": # U2F key for Dropbox @@ -97,7 +86,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="www.dropbox.com", icon="A:/res/icon-dropbox.png", use_sign_count=False, - use_self_attestation=None, ) if rp_id_hash == b"\x82\xf4\xa8\xc9\x5f\xec\x94\xb2\x6b\xaf\x9e\x37\x25\x0e\x95\x63\xd9\xa3\x66\xc7\xbe\x26\x1c\xa4\xdd\x01\x01\xf4\xd5\xef\xcb\x83": # WebAuthn key for Dropbox @@ -105,7 +93,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="www.dropbox.com", icon="A:/res/icon-dropbox.png", use_sign_count=False, - use_self_attestation=None, ) if rp_id_hash == b"\xf3\xe2\x04\x2f\x94\x60\x7d\xa0\xa9\xc1\xf3\xb9\x5e\x0d\x2f\x2b\xb2\xe0\x69\xc5\xbb\x4f\xa7\x64\xaf\xfa\x64\x7d\x84\x7b\x7e\xd6": # U2F key for Duo @@ -113,7 +100,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="duosecurity.com", icon="A:/res/icon-duo.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x31\x19\x33\x28\xf8\xe2\x1d\xfb\x6c\x99\xf3\x22\xd2\x2d\x7b\x0b\x50\x87\x78\xe6\x4f\xfb\xba\x86\xe5\x22\x93\x37\x90\x31\xb8\x74": # WebAuthn key for Facebook @@ -121,7 +107,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="facebook.com", icon="A:/res/icon-facebook.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x69\x66\xab\xe3\x67\x4e\xa2\xf5\x30\x79\xeb\x71\x01\x97\x84\x8c\x9b\xe6\xf3\x63\x99\x2f\xd0\x29\xe9\x89\x84\x47\xcb\x9f\x00\x84": # U2F key for FastMail @@ -129,7 +114,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="www.fastmail.com", icon="A:/res/icon-fastmail.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x3f\xcb\x82\x82\xb8\x46\x76\xeb\xee\x71\x40\xe3\x9e\xca\xe1\x6e\xeb\x19\x90\x64\xc7\xc7\xe4\x43\x2e\x28\xc9\xb5\x7e\x4b\x60\x39": # WebAuthn key for FastMail @@ -137,7 +121,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="fastmail.com", icon="A:/res/icon-fastmail.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x9d\x61\x44\x2f\x5c\xe1\x33\xbd\x46\x54\x4f\xc4\x2f\x0a\x6d\x54\xc0\xde\xb8\x88\x40\xca\xc2\xb6\xae\xfa\x65\x14\xf8\x93\x49\xe9": # U2F key for Fedora @@ -145,7 +128,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="fedoraproject.org", icon="A:/res/icon-fedora.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xa4\xe2\x2d\xca\xfe\xa7\xe9\x0e\x12\x89\x50\x11\x39\x89\xfc\x45\x97\x8d\xc9\xfb\x87\x76\x75\x60\x51\x6c\x1c\x69\xdf\xdf\xd1\x96": # U2F key for Gandi @@ -153,7 +135,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="gandi.net", icon="A:/res/icon-gandi.png", use_sign_count=False, - use_self_attestation=None, ) if rp_id_hash == b"\x54\xce\x65\x1e\xd7\x15\xb4\xaa\xa7\x55\xee\xce\xbd\x4e\xa0\x95\x08\x15\xb3\x34\xbd\x07\xd1\x09\x89\x3e\x96\x30\x18\xcd\xdb\xd9": # WebAuthn key for Gandi @@ -161,7 +142,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="gandi.net", icon="A:/res/icon-gandi.png", use_sign_count=False, - use_self_attestation=None, ) if rp_id_hash == b"\x86\x06\xc1\x68\xe5\x1f\xc1\x31\xe5\x46\xad\x57\xa1\x9f\x32\x97\xb1\x1e\x0e\x5c\xe8\x3e\x8e\x89\x31\xb2\x85\x08\x11\xcf\xa8\x81": # WebAuthn key for Gemini @@ -169,7 +149,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="gemini.com", icon="A:/res/icon-gemini.png", use_sign_count=False, - use_self_attestation=True, ) if rp_id_hash == b"\x70\x61\x7d\xfe\xd0\x65\x86\x3a\xf4\x7c\x15\x55\x6c\x91\x79\x88\x80\x82\x8c\xc4\x07\xfd\xf7\x0a\xe8\x50\x11\x56\x94\x65\xa0\x75": # U2F key for GitHub @@ -177,7 +156,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="github.com", icon="A:/res/icon-github.png", use_sign_count=True, - use_self_attestation=None, ) if rp_id_hash == b"\x3a\xeb\x00\x24\x60\x38\x1c\x6f\x25\x8e\x83\x95\xd3\x02\x6f\x57\x1f\x0d\x9a\x76\x48\x8d\xcd\x83\x76\x39\xb1\x3a\xed\x31\x65\x60": # WebAuthn key for GitHub @@ -185,7 +163,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="github.com", icon="A:/res/icon-github.png", use_sign_count=True, - use_self_attestation=None, ) if rp_id_hash == b"\xe7\xbe\x96\xa5\x1b\xd0\x19\x2a\x72\x84\x0d\x2e\x59\x09\xf7\x2b\xa8\x2a\x2f\xe9\x3f\xaa\x62\x4f\x03\x39\x6b\x30\xe4\x94\xc8\x04": # U2F key for GitLab @@ -193,7 +170,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="gitlab.com", icon="A:/res/icon-gitlab.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xa5\x46\x72\xb2\x22\xc4\xcf\x95\xe1\x51\xed\x8d\x4d\x3c\x76\x7a\x6c\xc3\x49\x43\x59\x43\x79\x4e\x88\x4f\x3d\x02\x3a\x82\x29\xfd": # U2F key for Google @@ -201,7 +177,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="google.com", icon="A:/res/icon-google.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xd4\xc9\xd9\x02\x73\x26\x27\x1a\x89\xce\x51\xfc\xaf\x32\x8e\xd6\x73\xf1\x7b\xe3\x34\x69\xff\x97\x9e\x8a\xb8\xdd\x50\x1e\x66\x4f": # WebAuthn key for Google @@ -209,7 +184,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="google.com", icon="A:/res/icon-google.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x9c\x2e\x02\xc4\xff\xf7\x76\x62\xe1\xde\x80\x3b\x43\x9e\x11\xc0\xdd\x0c\x3f\x66\x42\xce\xc4\xe6\x84\xd6\x49\x87\x0a\xd1\xbb\x59": # WebAuthn key for Invity @@ -217,7 +191,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="invity.io", icon="A:/res/icon-invity.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x53\xa1\x5b\xa4\x2a\x7c\x03\x25\xb8\xdb\xee\x28\x96\x34\xa4\x8f\x58\xae\xa3\x24\x66\x45\xd5\xff\x41\x8f\x9b\xb8\x81\x98\x85\xa9": # U2F key for Keeper @@ -225,7 +198,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="keepersecurity.com", icon="A:/res/icon-keeper.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xd6\x5f\x00\x5e\xf4\xde\xa9\x32\x0c\x99\x73\x05\x3c\x95\xff\x60\x20\x11\x5d\x5f\xec\x1b\x7f\xee\x41\xa5\x78\xe1\x8d\xf9\xca\x8c": # U2F key for Keeper @@ -233,7 +205,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="keepersecurity.eu", icon="A:/res/icon-keeper.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x3f\x37\x50\x85\x33\x2c\xac\x4f\xad\xf9\xe5\xdd\x28\xcd\x54\x69\x8f\xab\x98\x4b\x75\xd9\xc3\x6a\x07\x2c\xb1\x60\x77\x3f\x91\x52": # WebAuthn key for Kraken @@ -241,7 +212,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="kraken.com", icon="A:/res/icon-kraken.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xf8\x3f\xc3\xa1\xb2\x89\xa0\xde\xc5\xc1\xc8\xaa\x07\xe9\xb5\xdd\x9c\xbb\x76\xf6\xb2\xf5\x60\x60\x17\x66\x72\x68\xe5\xb9\xc4\x5e": # WebAuthn key for login.gov @@ -249,7 +219,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="secure.login.gov", icon="A:/res/icon-login.gov.png", use_sign_count=False, - use_self_attestation=None, ) if rp_id_hash == b"\x35\x6c\x9e\xd4\xa0\x93\x21\xb9\x69\x5f\x1e\xaf\x91\x82\x03\xf1\xb5\x5f\x68\x9d\xa6\x1f\xbc\x96\x18\x4c\x15\x7d\xda\x68\x0c\x81": # WebAuthn key for Microsoft @@ -257,7 +226,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="login.microsoft.com", icon="A:/res/icon-microsoft.png", use_sign_count=False, - use_self_attestation=False, ) if rp_id_hash == b"\xab\x2d\xaf\x07\x43\xde\x78\x2a\x70\x18\x9a\x0f\x5e\xfc\x30\x90\x2f\x92\x5b\x9f\x9a\x18\xc5\xd7\x14\x1b\x7b\x12\xf8\xa0\x10\x0c": # WebAuthn key for mojeID @@ -265,7 +233,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="mojeid.cz", icon="A:/res/icon-mojeid.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x85\x71\x01\x36\x1b\x20\xa9\x54\x4c\xdb\x9b\xef\x65\x85\x8b\x6b\xac\x70\x13\x55\x0d\x8f\x84\xf7\xef\xee\x25\x2b\x96\xfa\x7c\x1e": # WebAuthn key for Namecheap @@ -273,7 +240,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="www.namecheap.com", icon="A:/res/icon-namecheap.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xa2\x59\xc2\xb5\x0d\x78\x50\x80\xf8\xbe\x7f\x17\xca\xf8\x15\x6c\x8d\x18\xf4\x7e\xdb\xaf\x51\x8f\xa6\xf5\x9f\x29\xcd\x28\xf1\x5c": # WebAuthn key for Proton @@ -281,7 +247,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="proton.me", icon="A:/res/icon-proton.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x08\xb2\xa3\xd4\x19\x39\xaa\x31\x66\x84\x93\xcb\x36\xcd\xcc\x4f\x16\xc4\xd9\xb4\xc8\x23\x8b\x73\xc2\xf6\x72\xc0\x33\x00\x71\x97": # U2F key for Slush Pool @@ -289,7 +254,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="slushpool.com", icon="A:/res/icon-slushpool.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x38\x80\x4f\x2e\xff\x74\xf2\x28\xb7\x41\x51\xc2\x01\xaa\x82\xe7\xe8\xee\xfc\xac\xfe\xcf\x23\xfa\x14\x6b\x13\xa3\x76\x66\x31\x4f": # U2F key for Slush Pool @@ -297,7 +261,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="slushpool.com", icon="A:/res/icon-slushpool.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x2a\xc6\xad\x09\xa6\xd0\x77\x2c\x44\xda\x73\xa6\x07\x2f\x9d\x24\x0f\xc6\x85\x4a\x70\xd7\x9c\x10\x24\xff\x7c\x75\x59\x59\x32\x92": # U2F key for Stripe @@ -305,7 +268,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="stripe.com", icon="A:/res/icon-stripe.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xfa\xbe\xec\xe3\x98\x2f\xad\x9d\xdc\xc9\x8f\x91\xbd\x2e\x75\xaf\xc7\xd1\xf4\xca\x54\x49\x29\xb2\xd0\xd0\x42\x12\xdf\xfa\x30\xfa": # U2F key for Tutanota @@ -313,7 +275,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="tutanota.com", icon="A:/res/icon-tutanota.png", use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x1b\x3c\x16\xdd\x2f\x7c\x46\xe2\xb4\xc2\x89\xdc\x16\x74\x6b\xcc\x60\xdf\xcf\x0f\xb8\x18\xe1\x32\x15\x52\x6e\x14\x08\xe7\xf4\x68": # U2F key for u2f.bin.coffee @@ -321,7 +282,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="u2f.bin.coffee", icon=None, use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xa6\x42\xd2\x1b\x7c\x6d\x55\xe1\xce\x23\xc5\x39\x98\x28\xd2\xc7\x49\xbf\x6a\x6e\xf2\xfe\x03\xcc\x9e\x10\xcd\xf4\xed\x53\x08\x8b": # WebAuthn key for webauthn.bin.coffee @@ -329,7 +289,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="webauthn.bin.coffee", icon=None, use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\x74\xa6\xea\x92\x13\xc9\x9c\x2f\x74\xb2\x24\x92\xb3\x20\xcf\x40\x26\x2a\x94\xc1\xa9\x50\xa0\x39\x7f\x29\x25\x0b\x60\x84\x1e\xf0": # WebAuthn key for WebAuthn.io @@ -337,7 +296,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="webauthn.io", icon=None, use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xf9\x5b\xc7\x38\x28\xee\x21\x0f\x9f\xd3\xbb\xe7\x2d\x97\x90\x80\x13\xb0\xa3\x75\x9e\x9a\xea\x3d\x0a\xe3\x18\x76\x6c\xd2\xe1\xad": # WebAuthn key for WebAuthn.me @@ -345,7 +303,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="webauthn.me", icon=None, use_sign_count=None, - use_self_attestation=None, ) if rp_id_hash == b"\xc4\x6c\xef\x82\xad\x1b\x54\x64\x77\x59\x1d\x00\x8b\x08\x75\x9e\xc3\xe6\xd2\xec\xb4\xf3\x94\x74\xbf\xea\x69\x69\x92\x5d\x03\xb7": # WebAuthn key for demo.yubico.com @@ -353,7 +310,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label="demo.yubico.com", icon=None, use_sign_count=None, - use_self_attestation=None, ) return None diff --git a/core/src/apps/webauthn/knownapps.py.mako b/core/src/apps/webauthn/knownapps.py.mako index 880c32748b..467232b50c 100644 --- a/core/src/apps/webauthn/knownapps.py.mako +++ b/core/src/apps/webauthn/knownapps.py.mako @@ -9,12 +9,10 @@ class FIDOApp: label: str, icon: str | None, use_sign_count: bool | None, - use_self_attestation: bool | None, ) -> None: self.label = label self.icon = icon self.use_sign_count = use_sign_count - self.use_self_attestation = use_self_attestation <% @@ -42,7 +40,6 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None: label=${black_repr(label)}, icon=${black_repr(app.icon_res)}, use_sign_count=${black_repr(app.use_sign_count)}, - use_self_attestation=${black_repr(app.use_self_attestation)}, ) % endfor diff --git a/core/src/trezor/lvglui/scrs/webauthn.py b/core/src/trezor/lvglui/scrs/webauthn.py index 745bbacd03..fbeaafa4b6 100644 --- a/core/src/trezor/lvglui/scrs/webauthn.py +++ b/core/src/trezor/lvglui/scrs/webauthn.py @@ -1,7 +1,8 @@ from ..i18n import gettext as _, keys as i18n_keys -from .common import FullSizeWindow +from .common import FullSizeWindow, lv from .components.container import ContainerFlexCol from .components.listitem import DisplayItem +from .components.radio import RadioTrigger class ConfirmWebauthn(FullSizeWindow): @@ -26,3 +27,43 @@ def __init__( self.container, _(i18n_keys.LIST_KEY__ACCOUNT_NAME__COLON), account_name ) self.container.add_dummy() + + +class SelectWebauthnAccount(FullSizeWindow): + def __init__( + self, title: str, app_icon: str, app_name: str, account_names: list[str] + ): + super().__init__( + title, + app_name, + cancel_text=_(i18n_keys.BUTTON__CANCEL), + icon_path=app_icon, + anim_dir=2, + ) + self.content_area.set_style_max_height(632, 0) + options = "\n".join(account_names) + self.choices = RadioTrigger(self.content_area, options) + anchor = getattr(self, "subtitle", None) or self.title + self.choices.container.align_to(anchor, lv.ALIGN.OUT_BOTTOM_MID, 0, 24) + for item in self.choices.items: + arrow = lv.img(item) + arrow.set_src("A:/res/arrow-right.png") + arrow.set_align(lv.ALIGN.RIGHT_MID) + self.content_area.add_event_cb(self.on_ready, lv.EVENT.READY, None) + + def on_ready(self, _event_obj): + self.show_unload_anim() + self.channel.publish(self.choices.get_selected_index()) + + def eventhandler(self, event_obj): + code = event_obj.code + target = event_obj.get_target() + if ( + code == lv.EVENT.CLICKED + and hasattr(self, "btn_no") + and target == self.btn_no + ): + self.show_dismiss_anim() + self.channel.publish(None) + else: + super().eventhandler(event_obj) diff --git a/core/src/trezor/ui/components/common/webauthn.py b/core/src/trezor/ui/components/common/webauthn.py index 9b3ccc3818..99085ff24e 100644 --- a/core/src/trezor/ui/components/common/webauthn.py +++ b/core/src/trezor/ui/components/common/webauthn.py @@ -1,9 +1,17 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from trezor.lvglui.scrs.common import FullSizeWindow + DEFAULT_ICON = "A:/res/icon_webauthn.png" class ConfirmInfo: def __init__(self) -> None: self.app_icon: str | None = None + # The on-screen confirmation window, if currently shown. Kept so that an + # external CTAPHID_CANCEL can dismiss the retained lvgl overlay. + self.screen: "FullSizeWindow | None" = None def get_header(self) -> str: raise NotImplementedError @@ -14,6 +22,9 @@ def app_name(self) -> str: def account_name(self) -> str | None: return None + def account_names(self) -> list[str]: + return [] + def load_icon(self, rp_id_hash: bytes) -> None: from apps.webauthn import knownapps diff --git a/core/src/trezor/ui/layouts/lvgl/webauthn.py b/core/src/trezor/ui/layouts/lvgl/webauthn.py index 942b1cbad0..4a22795154 100644 --- a/core/src/trezor/ui/layouts/lvgl/webauthn.py +++ b/core/src/trezor/ui/layouts/lvgl/webauthn.py @@ -25,12 +25,45 @@ async def confirm_webauthn( if app_name == account_name: account_name = None confirm = ConfirmWebauthn(title, icon_path, app_name, account_name) - if ctx is None: - return bool(await confirm.request()) - else: - return bool( - await interact(ctx, confirm, "confirm_webauthn", ButtonRequestType.Other) - ) + # Remember the shown window so an external CTAPHID_CANCEL can dismiss it; the + # lvgl overlay is not removed automatically when the workflow coroutine is killed. + info.screen = confirm + try: + if ctx is None: + return bool(await confirm.request()) + else: + return bool( + await interact( + ctx, confirm, "confirm_webauthn", ButtonRequestType.Other + ) + ) + finally: + info.screen = None + + +async def select_webauthn_account( + ctx: wire.GenericContext | None, + info: ConfirmInfo, +) -> int | None: + + from trezor.lvglui.scrs.webauthn import SelectWebauthnAccount + + select = SelectWebauthnAccount( + info.get_header(), info.app_icon, info.app_name(), info.account_names() + ) + info.screen = select + try: + if ctx is None: + result = await select.request() + else: + result = await interact( + ctx, select, "select_webauthn_account", ButtonRequestType.Other + ) + if isinstance(result, int) and result >= 0: + return result + return None + finally: + info.screen = None async def confirm_webauthn_reset() -> bool: