1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-05 13:26:57 +00:00

refactor(core): convert apps.webauthn to layouts

This commit is contained in:
Martin Milata 2021-03-19 17:39:35 +01:00
parent 9ab1891b22
commit 875cc0cb1a
11 changed files with 166 additions and 133 deletions

View File

@ -2,9 +2,7 @@ import storage.device
from trezor import ui, wire from trezor import ui, wire
from trezor.enums import ButtonRequestType from trezor.enums import ButtonRequestType
from trezor.messages import GetNextU2FCounter, NextU2FCounter from trezor.messages import GetNextU2FCounter, NextU2FCounter
from trezor.ui.components.tt.text import Text from trezor.ui.layouts import confirm_action
from apps.common.confirm import require_confirm
async def get_next_u2f_counter( async def get_next_u2f_counter(
@ -12,10 +10,15 @@ async def get_next_u2f_counter(
) -> NextU2FCounter: ) -> NextU2FCounter:
if not storage.device.is_initialized(): if not storage.device.is_initialized():
raise wire.NotInitialized("Device is not initialized") raise wire.NotInitialized("Device is not initialized")
text = Text("Get next U2F counter", ui.ICON_CONFIG)
text.normal("Do you really want to") await confirm_action(
text.bold("increase and retrieve") ctx,
text.normal("the U2F counter?") "get_u2f_counter",
await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall) 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()) return NextU2FCounter(u2f_counter=storage.device.next_u2f_counter())

View File

@ -2,9 +2,7 @@ import storage.device
from trezor import ui, wire from trezor import ui, wire
from trezor.enums import ButtonRequestType from trezor.enums import ButtonRequestType
from trezor.messages import SetU2FCounter, Success from trezor.messages import SetU2FCounter, Success
from trezor.ui.components.tt.text import Text from trezor.ui.layouts import confirm_action
from apps.common.confirm import require_confirm
async def set_u2f_counter(ctx: wire.Context, msg: SetU2FCounter) -> Success: 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: if msg.u2f_counter is None:
raise wire.ProcessError("No value provided") raise wire.ProcessError("No value provided")
text = Text("Set U2F counter", ui.ICON_CONFIG) await confirm_action(
text.normal("Do you really want to", "set the U2F counter") ctx,
text.bold("to %d?" % msg.u2f_counter) "set_u2f_counter",
await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall) 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) storage.device.set_u2f_counter(msg.u2f_counter)

View File

@ -1,11 +1,8 @@
import storage.device import storage.device
from trezor import wire from trezor import wire
from trezor.messages import Success, WebAuthnAddResidentCredential 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 .credential import Fido2Credential
from .resident_credentials import store_resident_credential from .resident_credentials import store_resident_credential
@ -46,8 +43,8 @@ async def add_resident_credential(
red=True, red=True,
) )
content = ConfirmContent(ConfirmAddCredential(cred)) if not await confirm_webauthn(ctx, ConfirmAddCredential(cred)):
await require_confirm(ctx, content) raise wire.ActionCancelled
if store_resident_credential(cred): if store_resident_credential(cred):
return Success(message="Credential added") return Success(message="Credential added")

View File

@ -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 import config, io, log, loop, ui, utils, workflow
from trezor.crypto import aes, der, hashlib, hmac, random from trezor.crypto import aes, der, hashlib, hmac, random
from trezor.crypto.curve import nist256p1 from trezor.crypto.curve import nist256p1
from trezor.ui.components.tt.confirm import ( from trezor.ui.components.common.confirm import Pageable
CONFIRMED, from trezor.ui.components.common.webauthn import ConfirmInfo
Confirm, from trezor.ui.layouts import confirm_webauthn, confirm_webauthn_reset, show_popup
ConfirmPageable,
Pageable,
)
from trezor.ui.components.tt.text import Text
from trezor.ui.popup import Popup
from apps.base import set_homescreen from apps.base import set_homescreen
from apps.common import cbor from apps.common import cbor
from . import common from . import common
from .confirm import ConfirmContent, ConfirmInfo
from .credential import CRED_ID_MAX_LENGTH, Credential, Fido2Credential, U2fCredential from .credential import CRED_ID_MAX_LENGTH, Credential, Fido2Credential, U2fCredential
from .resident_credentials import find_by_rp_id_hash, store_resident_credential 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 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: class State:
def __init__(self, cid: int, iface: io.HID) -> None: def __init__(self, cid: int, iface: io.HID) -> None:
self.cid = cid self.cid = cid
@ -661,23 +647,23 @@ class U2fConfirmRegister(U2fState):
async def confirm_dialog(self) -> bool: async def confirm_dialog(self) -> bool:
if self._cred.rp_id_hash in _BOGUS_APPIDS: 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: if self.cid == _last_good_auth_check_cid:
text.bold("Already registered.") await show_popup(
text.br_half() title="U2F",
text.normal( subtitle="Already registered.",
"This device is already", "registered with this", "application." description="This device is already\nregistered with this\napplication.",
timeout_ms=_POPUP_TIMEOUT_MS,
) )
else: else:
text.bold("Not registered.") await show_popup(
text.br_half() title="U2F",
text.normal( subtitle="Not registered.",
"This device is not", "registered with this", "application." description="This device is not\nregistered with this\napplication.",
timeout_ms=_POPUP_TIMEOUT_MS,
) )
return await Popup(text, _POPUP_TIMEOUT_MS) return False
else: else:
content = ConfirmContent(self) return await confirm_webauthn(None, self)
return await confirm(content)
def get_header(self) -> str: def get_header(self) -> str:
return "U2F Register" return "U2F Register"
@ -700,8 +686,7 @@ class U2fConfirmAuthenticate(U2fState):
return "U2F Authenticate" return "U2F Authenticate"
async def confirm_dialog(self) -> bool: async def confirm_dialog(self) -> bool:
content = ConfirmContent(self) return await confirm_webauthn(None, self)
return await confirm(content)
def __eq__(self, other: object) -> bool: def __eq__(self, other: object) -> bool:
return ( return (
@ -817,8 +802,7 @@ class Fido2ConfirmMakeCredential(Fido2State, ConfirmInfo):
return self._cred.account_name() return self._cred.account_name()
async def confirm_dialog(self) -> bool: async def confirm_dialog(self) -> bool:
content = ConfirmContent(self) if not await confirm_webauthn(None, self):
if not await confirm(content):
return False return False
if self._user_verification: if self._user_verification:
return await verify_user(KeepaliveCallback(self.cid, self.iface)) return await verify_user(KeepaliveCallback(self.cid, self.iface))
@ -851,11 +835,13 @@ class Fido2ConfirmExcluded(Fido2ConfirmMakeCredential):
await send_cmd(cmd, self.iface) await send_cmd(cmd, self.iface)
self.finished = True self.finished = True
text = Text("FIDO2 Register", ui.ICON_WRONG, ui.RED) await show_popup(
text.bold("Already registered.") title="FIDO2 Register",
text.br_half() subtitle="Already registered.",
text.normal("This device is already", "registered with", self._cred.rp_id + ".") description="This device is already\nregistered with {}.",
await Popup(text, _POPUP_TIMEOUT_MS) description_param=self._cred.rp_id,
timeout_ms=_POPUP_TIMEOUT_MS,
)
class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable): class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
@ -892,8 +878,7 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
return len(self._creds) return len(self._creds)
async def confirm_dialog(self) -> bool: async def confirm_dialog(self) -> bool:
content = ConfirmContent(self) if not await confirm_webauthn(None, self, pageable=self):
if not await confirm_pageable(self, content):
return False return False
if self._user_verification: if self._user_verification:
return await verify_user(KeepaliveCallback(self.cid, self.iface)) return await verify_user(KeepaliveCallback(self.cid, self.iface))
@ -938,11 +923,13 @@ class Fido2ConfirmNoPin(State):
await send_cmd(cmd, self.iface) await send_cmd(cmd, self.iface)
self.finished = True self.finished = True
text = Text("FIDO2 Verify User", ui.ICON_WRONG, ui.RED) await show_popup(
text.bold("Unable to verify user.") title="FIDO2 Verify User",
text.br_half() subtitle="Unable to verify user.",
text.normal("Please enable PIN", "protection.") description="Please enable PIN\nprotection.",
return await Popup(text, _POPUP_TIMEOUT_MS) timeout_ms=_POPUP_TIMEOUT_MS,
)
return False
class Fido2ConfirmNoCredentials(Fido2ConfirmGetAssertion): class Fido2ConfirmNoCredentials(Fido2ConfirmGetAssertion):
@ -958,13 +945,13 @@ class Fido2ConfirmNoCredentials(Fido2ConfirmGetAssertion):
await send_cmd(cmd, self.iface) await send_cmd(cmd, self.iface)
self.finished = True self.finished = True
text = Text("FIDO2 Authenticate", ui.ICON_WRONG, ui.RED) await show_popup(
text.bold("Not registered.") title="FIDO2 Authenticate",
text.br_half() subtitle="Not registered.",
text.normal( description="This device is not\nregistered with\n{}.",
"This device is not", "registered with", self._creds[0].app_name() + "." description_param=self._creds[0].app_name(),
timeout_ms=_POPUP_TIMEOUT_MS,
) )
await Popup(text, _POPUP_TIMEOUT_MS)
class Fido2ConfirmReset(Fido2State): class Fido2ConfirmReset(Fido2State):
@ -972,10 +959,7 @@ class Fido2ConfirmReset(Fido2State):
super().__init__(cid, iface) super().__init__(cid, iface)
async def confirm_dialog(self) -> bool: async def confirm_dialog(self) -> bool:
text = Text("FIDO2 Reset", ui.ICON_CONFIG) return await confirm_webauthn_reset()
text.normal("Do you really want to")
text.bold("erase all credentials?")
return await confirm(text)
async def on_confirm(self) -> None: async def on_confirm(self) -> None:
storage.resident_credentials.delete_all() storage.resident_credentials.delete_all()

View File

@ -2,10 +2,9 @@ import storage.device
import storage.resident_credentials import storage.resident_credentials
from trezor import wire from trezor import wire
from trezor.messages import Success, WebAuthnRemoveResidentCredential 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 .credential import Fido2Credential
from .resident_credentials import get_resident_credential from .resident_credentials import get_resident_credential
@ -38,8 +37,8 @@ async def remove_resident_credential(
if cred is None: if cred is None:
raise wire.ProcessError("Invalid credential index.") raise wire.ProcessError("Invalid credential index.")
content = ConfirmContent(ConfirmRemoveCredential(cred)) if not await confirm_webauthn(ctx, ConfirmRemoveCredential(cred)):
await require_confirm(ctx, content) raise wire.ActionCancelled
assert cred.index is not None assert cred.index is not None
storage.resident_credentials.delete(cred.index) storage.resident_credentials.delete(cred.index)

View File

@ -52,3 +52,26 @@ class ConfirmBase(ui.Layout):
from apps.debug import confirm_signal from apps.debug import confirm_signal
return super().create_tasks() + (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)

View 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)

View File

@ -3,7 +3,7 @@ from micropython import const
from trezor import loop, res, ui, utils from trezor import loop, res, ui, utils
from trezor.ui.loader import Loader, LoaderDefault 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 from .button import Button, ButtonAbort, ButtonCancel, ButtonConfirm, ButtonDefault
if False: if False:
@ -54,29 +54,6 @@ class Confirm(ConfirmBase):
super().__init__(content, button_confirm, button_cancel) 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): class ConfirmPageable(Confirm):
def __init__(self, pageable: Pageable, *args: Any, **kwargs: Any): def __init__(self, pageable: Pageable, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -1,31 +1,7 @@
from trezor import ui 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" from ..common.webauthn import ConfirmInfo
from .text import text_center_trim_left, text_center_trim_right
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)
class ConfirmContent(ui.Component): class ConfirmContent(ui.Component):

View File

@ -5,13 +5,15 @@ from trezor import ui, wire
from trezor.enums import ButtonRequestType from trezor.enums import ButtonRequestType
from trezor.ui.container import Container from trezor.ui.container import Container
from trezor.ui.loader import LoaderDanger from trezor.ui.loader import LoaderDanger
from trezor.ui.popup import Popup
from trezor.ui.qr import Qr from trezor.ui.qr import Qr
from trezor.utils import chunks, chunks_intersperse from trezor.utils import chunks, chunks_intersperse
from ..components.common import break_path_to_lines from ..components.common import break_path_to_lines
from ..components.common.confirm import is_confirmed, raise_if_cancelled 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.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 ( from ..components.tt.scroll import (
PAGEBREAK, PAGEBREAK,
Paginated, Paginated,
@ -19,6 +21,7 @@ from ..components.tt.scroll import (
paginate_text, paginate_text,
) )
from ..components.tt.text import LINE_WIDTH_PAGINATED, Span, Text from ..components.tt.text import LINE_WIDTH_PAGINATED, Span, Text
from ..components.tt.webauthn import ConfirmContent
from ..constants.tt import ( from ..constants.tt import (
MONO_ADDR_PER_LINE, MONO_ADDR_PER_LINE,
MONO_HEX_PER_LINE, MONO_HEX_PER_LINE,
@ -72,6 +75,9 @@ __all__ = (
"confirm_coinjoin", "confirm_coinjoin",
"confirm_timebounds_stellar", "confirm_timebounds_stellar",
"confirm_transfer_binance", "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 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))

View File

@ -719,5 +719,5 @@
"test_session_id_and_passphrase.py::test_passphrase_on_device": "c9ca2c9cf6dd416dad4de311266690ec2266b551d74f9d3619301305b3dbe81e", "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_enable_passphrase": "b27321ed372b8ade7c4941a80f1f945851046b039a1b43c43a6953106bd1619e",
"test_session_id_and_passphrase.py::test_session_with_passphrase": "a044d7a42229ab7cd74651e03dd64edcceb86d72d50bd63a40336decf0b25d3d", "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"
} }