mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-11 16:00:57 +00:00
refactor(core): convert apps.webauthn to layouts
This commit is contained in:
parent
9ab1891b22
commit
875cc0cb1a
@ -2,9 +2,7 @@ import storage.device
|
||||
from trezor import ui, wire
|
||||
from trezor.enums import ButtonRequestType
|
||||
from trezor.messages import GetNextU2FCounter, NextU2FCounter
|
||||
from trezor.ui.components.tt.text import Text
|
||||
|
||||
from apps.common.confirm import require_confirm
|
||||
from trezor.ui.layouts import confirm_action
|
||||
|
||||
|
||||
async def get_next_u2f_counter(
|
||||
@ -12,10 +10,15 @@ async def get_next_u2f_counter(
|
||||
) -> NextU2FCounter:
|
||||
if not storage.device.is_initialized():
|
||||
raise wire.NotInitialized("Device is not initialized")
|
||||
text = Text("Get next U2F counter", ui.ICON_CONFIG)
|
||||
text.normal("Do you really want to")
|
||||
text.bold("increase and retrieve")
|
||||
text.normal("the U2F counter?")
|
||||
await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall)
|
||||
|
||||
await confirm_action(
|
||||
ctx,
|
||||
"get_u2f_counter",
|
||||
title="Get next U2F counter",
|
||||
description="Do you really want to\n{}\nthe U2F counter?",
|
||||
description_param="increase and retrieve",
|
||||
icon=ui.ICON_CONFIG,
|
||||
br_code=ButtonRequestType.ProtectCall,
|
||||
)
|
||||
|
||||
return NextU2FCounter(u2f_counter=storage.device.next_u2f_counter())
|
||||
|
@ -2,9 +2,7 @@ import storage.device
|
||||
from trezor import ui, wire
|
||||
from trezor.enums import ButtonRequestType
|
||||
from trezor.messages import SetU2FCounter, Success
|
||||
from trezor.ui.components.tt.text import Text
|
||||
|
||||
from apps.common.confirm import require_confirm
|
||||
from trezor.ui.layouts import confirm_action
|
||||
|
||||
|
||||
async def set_u2f_counter(ctx: wire.Context, msg: SetU2FCounter) -> Success:
|
||||
@ -13,10 +11,15 @@ async def set_u2f_counter(ctx: wire.Context, msg: SetU2FCounter) -> Success:
|
||||
if msg.u2f_counter is None:
|
||||
raise wire.ProcessError("No value provided")
|
||||
|
||||
text = Text("Set U2F counter", ui.ICON_CONFIG)
|
||||
text.normal("Do you really want to", "set the U2F counter")
|
||||
text.bold("to %d?" % msg.u2f_counter)
|
||||
await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall)
|
||||
await confirm_action(
|
||||
ctx,
|
||||
"set_u2f_counter",
|
||||
title="Set U2F counter",
|
||||
description="Do you really want to\nset the U2F counter\nto {}?",
|
||||
description_param=str(msg.u2f_counter),
|
||||
icon=ui.ICON_CONFIG,
|
||||
br_code=ButtonRequestType.ProtectCall,
|
||||
)
|
||||
|
||||
storage.device.set_u2f_counter(msg.u2f_counter)
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
import storage.device
|
||||
from trezor import wire
|
||||
from trezor.messages import Success, WebAuthnAddResidentCredential
|
||||
from trezor.ui.layouts import show_error_and_raise
|
||||
from trezor.ui.layouts import confirm_webauthn, show_error_and_raise
|
||||
|
||||
from apps.common.confirm import require_confirm
|
||||
|
||||
from .confirm import ConfirmContent, ConfirmInfo
|
||||
from .credential import Fido2Credential
|
||||
from .resident_credentials import store_resident_credential
|
||||
|
||||
@ -46,8 +43,8 @@ async def add_resident_credential(
|
||||
red=True,
|
||||
)
|
||||
|
||||
content = ConfirmContent(ConfirmAddCredential(cred))
|
||||
await require_confirm(ctx, content)
|
||||
if not await confirm_webauthn(ctx, ConfirmAddCredential(cred)):
|
||||
raise wire.ActionCancelled
|
||||
|
||||
if store_resident_credential(cred):
|
||||
return Success(message="Credential added")
|
||||
|
@ -9,20 +9,14 @@ from storage.fido2 import KEY_AGREEMENT_PRIVKEY, KEY_AGREEMENT_PUBKEY
|
||||
from trezor import config, io, log, loop, ui, utils, workflow
|
||||
from trezor.crypto import aes, der, hashlib, hmac, random
|
||||
from trezor.crypto.curve import nist256p1
|
||||
from trezor.ui.components.tt.confirm import (
|
||||
CONFIRMED,
|
||||
Confirm,
|
||||
ConfirmPageable,
|
||||
Pageable,
|
||||
)
|
||||
from trezor.ui.components.tt.text import Text
|
||||
from trezor.ui.popup import Popup
|
||||
from trezor.ui.components.common.confirm import Pageable
|
||||
from trezor.ui.components.common.webauthn import ConfirmInfo
|
||||
from trezor.ui.layouts import confirm_webauthn, confirm_webauthn_reset, show_popup
|
||||
|
||||
from apps.base import set_homescreen
|
||||
from apps.common import cbor
|
||||
|
||||
from . import common
|
||||
from .confirm import ConfirmContent, ConfirmInfo
|
||||
from .credential import CRED_ID_MAX_LENGTH, Credential, Fido2Credential, U2fCredential
|
||||
from .resident_credentials import find_by_rp_id_hash, store_resident_credential
|
||||
|
||||
@ -596,14 +590,6 @@ async def verify_user(keepalive_callback: KeepaliveCallback) -> bool:
|
||||
return ret
|
||||
|
||||
|
||||
async def confirm(*args: Any, **kwargs: Any) -> bool:
|
||||
return await Confirm(*args, **kwargs) is CONFIRMED
|
||||
|
||||
|
||||
async def confirm_pageable(*args: Any, **kwargs: Any) -> bool:
|
||||
return await ConfirmPageable(*args, **kwargs) is CONFIRMED
|
||||
|
||||
|
||||
class State:
|
||||
def __init__(self, cid: int, iface: io.HID) -> None:
|
||||
self.cid = cid
|
||||
@ -661,23 +647,23 @@ class U2fConfirmRegister(U2fState):
|
||||
|
||||
async def confirm_dialog(self) -> bool:
|
||||
if self._cred.rp_id_hash in _BOGUS_APPIDS:
|
||||
text = Text("U2F", ui.ICON_WRONG, ui.RED)
|
||||
if self.cid == _last_good_auth_check_cid:
|
||||
text.bold("Already registered.")
|
||||
text.br_half()
|
||||
text.normal(
|
||||
"This device is already", "registered with this", "application."
|
||||
await show_popup(
|
||||
title="U2F",
|
||||
subtitle="Already registered.",
|
||||
description="This device is already\nregistered with this\napplication.",
|
||||
timeout_ms=_POPUP_TIMEOUT_MS,
|
||||
)
|
||||
else:
|
||||
text.bold("Not registered.")
|
||||
text.br_half()
|
||||
text.normal(
|
||||
"This device is not", "registered with this", "application."
|
||||
await show_popup(
|
||||
title="U2F",
|
||||
subtitle="Not registered.",
|
||||
description="This device is not\nregistered with this\napplication.",
|
||||
timeout_ms=_POPUP_TIMEOUT_MS,
|
||||
)
|
||||
return await Popup(text, _POPUP_TIMEOUT_MS)
|
||||
return False
|
||||
else:
|
||||
content = ConfirmContent(self)
|
||||
return await confirm(content)
|
||||
return await confirm_webauthn(None, self)
|
||||
|
||||
def get_header(self) -> str:
|
||||
return "U2F Register"
|
||||
@ -700,8 +686,7 @@ class U2fConfirmAuthenticate(U2fState):
|
||||
return "U2F Authenticate"
|
||||
|
||||
async def confirm_dialog(self) -> bool:
|
||||
content = ConfirmContent(self)
|
||||
return await confirm(content)
|
||||
return await confirm_webauthn(None, self)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return (
|
||||
@ -817,8 +802,7 @@ class Fido2ConfirmMakeCredential(Fido2State, ConfirmInfo):
|
||||
return self._cred.account_name()
|
||||
|
||||
async def confirm_dialog(self) -> bool:
|
||||
content = ConfirmContent(self)
|
||||
if not await confirm(content):
|
||||
if not await confirm_webauthn(None, self):
|
||||
return False
|
||||
if self._user_verification:
|
||||
return await verify_user(KeepaliveCallback(self.cid, self.iface))
|
||||
@ -851,11 +835,13 @@ class Fido2ConfirmExcluded(Fido2ConfirmMakeCredential):
|
||||
await send_cmd(cmd, self.iface)
|
||||
self.finished = True
|
||||
|
||||
text = Text("FIDO2 Register", ui.ICON_WRONG, ui.RED)
|
||||
text.bold("Already registered.")
|
||||
text.br_half()
|
||||
text.normal("This device is already", "registered with", self._cred.rp_id + ".")
|
||||
await Popup(text, _POPUP_TIMEOUT_MS)
|
||||
await show_popup(
|
||||
title="FIDO2 Register",
|
||||
subtitle="Already registered.",
|
||||
description="This device is already\nregistered with {}.",
|
||||
description_param=self._cred.rp_id,
|
||||
timeout_ms=_POPUP_TIMEOUT_MS,
|
||||
)
|
||||
|
||||
|
||||
class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
|
||||
@ -892,8 +878,7 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
|
||||
return len(self._creds)
|
||||
|
||||
async def confirm_dialog(self) -> bool:
|
||||
content = ConfirmContent(self)
|
||||
if not await confirm_pageable(self, content):
|
||||
if not await confirm_webauthn(None, self, pageable=self):
|
||||
return False
|
||||
if self._user_verification:
|
||||
return await verify_user(KeepaliveCallback(self.cid, self.iface))
|
||||
@ -938,11 +923,13 @@ class Fido2ConfirmNoPin(State):
|
||||
await send_cmd(cmd, self.iface)
|
||||
self.finished = True
|
||||
|
||||
text = Text("FIDO2 Verify User", ui.ICON_WRONG, ui.RED)
|
||||
text.bold("Unable to verify user.")
|
||||
text.br_half()
|
||||
text.normal("Please enable PIN", "protection.")
|
||||
return await Popup(text, _POPUP_TIMEOUT_MS)
|
||||
await show_popup(
|
||||
title="FIDO2 Verify User",
|
||||
subtitle="Unable to verify user.",
|
||||
description="Please enable PIN\nprotection.",
|
||||
timeout_ms=_POPUP_TIMEOUT_MS,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
class Fido2ConfirmNoCredentials(Fido2ConfirmGetAssertion):
|
||||
@ -958,13 +945,13 @@ class Fido2ConfirmNoCredentials(Fido2ConfirmGetAssertion):
|
||||
await send_cmd(cmd, self.iface)
|
||||
self.finished = True
|
||||
|
||||
text = Text("FIDO2 Authenticate", ui.ICON_WRONG, ui.RED)
|
||||
text.bold("Not registered.")
|
||||
text.br_half()
|
||||
text.normal(
|
||||
"This device is not", "registered with", self._creds[0].app_name() + "."
|
||||
await show_popup(
|
||||
title="FIDO2 Authenticate",
|
||||
subtitle="Not registered.",
|
||||
description="This device is not\nregistered with\n{}.",
|
||||
description_param=self._creds[0].app_name(),
|
||||
timeout_ms=_POPUP_TIMEOUT_MS,
|
||||
)
|
||||
await Popup(text, _POPUP_TIMEOUT_MS)
|
||||
|
||||
|
||||
class Fido2ConfirmReset(Fido2State):
|
||||
@ -972,10 +959,7 @@ class Fido2ConfirmReset(Fido2State):
|
||||
super().__init__(cid, iface)
|
||||
|
||||
async def confirm_dialog(self) -> bool:
|
||||
text = Text("FIDO2 Reset", ui.ICON_CONFIG)
|
||||
text.normal("Do you really want to")
|
||||
text.bold("erase all credentials?")
|
||||
return await confirm(text)
|
||||
return await confirm_webauthn_reset()
|
||||
|
||||
async def on_confirm(self) -> None:
|
||||
storage.resident_credentials.delete_all()
|
||||
|
@ -2,10 +2,9 @@ import storage.device
|
||||
import storage.resident_credentials
|
||||
from trezor import wire
|
||||
from trezor.messages import Success, WebAuthnRemoveResidentCredential
|
||||
from trezor.ui.components.common.webauthn import ConfirmInfo
|
||||
from trezor.ui.layouts import confirm_webauthn
|
||||
|
||||
from apps.common.confirm import require_confirm
|
||||
|
||||
from .confirm import ConfirmContent, ConfirmInfo
|
||||
from .credential import Fido2Credential
|
||||
from .resident_credentials import get_resident_credential
|
||||
|
||||
@ -38,8 +37,8 @@ async def remove_resident_credential(
|
||||
if cred is None:
|
||||
raise wire.ProcessError("Invalid credential index.")
|
||||
|
||||
content = ConfirmContent(ConfirmRemoveCredential(cred))
|
||||
await require_confirm(ctx, content)
|
||||
if not await confirm_webauthn(ctx, ConfirmRemoveCredential(cred)):
|
||||
raise wire.ActionCancelled
|
||||
|
||||
assert cred.index is not None
|
||||
storage.resident_credentials.delete(cred.index)
|
||||
|
@ -52,3 +52,26 @@ class ConfirmBase(ui.Layout):
|
||||
from apps.debug import confirm_signal
|
||||
|
||||
return super().create_tasks() + (confirm_signal(),)
|
||||
|
||||
|
||||
class Pageable:
|
||||
def __init__(self) -> None:
|
||||
self._page = 0
|
||||
|
||||
def page(self) -> int:
|
||||
return self._page
|
||||
|
||||
def page_count(self) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
def is_first(self) -> bool:
|
||||
return self._page == 0
|
||||
|
||||
def is_last(self) -> bool:
|
||||
return self._page == self.page_count() - 1
|
||||
|
||||
def next(self) -> None:
|
||||
self._page = min(self._page + 1, self.page_count() - 1)
|
||||
|
||||
def prev(self) -> None:
|
||||
self._page = max(self._page - 1, 0)
|
||||
|
25
core/src/trezor/ui/components/common/webauthn.py
Normal file
25
core/src/trezor/ui/components/common/webauthn.py
Normal file
@ -0,0 +1,25 @@
|
||||
DEFAULT_ICON = "apps/webauthn/res/icon_webauthn.toif"
|
||||
|
||||
|
||||
class ConfirmInfo:
|
||||
def __init__(self) -> None:
|
||||
self.app_icon: bytes | None = None
|
||||
|
||||
def get_header(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def app_name(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def account_name(self) -> str | None:
|
||||
return None
|
||||
|
||||
def load_icon(self, rp_id_hash: bytes) -> None:
|
||||
from trezor import res
|
||||
from apps.webauthn import knownapps
|
||||
|
||||
fido_app = knownapps.by_rp_id_hash(rp_id_hash)
|
||||
if fido_app is not None and fido_app.icon is not None:
|
||||
self.app_icon = res.load(fido_app.icon)
|
||||
else:
|
||||
self.app_icon = res.load(DEFAULT_ICON)
|
@ -3,7 +3,7 @@ from micropython import const
|
||||
from trezor import loop, res, ui, utils
|
||||
from trezor.ui.loader import Loader, LoaderDefault
|
||||
|
||||
from ..common.confirm import CANCELLED, CONFIRMED, INFO, ConfirmBase
|
||||
from ..common.confirm import CANCELLED, CONFIRMED, INFO, ConfirmBase, Pageable
|
||||
from .button import Button, ButtonAbort, ButtonCancel, ButtonConfirm, ButtonDefault
|
||||
|
||||
if False:
|
||||
@ -54,29 +54,6 @@ class Confirm(ConfirmBase):
|
||||
super().__init__(content, button_confirm, button_cancel)
|
||||
|
||||
|
||||
class Pageable:
|
||||
def __init__(self) -> None:
|
||||
self._page = 0
|
||||
|
||||
def page(self) -> int:
|
||||
return self._page
|
||||
|
||||
def page_count(self) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
def is_first(self) -> bool:
|
||||
return self._page == 0
|
||||
|
||||
def is_last(self) -> bool:
|
||||
return self._page == self.page_count() - 1
|
||||
|
||||
def next(self) -> None:
|
||||
self._page = min(self._page + 1, self.page_count() - 1)
|
||||
|
||||
def prev(self) -> None:
|
||||
self._page = max(self._page - 1, 0)
|
||||
|
||||
|
||||
class ConfirmPageable(Confirm):
|
||||
def __init__(self, pageable: Pageable, *args: Any, **kwargs: Any):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -1,31 +1,7 @@
|
||||
from trezor import ui
|
||||
from trezor.ui.components.tt.text import text_center_trim_left, text_center_trim_right
|
||||
|
||||
DEFAULT_ICON = "apps/webauthn/res/icon_webauthn.toif"
|
||||
|
||||
|
||||
class ConfirmInfo:
|
||||
def __init__(self) -> None:
|
||||
self.app_icon: bytes | None = None
|
||||
|
||||
def get_header(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def app_name(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def account_name(self) -> str | None:
|
||||
return None
|
||||
|
||||
def load_icon(self, rp_id_hash: bytes) -> None:
|
||||
from trezor import res
|
||||
from . import knownapps
|
||||
|
||||
fido_app = knownapps.by_rp_id_hash(rp_id_hash)
|
||||
if fido_app is not None and fido_app.icon is not None:
|
||||
self.app_icon = res.load(fido_app.icon)
|
||||
else:
|
||||
self.app_icon = res.load(DEFAULT_ICON)
|
||||
from ..common.webauthn import ConfirmInfo
|
||||
from .text import text_center_trim_left, text_center_trim_right
|
||||
|
||||
|
||||
class ConfirmContent(ui.Component):
|
@ -5,13 +5,15 @@ from trezor import ui, wire
|
||||
from trezor.enums import ButtonRequestType
|
||||
from trezor.ui.container import Container
|
||||
from trezor.ui.loader import LoaderDanger
|
||||
from trezor.ui.popup import Popup
|
||||
from trezor.ui.qr import Qr
|
||||
from trezor.utils import chunks, chunks_intersperse
|
||||
|
||||
from ..components.common import break_path_to_lines
|
||||
from ..components.common.confirm import is_confirmed, raise_if_cancelled
|
||||
from ..components.common.webauthn import ConfirmInfo
|
||||
from ..components.tt.button import ButtonCancel, ButtonDefault
|
||||
from ..components.tt.confirm import Confirm, HoldToConfirm
|
||||
from ..components.tt.confirm import Confirm, ConfirmPageable, HoldToConfirm, Pageable
|
||||
from ..components.tt.scroll import (
|
||||
PAGEBREAK,
|
||||
Paginated,
|
||||
@ -19,6 +21,7 @@ from ..components.tt.scroll import (
|
||||
paginate_text,
|
||||
)
|
||||
from ..components.tt.text import LINE_WIDTH_PAGINATED, Span, Text
|
||||
from ..components.tt.webauthn import ConfirmContent
|
||||
from ..constants.tt import (
|
||||
MONO_ADDR_PER_LINE,
|
||||
MONO_HEX_PER_LINE,
|
||||
@ -72,6 +75,9 @@ __all__ = (
|
||||
"confirm_coinjoin",
|
||||
"confirm_timebounds_stellar",
|
||||
"confirm_transfer_binance",
|
||||
"show_popup",
|
||||
"confirm_webauthn",
|
||||
"confirm_webauthn_reset",
|
||||
)
|
||||
|
||||
|
||||
@ -1009,3 +1015,43 @@ async def confirm_transfer_binance(
|
||||
ctx, Paginated(pages), "confirm_transfer", ButtonRequestType.ConfirmOutput
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def show_popup(
|
||||
title: str,
|
||||
description: str,
|
||||
subtitle: Optional[str] = None,
|
||||
description_param: str = "",
|
||||
timeout_ms: int = 3000,
|
||||
) -> None:
|
||||
text = Text(title, ui.ICON_WRONG, ui.RED)
|
||||
if subtitle is not None:
|
||||
text.bold(subtitle)
|
||||
text.br_half()
|
||||
text.format_parametrized(description, description_param)
|
||||
await Popup(text, timeout_ms)
|
||||
|
||||
|
||||
async def confirm_webauthn(
|
||||
ctx: Optional[wire.GenericContext],
|
||||
info: ConfirmInfo,
|
||||
pageable: Optional[Pageable] = None,
|
||||
) -> bool:
|
||||
if pageable is not None:
|
||||
confirm: ui.Layout = ConfirmPageable(pageable, ConfirmContent(info))
|
||||
else:
|
||||
confirm = Confirm(ConfirmContent(info))
|
||||
|
||||
if ctx is None:
|
||||
return is_confirmed(await confirm)
|
||||
else:
|
||||
return is_confirmed(
|
||||
await interact(ctx, confirm, "confirm_webauthn", ButtonRequestType.Other)
|
||||
)
|
||||
|
||||
|
||||
async def confirm_webauthn_reset() -> bool:
|
||||
text = Text("FIDO2 Reset", ui.ICON_CONFIG)
|
||||
text.normal("Do you really want to")
|
||||
text.bold("erase all credentials?")
|
||||
return is_confirmed(await Confirm(text))
|
||||
|
@ -719,5 +719,5 @@
|
||||
"test_session_id_and_passphrase.py::test_passphrase_on_device": "c9ca2c9cf6dd416dad4de311266690ec2266b551d74f9d3619301305b3dbe81e",
|
||||
"test_session_id_and_passphrase.py::test_session_enable_passphrase": "b27321ed372b8ade7c4941a80f1f945851046b039a1b43c43a6953106bd1619e",
|
||||
"test_session_id_and_passphrase.py::test_session_with_passphrase": "a044d7a42229ab7cd74651e03dd64edcceb86d72d50bd63a40336decf0b25d3d",
|
||||
"test_u2f_counter.py::test_u2f_counter": "7d96a4d262b9d8a2c1158ac1e5f0f7b2c3ed5f2ba9d6235a014320313f9488fe"
|
||||
"test_u2f_counter.py::test_u2f_counter": "15865e4364347ac56ea45bbff5ded28fd72f58ad2427d1ef190f125dea10ece0"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user