diff --git a/core/src/apps/common/request_pin.py b/core/src/apps/common/request_pin.py index 7c6ae4cb6..999d1dddc 100644 --- a/core/src/apps/common/request_pin.py +++ b/core/src/apps/common/request_pin.py @@ -1,5 +1,17 @@ -from trezor import loop +from trezor import config, loop, ui, wire +from trezor.messages import ButtonRequestType +from trezor.messages.ButtonAck import ButtonAck +from trezor.messages.ButtonRequest import ButtonRequest +from trezor.pin import pin_to_int from trezor.ui.pin import CANCELLED, PinDialog +from trezor.ui.popup import Popup +from trezor.ui.text import Text + +from apps.common.sd_salt import request_sd_salt +from apps.common.storage import device + +if False: + from typing import Any, Optional, Tuple if __debug__: from apps.debug import input_signal @@ -9,6 +21,10 @@ class PinCancelled(Exception): pass +class PinInvalid(Exception): + pass + + async def request_pin( prompt: str = "Enter your PIN", attempts_remaining: int = None, @@ -31,3 +47,68 @@ async def request_pin( if result is CANCELLED: raise PinCancelled return result + + +async def request_pin_ack(ctx: wire.Context, *args: Any, **kwargs: Any) -> str: + try: + await ctx.call(ButtonRequest(code=ButtonRequestType.Other), ButtonAck) + return await ctx.wait(request_pin(*args, **kwargs)) + except PinCancelled: + raise wire.ActionCancelled("Cancelled") + + +async def request_pin_confirm(ctx: wire.Context, *args: Any, **kwargs: Any) -> str: + while True: + pin1 = await request_pin_ack(ctx, "Enter new PIN", *args, **kwargs) + pin2 = await request_pin_ack(ctx, "Re-enter new PIN", *args, **kwargs) + if pin1 == pin2: + return pin1 + await pin_mismatch() + + +async def pin_mismatch() -> None: + text = Text("PIN mismatch", ui.ICON_WRONG, ui.RED) + text.normal("The PINs you entered", "do not match.") + text.normal("") + text.normal("Please try again.") + popup = Popup(text, 3000) # show for 3 seconds + await popup + + +async def request_pin_and_sd_salt( + ctx: wire.Context, prompt: str = "Enter your PIN", allow_cancel: bool = True +) -> Tuple[str, Optional[bytearray]]: + salt_auth_key = device.get_sd_salt_auth_key() + if salt_auth_key is not None: + salt = await request_sd_salt(ctx, salt_auth_key) # type: Optional[bytearray] + else: + salt = None + + if config.has_pin(): + pin = await request_pin_ack(ctx, prompt, config.get_pin_rem(), allow_cancel) + else: + pin = "" + + return pin, salt + + +async def verify_user_pin( + prompt: str = "Enter your PIN", allow_cancel: bool = True, retry: bool = True +) -> None: + salt_auth_key = device.get_sd_salt_auth_key() + if salt_auth_key is not None: + salt = await request_sd_salt(None, salt_auth_key) # type: Optional[bytearray] + else: + salt = None + + if not config.has_pin() and not config.check_pin(pin_to_int(""), salt): + raise RuntimeError + + while retry: + pin = await request_pin(prompt, config.get_pin_rem(), allow_cancel) + if config.check_pin(pin_to_int(pin), salt): + return + else: + prompt = "Wrong PIN, enter again" + + raise PinInvalid diff --git a/core/src/apps/management/change_pin.py b/core/src/apps/management/change_pin.py index b8dbdf61a..90d0a533e 100644 --- a/core/src/apps/management/change_pin.py +++ b/core/src/apps/management/change_pin.py @@ -1,19 +1,12 @@ from trezor import config, ui, wire -from trezor.messages import ButtonRequestType -from trezor.messages.ButtonAck import ButtonAck -from trezor.messages.ButtonRequest import ButtonRequest from trezor.messages.Success import Success from trezor.pin import pin_to_int -from trezor.ui.popup import Popup from trezor.ui.text import Text from apps.common.confirm import require_confirm -from apps.common.request_pin import PinCancelled, request_pin -from apps.common.sd_salt import request_sd_salt -from apps.common.storage import device +from apps.common.request_pin import request_pin_and_sd_salt, request_pin_confirm if False: - from typing import Any, Optional, Tuple from trezor.messages.ChangePin import ChangePin @@ -65,46 +58,3 @@ def require_confirm_change_pin(ctx: wire.Context, msg: ChangePin) -> None: text.normal("Do you really want to") text.bold("enable PIN protection?") return require_confirm(ctx, text) - - -async def request_pin_confirm(ctx: wire.Context, *args: Any, **kwargs: Any) -> str: - while True: - pin1 = await request_pin_ack(ctx, "Enter new PIN", *args, **kwargs) - pin2 = await request_pin_ack(ctx, "Re-enter new PIN", *args, **kwargs) - if pin1 == pin2: - return pin1 - await pin_mismatch() - - -async def request_pin_and_sd_salt( - ctx: wire.Context, prompt: str = "Enter your PIN", allow_cancel: bool = True -) -> Tuple[str, Optional[bytearray]]: - salt_auth_key = device.get_sd_salt_auth_key() - if salt_auth_key is not None: - salt = await request_sd_salt(ctx, salt_auth_key) # type: Optional[bytearray] - else: - salt = None - - if config.has_pin(): - pin = await request_pin_ack(ctx, prompt, config.get_pin_rem(), allow_cancel) - else: - pin = "" - - return pin, salt - - -async def request_pin_ack(ctx: wire.Context, *args: Any, **kwargs: Any) -> str: - try: - await ctx.call(ButtonRequest(code=ButtonRequestType.Other), ButtonAck) - return await ctx.wait(request_pin(*args, **kwargs)) - except PinCancelled: - raise wire.ActionCancelled("Cancelled") - - -async def pin_mismatch() -> None: - text = Text("PIN mismatch", ui.ICON_WRONG, ui.RED) - text.normal("The PINs you entered", "do not match.") - text.normal("") - text.normal("Please try again.") - popup = Popup(text, 3000) # show for 3 seconds - await popup diff --git a/core/src/apps/management/recovery_device/__init__.py b/core/src/apps/management/recovery_device/__init__.py index 8342c5b23..09b3e1817 100644 --- a/core/src/apps/management/recovery_device/__init__.py +++ b/core/src/apps/management/recovery_device/__init__.py @@ -6,7 +6,7 @@ from trezor.ui.text import Text from apps.common import storage from apps.common.confirm import require_confirm -from apps.management.change_pin import request_pin_and_sd_salt, request_pin_confirm +from apps.common.request_pin import request_pin_and_sd_salt, request_pin_confirm from apps.management.recovery_device.homescreen import recovery_process if False: diff --git a/core/src/apps/management/reset_device.py b/core/src/apps/management/reset_device.py index 1c96c0949..5c5d05930 100644 --- a/core/src/apps/management/reset_device.py +++ b/core/src/apps/management/reset_device.py @@ -10,7 +10,7 @@ from trezor.ui.text import Text from apps.common import mnemonic, storage from apps.common.confirm import require_confirm -from apps.management.change_pin import request_pin_confirm +from apps.common.request_pin import request_pin_confirm from apps.management.common import layout if __debug__: diff --git a/core/src/apps/management/sd_protect.py b/core/src/apps/management/sd_protect.py index fe9c2da36..4d980ed9b 100644 --- a/core/src/apps/management/sd_protect.py +++ b/core/src/apps/management/sd_protect.py @@ -7,6 +7,7 @@ from trezor.pin import pin_to_int from trezor.ui.text import Text from apps.common.confirm import require_confirm +from apps.common.request_pin import request_pin_ack, request_pin_and_sd_salt from apps.common.sd_salt import ( SD_SALT_AUTH_KEY_LEN_BYTES, SD_SALT_AUTH_TAG_LEN_BYTES, @@ -17,7 +18,6 @@ from apps.common.sd_salt import ( stage_sd_salt, ) from apps.common.storage import device, is_initialized -from apps.management.change_pin import request_pin_ack, request_pin_and_sd_salt if False: from trezor.messages.SdProtect import SdProtect diff --git a/core/src/apps/webauthn/__init__.py b/core/src/apps/webauthn/__init__.py index 71a7efff0..ce66aa61b 100644 --- a/core/src/apps/webauthn/__init__.py +++ b/core/src/apps/webauthn/__init__.py @@ -512,20 +512,15 @@ class KeepaliveCallback: send_cmd_sync(cmd_keepalive(self.cid, _KEEPALIVE_STATUS_PROCESSING), self.iface) -async def check_pin(keepalive_callback: KeepaliveCallback) -> bool: - from apps.common.request_pin import PinCancelled, request_pin +async def verify_user(keepalive_callback: KeepaliveCallback) -> bool: + from apps.common.request_pin import verify_user_pin, PinCancelled, PinInvalid import trezor.pin try: trezor.pin.keepalive_callback = keepalive_callback - if config.has_pin(): - pin = await request_pin("Enter your PIN", config.get_pin_rem()) - while config.unlock(trezor.pin.pin_to_int(pin)) is not True: - pin = await request_pin("Wrong PIN, enter again", config.get_pin_rem()) - ret = True - else: - ret = config.unlock(trezor.pin.pin_to_int("")) - except PinCancelled: + await verify_user_pin() + ret = True + except (PinCancelled, PinInvalid): ret = False finally: trezor.pin.keepalive_callback = None @@ -695,7 +690,7 @@ class Fido2ConfirmMakeCredential(Fido2State, ConfirmInfo): if not await confirm(content): return False if self._user_verification: - return await check_pin(KeepaliveCallback(self.cid, self.iface)) + return await verify_user(KeepaliveCallback(self.cid, self.iface)) return True async def on_confirm(self) -> None: @@ -764,7 +759,7 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable): if await ConfirmPageable(self, content) is not CONFIRMED: return False if self._user_verification: - return await check_pin(KeepaliveCallback(self.cid, self.iface)) + return await verify_user(KeepaliveCallback(self.cid, self.iface)) return True async def on_confirm(self) -> None: diff --git a/core/src/trezor/ui/pin.py b/core/src/trezor/ui/pin.py index a69cc0526..d25b1136b 100644 --- a/core/src/trezor/ui/pin.py +++ b/core/src/trezor/ui/pin.py @@ -12,7 +12,7 @@ from trezor.ui.button import ( ) if False: - from typing import Iterable + from typing import Iterable, Optional def digit_area(i: int) -> ui.Area: @@ -30,7 +30,7 @@ def generate_digits() -> Iterable[int]: class PinInput(ui.Component): - def __init__(self, prompt: str, subprompt: str, pin: str) -> None: + def __init__(self, prompt: str, subprompt: Optional[str], pin: str) -> None: self.prompt = prompt self.subprompt = subprompt self.pin = pin @@ -82,7 +82,11 @@ CANCELLED = object() class PinDialog(ui.Layout): def __init__( - self, prompt: str, subprompt: str, allow_cancel: bool = True, maxlength: int = 9 + self, + prompt: str, + subprompt: Optional[str], + allow_cancel: bool = True, + maxlength: int = 9, ) -> None: self.maxlength = maxlength self.input = PinInput(prompt, subprompt, "")