diff --git a/core/src/apps/webauthn/fido2.py b/core/src/apps/webauthn/fido2.py index 50bdcce48..5a5549450 100644 --- a/core/src/apps/webauthn/fido2.py +++ b/core/src/apps/webauthn/fido2.py @@ -12,6 +12,7 @@ from trezor.ui.confirm import CONFIRMED, Confirm, ConfirmPageable, Pageable from trezor.ui.popup import Popup from trezor.ui.text import Text +from apps.base import set_homescreen from apps.common import cbor from apps.webauthn import common from apps.webauthn.confirm import ConfirmContent, ConfirmInfo @@ -710,6 +711,28 @@ class U2fConfirmAuthenticate(U2fState): ) +class U2fUnlock(State): + def keepalive_status(self) -> int: + return _KEEPALIVE_STATUS_NONE + + def timeout_ms(self) -> int: + return _U2F_CONFIRM_TIMEOUT_MS + + async def confirm_dialog(self) -> bool: + from trezor.wire import PinCancelled, PinInvalid + from apps.common.request_pin import verify_user_pin + + try: + await verify_user_pin() + set_homescreen() + except (PinCancelled, PinInvalid): + return False + return True + + def __eq__(self, other: object) -> bool: + return isinstance(other, U2fUnlock) + + class Fido2State(State): def __init__(self, cid: int, iface: io.HID) -> None: super().__init__(cid, iface) @@ -975,15 +998,11 @@ class DialogManager: return True - def compare(self, state: State) -> bool: - if self.state != state: - return False - if utime.ticks_ms() >= self.deadline: - self.reset() - return False - return True - def set_state(self, state: State) -> bool: + if self.state == state and utime.ticks_ms() < self.deadline: + self.reset_timeout() + return True + if self.is_busy(): return False @@ -999,7 +1018,7 @@ class DialogManager: async def keepalive_loop(self) -> None: try: - if not isinstance(self.state, (U2fState, Fido2State)): + if not isinstance(self.state, (U2fState, U2fUnlock, Fido2State)): return while utime.ticks_ms() < self.deadline: if self.state.keepalive_status() != _KEEPALIVE_STATUS_NONE: @@ -1161,6 +1180,11 @@ def cmd_wink(req: Cmd) -> Cmd: def msg_register(req: Msg, dialog_mgr: DialogManager) -> Cmd: + if not config.is_unlocked(): + new_state = U2fUnlock(req.cid, dialog_mgr.iface) # type: State + dialog_mgr.set_state(new_state) + return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) + if not storage.is_initialized(): if __debug__: log.warning(__name__, "not initialized") @@ -1182,10 +1206,8 @@ def msg_register(req: Msg, dialog_mgr: DialogManager) -> Cmd: # check equality with last request new_state = U2fConfirmRegister(req.cid, dialog_mgr.iface, req.data, cred) - if not dialog_mgr.compare(new_state): - if not dialog_mgr.set_state(new_state): - return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) - dialog_mgr.reset_timeout() + if not dialog_mgr.set_state(new_state): + return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # wait for a button or continue if dialog_mgr.result == _RESULT_NONE: @@ -1239,6 +1261,11 @@ def msg_register_sign(challenge: bytes, cred: U2fCredential) -> bytes: def msg_authenticate(req: Msg, dialog_mgr: DialogManager) -> Cmd: + if not config.is_unlocked(): + new_state = U2fUnlock(req.cid, dialog_mgr.iface) # type: State + dialog_mgr.set_state(new_state) + return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) + if not storage.is_initialized(): if __debug__: log.warning(__name__, "not initialized") @@ -1280,10 +1307,8 @@ def msg_authenticate(req: Msg, dialog_mgr: DialogManager) -> Cmd: # check equality with last request new_state = U2fConfirmAuthenticate(req.cid, dialog_mgr.iface, req.data, cred) - if not dialog_mgr.compare(new_state): - if not dialog_mgr.set_state(new_state): - return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) - dialog_mgr.reset_timeout() + if not dialog_mgr.set_state(new_state): + return msg_error(req.cid, _SW_CONDITIONS_NOT_SATISFIED) # wait for a button or continue if dialog_mgr.result == _RESULT_NONE: