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/management/sd_protect.py

174 lines
6.1 KiB

import storage.device
import storage.sd_salt
from trezor import config, wire
from trezor.crypto import random
from trezor.messages import SdProtectOperationType
from trezor.messages.Success import Success
from trezor.ui.layouts import confirm_action, show_success
from apps.common.request_pin import (
error_pin_invalid,
request_pin,
request_pin_and_sd_salt,
)
from apps.common.sdcard import confirm_retry_sd, ensure_sdcard
if False:
from typing import Awaitable
from trezor.messages.SdProtect import SdProtect
def _make_salt() -> tuple[bytes, bytes, bytes]:
salt = random.bytes(storage.sd_salt.SD_SALT_LEN_BYTES)
auth_key = random.bytes(storage.device.SD_SALT_AUTH_KEY_LEN_BYTES)
tag = storage.sd_salt.compute_auth_tag(salt, auth_key)
return salt, auth_key, tag
async def _set_salt(
ctx: wire.Context, salt: bytes, salt_tag: bytes, stage: bool = False
) -> None:
while True:
await ensure_sdcard(ctx)
try:
return storage.sd_salt.set_sd_salt(salt, salt_tag, stage)
except OSError:
await confirm_retry_sd(ctx, exc=wire.ProcessError("SD card I/O error."))
async def sd_protect(ctx: wire.Context, msg: SdProtect) -> Success:
if not storage.device.is_initialized():
raise wire.NotInitialized("Device is not initialized")
if msg.operation == SdProtectOperationType.ENABLE:
return await sd_protect_enable(ctx, msg)
elif msg.operation == SdProtectOperationType.DISABLE:
return await sd_protect_disable(ctx, msg)
elif msg.operation == SdProtectOperationType.REFRESH:
return await sd_protect_refresh(ctx, msg)
else:
raise wire.ProcessError("Unknown operation")
async def sd_protect_enable(ctx: wire.Context, msg: SdProtect) -> Success:
if storage.sd_salt.is_enabled():
raise wire.ProcessError("SD card protection already enabled")
# Confirm that user wants to proceed with the operation.
await require_confirm_sd_protect(ctx, msg)
# Make sure SD card is present.
await ensure_sdcard(ctx)
# Get the current PIN.
if config.has_pin():
pin = await request_pin(ctx, "Enter PIN", config.get_pin_rem())
else:
pin = ""
# Check PIN and prepare salt file.
salt, salt_auth_key, salt_tag = _make_salt()
await _set_salt(ctx, salt, salt_tag)
if not config.change_pin(pin, pin, None, salt):
# Wrong PIN. Clean up the prepared salt file.
try:
storage.sd_salt.remove_sd_salt()
except Exception:
# The cleanup is not necessary for the correct functioning of
# SD-protection. If it fails for any reason, we suppress the
# exception, because primarily we need to raise wire.PinInvalid.
pass
await error_pin_invalid(ctx)
storage.device.set_sd_salt_auth_key(salt_auth_key)
await show_success(
ctx, "success_sd", "You have successfully enabled SD protection."
)
return Success(message="SD card protection enabled")
async def sd_protect_disable(ctx: wire.Context, msg: SdProtect) -> Success:
if not storage.sd_salt.is_enabled():
raise wire.ProcessError("SD card protection not enabled")
# Note that the SD card doesn't need to be present in order to disable SD
# protection. The cleanup will not happen in such case, but that does not matter.
# Confirm that user wants to proceed with the operation.
await require_confirm_sd_protect(ctx, msg)
# Get the current PIN and salt from the SD card.
pin, salt = await request_pin_and_sd_salt(ctx, "Enter PIN")
# Check PIN and remove salt.
if not config.change_pin(pin, pin, salt, None):
await error_pin_invalid(ctx)
storage.device.set_sd_salt_auth_key(None)
try:
# Clean up.
storage.sd_salt.remove_sd_salt()
except Exception:
# The cleanup is not necessary for the correct functioning of
# SD-protection. If it fails for any reason, we suppress the exception,
# because overall SD-protection was successfully disabled.
pass
await show_success(
ctx, "success_sd", "You have successfully disabled SD protection."
)
return Success(message="SD card protection disabled")
async def sd_protect_refresh(ctx: wire.Context, msg: SdProtect) -> Success:
if not storage.sd_salt.is_enabled():
raise wire.ProcessError("SD card protection not enabled")
# Confirm that user wants to proceed with the operation.
await require_confirm_sd_protect(ctx, msg)
# Make sure SD card is present.
await ensure_sdcard(ctx)
# Get the current PIN and salt from the SD card.
pin, old_salt = await request_pin_and_sd_salt(ctx, "Enter PIN")
# Check PIN and change salt.
new_salt, new_auth_key, new_salt_tag = _make_salt()
await _set_salt(ctx, new_salt, new_salt_tag, stage=True)
if not config.change_pin(pin, pin, old_salt, new_salt):
await error_pin_invalid(ctx)
storage.device.set_sd_salt_auth_key(new_auth_key)
try:
# Clean up.
storage.sd_salt.commit_sd_salt()
except Exception:
# If the cleanup fails, then request_sd_salt() will bring the SD card
# into a consistent state. We suppress the exception, because overall
# SD-protection was successfully refreshed.
pass
await show_success(
ctx, "success_sd", "You have successfully refreshed SD protection."
)
return Success(message="SD card protection refreshed")
def require_confirm_sd_protect(ctx: wire.Context, msg: SdProtect) -> Awaitable[None]:
if msg.operation == SdProtectOperationType.ENABLE:
text = "Do you really want to secure your device with SD card protection?"
elif msg.operation == SdProtectOperationType.DISABLE:
text = "Do you really want to remove SD card protection from your device?"
elif msg.operation == SdProtectOperationType.REFRESH:
text = "Do you really want to replace the current\nSD card secret with a newly generated one?"
else:
raise wire.ProcessError("Unknown operation")
return confirm_action(ctx, "set_sd", "SD card protection", description=text)