You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/src/apps/common/request_pin.py

151 lines
4.1 KiB

import utime
import storage.sd_salt
from trezor import config, ui, wire
from trezor.messages import ButtonRequestType
from trezor.ui.components.tt.pin import CANCELLED, PinDialog
from trezor.ui.components.tt.text import Text
from trezor.ui.popup import Popup
from . import button_request
from .sdcard import SdCardUnavailable, request_sd_salt
if False:
from typing import Any, NoReturn
_last_successful_unlock = 0
def can_lock_device() -> bool:
"""Return True if the device has a PIN set or SD-protect enabled."""
return config.has_pin() or storage.sd_salt.is_enabled()
async def request_pin(
ctx: wire.GenericContext,
prompt: str = "Enter your PIN",
attempts_remaining: int | None = None,
allow_cancel: bool = True,
) -> str:
await button_request(ctx, code=ButtonRequestType.PinEntry)
if attempts_remaining is None:
subprompt = None
elif attempts_remaining == 1:
subprompt = "This is your last attempt"
else:
subprompt = "%s attempts remaining" % attempts_remaining
dialog = PinDialog(prompt, subprompt, allow_cancel)
while True:
pin = await ctx.wait(dialog)
if pin is CANCELLED:
raise wire.PinCancelled
assert isinstance(pin, str)
return pin
async def request_pin_confirm(ctx: wire.Context, *args: Any, **kwargs: Any) -> str:
while True:
pin1 = await request_pin(ctx, "Enter new PIN", *args, **kwargs)
pin2 = await request_pin(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, bytearray | None]:
if config.has_pin():
pin = await request_pin(ctx, prompt, config.get_pin_rem(), allow_cancel)
config.ensure_not_wipe_code(pin)
else:
pin = ""
salt = await request_sd_salt(ctx)
return pin, salt
async def verify_user_pin(
ctx: wire.GenericContext = wire.DUMMY_CONTEXT,
prompt: str = "Enter your PIN",
allow_cancel: bool = True,
retry: bool = True,
cache_time_ms: int = 0,
) -> None:
global _last_successful_unlock
if (
cache_time_ms
and _last_successful_unlock
and utime.ticks_ms() - _last_successful_unlock <= cache_time_ms
and config.is_unlocked()
):
return
if config.has_pin():
pin = await request_pin(ctx, prompt, config.get_pin_rem(), allow_cancel)
config.ensure_not_wipe_code(pin)
else:
pin = ""
try:
salt = await request_sd_salt(ctx)
except SdCardUnavailable:
raise wire.PinCancelled("SD salt is unavailable")
if config.unlock(pin, salt):
_last_successful_unlock = utime.ticks_ms()
return
elif not config.has_pin():
raise RuntimeError
while retry:
pin = await request_pin(
ctx, "Wrong PIN, enter again", config.get_pin_rem(), allow_cancel
)
if config.unlock(pin, salt):
_last_successful_unlock = utime.ticks_ms()
return
raise wire.PinInvalid
async def error_pin_invalid(ctx: wire.Context) -> NoReturn:
from trezor.ui.layouts import show_error_and_raise
await show_error_and_raise(
ctx,
"warning_wrong_pin",
header="Wrong PIN",
content="The PIN you entered is invalid.",
red=True,
exc=wire.PinInvalid,
)
assert False
async def error_pin_matches_wipe_code(ctx: wire.Context) -> NoReturn:
from trezor.ui.layouts import show_error_and_raise
await show_error_and_raise(
ctx,
"warning_invalid_new_pin",
header="Invalid PIN",
content="The new PIN must be different from your\nwipe code.",
red=True,
exc=wire.PinInvalid,
)
assert False