refactor(core): convert apps.webauthn to layouts

pull/1750/head
Martin Milata 3 years ago
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)

@ -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…
Cancel
Save