|
13 | 13 | from trezor.lvglui.i18n import gettext as _, keys as i18n_keys |
14 | 14 | from trezor.ui.components.common.confirm import Pageable |
15 | 15 | from trezor.ui.components.common.webauthn import ConfirmInfo |
16 | | -from trezor.ui.layouts.lvgl.webauthn import confirm_webauthn, confirm_webauthn_reset |
| 16 | +from trezor.ui.layouts.lvgl.webauthn import ( |
| 17 | + confirm_webauthn, |
| 18 | + confirm_webauthn_reset, |
| 19 | + select_webauthn_account, |
| 20 | +) |
17 | 21 |
|
18 | 22 | from apps.base import device_is_unlocked, set_homescreen |
19 | 23 | from apps.common import cbor |
|
176 | 180 | _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" |
177 | 181 | _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" |
178 | 182 | else: |
179 | | - _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" |
| 183 | + _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" |
180 | 184 | _BOGUS_APPID_CHROME = b"A" * 32 |
181 | 185 | _BOGUS_APPID_FIREFOX = b"\0" * 32 |
182 | 186 | _BOGUS_APPIDS = (_BOGUS_APPID_CHROME, _BOGUS_APPID_FIREFOX) |
|
212 | 216 | _ALLOW_RESIDENT_CREDENTIALS = storage.device.get_se01_version() >= "1.1.5" |
213 | 217 | _ALLOW_WINK = False |
214 | 218 |
|
215 | | -# The default attestation type to use in MakeCredential responses. If false, then basic attestation will be used by default. |
216 | | -_DEFAULT_USE_SELF_ATTESTATION = True |
217 | | - |
218 | 219 | # The default value of the use_sign_count flag for newly created credentials. |
219 | 220 | _DEFAULT_USE_SIGN_COUNT = True |
220 | 221 |
|
@@ -620,6 +621,11 @@ async def handle_reports(usb_face: io.HID, spi_iface: io.SPI) -> None: |
620 | 621 |
|
621 | 622 | if req is None: |
622 | 623 | continue |
| 624 | + if dialog_mgr.is_busy() and dialog_mgr.state is not None: |
| 625 | + active_iface = dialog_mgr.state.iface |
| 626 | + if dialog_mgr.iface is not active_iface: |
| 627 | + dialog_mgr.set_iface(active_iface) |
| 628 | + continue |
623 | 629 | if dialog_mgr.is_busy() and ( |
624 | 630 | req.cid |
625 | 631 | not in ( |
@@ -860,8 +866,9 @@ async def on_timeout(self) -> None: |
860 | 866 | await self.on_decline() |
861 | 867 |
|
862 | 868 | async def on_cancel(self) -> None: |
863 | | - cmd = cbor_error(self.cid, _ERR_KEEPALIVE_CANCEL) |
864 | | - await send_cmd(cmd, self.iface) |
| 869 | + # The CTAP2 cancel response (_ERR_KEEPALIVE_CANCEL) is sent synchronously |
| 870 | + # from the _CMD_CANCEL dispatch path, because this coroutine is torn down |
| 871 | + # by DialogManager.reset() and can no longer await a send here. |
865 | 872 | self.finished = True |
866 | 873 |
|
867 | 874 |
|
@@ -1021,10 +1028,29 @@ def app_name(self) -> str: |
1021 | 1028 | def account_name(self) -> str | None: |
1022 | 1029 | return self._creds[self.page()].account_name() |
1023 | 1030 |
|
| 1031 | + def _single_line_label(self, label: str) -> str: |
| 1032 | + return label.replace("\r\n", " ").replace("\n", " ").replace("\r", " ") |
| 1033 | + |
| 1034 | + def account_names(self) -> list[str]: |
| 1035 | + return [ |
| 1036 | + self._single_line_label( |
| 1037 | + cred.account_name() or f"{cred.app_name()} #{i + 1}" |
| 1038 | + ) |
| 1039 | + for i, cred in enumerate(self._creds) |
| 1040 | + ] |
| 1041 | + |
1024 | 1042 | def page_count(self) -> int: |
1025 | 1043 | return len(self._creds) |
1026 | 1044 |
|
| 1045 | + def select_account(self, index: int) -> None: |
| 1046 | + self._page = min(max(index, 0), self.page_count() - 1) |
| 1047 | + |
1027 | 1048 | async def confirm_dialog(self) -> bool: |
| 1049 | + if self.page_count() > 1: |
| 1050 | + selected = await select_webauthn_account(None, self) |
| 1051 | + if selected is None: |
| 1052 | + return False |
| 1053 | + self.select_account(selected) |
1028 | 1054 | if not await confirm_webauthn(None, self): |
1029 | 1055 | return False |
1030 | 1056 | if self._user_verification: |
@@ -1225,6 +1251,39 @@ async def dialog_workflow(self) -> None: |
1225 | 1251 | await self.state.on_decline() |
1226 | 1252 |
|
1227 | 1253 |
|
| 1254 | +def cmd_cancel(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: |
| 1255 | + if __debug__: |
| 1256 | + log.debug(__name__, "_CMD_CANCEL") |
| 1257 | + state = dialog_mgr.state |
| 1258 | + # Was a FIDO2/CTAP2 request pending? Only those get a CBOR cancel response. |
| 1259 | + is_fido2 = isinstance(state, Fido2State) |
| 1260 | + pending_cid = state.cid if is_fido2 else req.cid |
| 1261 | + pending_iface = state.iface if is_fido2 else dialog_mgr.iface |
| 1262 | + if is_fido2 and dialog_mgr.iface is not pending_iface: |
| 1263 | + dialog_mgr.set_iface(pending_iface) |
| 1264 | + return None |
| 1265 | + # Dismiss the on-screen overlay. lvgl windows are retained and are not removed |
| 1266 | + # when reset() closes the workflow coroutine, so destroy them explicitly here, |
| 1267 | + # otherwise the confirmation / PIN page stays up after the host cancels. |
| 1268 | + if state is not None: |
| 1269 | + # The register / authenticate confirmation window. |
| 1270 | + if isinstance(state, ConfirmInfo) and state.screen is not None: |
| 1271 | + state.screen.destroy() |
| 1272 | + state.screen = None |
| 1273 | + # The user-verification PIN window shown during verify_user(), if any. |
| 1274 | + from trezor.lvglui.scrs.pinscreen import InputPin |
| 1275 | + |
| 1276 | + pin_wind = InputPin.get_window_if_visible() |
| 1277 | + if pin_wind is not None: |
| 1278 | + pin_wind.destroy() |
| 1279 | + dialog_mgr.result = _RESULT_CANCEL |
| 1280 | + dialog_mgr.reset() |
| 1281 | + if is_fido2: |
| 1282 | + dialog_mgr.set_iface(pending_iface) |
| 1283 | + return cbor_error(pending_cid, _ERR_KEEPALIVE_CANCEL) |
| 1284 | + return None |
| 1285 | + |
| 1286 | + |
1228 | 1287 | def dispatch_cmd_hid(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: |
1229 | 1288 | if req.cmd == _CMD_MSG: |
1230 | 1289 | try: |
@@ -1304,11 +1363,7 @@ def dispatch_cmd_hid(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: |
1304 | 1363 | return cbor_error(req.cid, _ERR_INVALID_CMD) |
1305 | 1364 |
|
1306 | 1365 | elif req.cmd == _CMD_CANCEL: |
1307 | | - if __debug__: |
1308 | | - log.debug(__name__, "_CMD_CANCEL") |
1309 | | - dialog_mgr.result = _RESULT_CANCEL |
1310 | | - dialog_mgr.reset() |
1311 | | - return None |
| 1366 | + return cmd_cancel(req, dialog_mgr) |
1312 | 1367 | else: |
1313 | 1368 | if __debug__: |
1314 | 1369 | log.warning(__name__, "_ERR_INVALID_CMD: %d", req.cmd) |
@@ -1394,11 +1449,7 @@ def dispatch_cmd_ble(req: Cmd, dialog_mgr: DialogManager) -> Cmd | None: |
1394 | 1449 | log.debug(__name__, "_CMD_WINK") |
1395 | 1450 | return cmd_wink(req) |
1396 | 1451 | elif req.cmd == _CMD_CANCEL: |
1397 | | - if __debug__: |
1398 | | - log.debug(__name__, "_CMD_CANCEL") |
1399 | | - dialog_mgr.result = _RESULT_CANCEL |
1400 | | - dialog_mgr.reset() |
1401 | | - return None |
| 1452 | + return cmd_cancel(req, dialog_mgr) |
1402 | 1453 | else: |
1403 | 1454 | if __debug__: |
1404 | 1455 | log.warning(__name__, "_ERR_INVALID_CMD: %d", req.cmd) |
@@ -1827,16 +1878,6 @@ def cbor_make_credential_process(req: Cmd, dialog_mgr: DialogManager) -> State | |
1827 | 1878 | ) |
1828 | 1879 |
|
1829 | 1880 |
|
1830 | | -def use_self_attestation(rp_id_hash: bytes) -> bool: |
1831 | | - from . import knownapps |
1832 | | - |
1833 | | - app = knownapps.by_rp_id_hash(rp_id_hash) |
1834 | | - if app is not None and app.use_self_attestation is not None: |
1835 | | - return app.use_self_attestation |
1836 | | - else: |
1837 | | - return _DEFAULT_USE_SELF_ATTESTATION |
1838 | | - |
1839 | | - |
1840 | 1881 | def cbor_make_credential_sign( |
1841 | 1882 | client_data_hash: bytes, cred: Fido2Credential, user_verification: bool |
1842 | 1883 | ) -> bytes: |
@@ -1864,16 +1905,12 @@ def cbor_make_credential_sign( |
1864 | 1905 | + extensions |
1865 | 1906 | ) |
1866 | 1907 |
|
1867 | | - if use_self_attestation(cred.rp_id_hash): |
1868 | | - sig = cred.sign((authenticator_data, client_data_hash)) |
1869 | | - attestation_statement = {"alg": cred.algorithm, "sig": sig} |
1870 | | - else: |
1871 | | - sig = basic_attestation_sign((authenticator_data, client_data_hash)) |
1872 | | - attestation_statement = { |
1873 | | - "alg": common.COSE_ALG_ES256, |
1874 | | - "sig": sig, |
1875 | | - "x5c": [_FIDO_ATT_CERT], |
1876 | | - } |
| 1908 | + sig = basic_attestation_sign((authenticator_data, client_data_hash)) |
| 1909 | + attestation_statement = { |
| 1910 | + "alg": common.COSE_ALG_ES256, |
| 1911 | + "sig": sig, |
| 1912 | + "x5c": [_FIDO_ATT_CERT], |
| 1913 | + } |
1877 | 1914 |
|
1878 | 1915 | # Encode the authenticatorMakeCredential response data. |
1879 | 1916 | return cbor.encode( |
|
0 commit comments