From 795fa078226b6ddc1613fee244823aebe47b4e61 Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Tue, 24 Sep 2019 15:32:39 +0200 Subject: [PATCH] core/sd-protect: Add SD_CARD_HOT_SWAPPABLE option and improve error handling. --- core/src/apps/common/sd_salt.py | 168 +++++++++++++++---------- core/src/apps/management/sd_protect.py | 10 +- 2 files changed, 103 insertions(+), 75 deletions(-) diff --git a/core/src/apps/common/sd_salt.py b/core/src/apps/common/sd_salt.py index 09b26eb47a..dfc909f6d6 100644 --- a/core/src/apps/common/sd_salt.py +++ b/core/src/apps/common/sd_salt.py @@ -3,12 +3,12 @@ from micropython import const from trezor import io, ui, wire from trezor.crypto import hmac from trezor.crypto.hashlib import sha256 -from trezor.ui.confirm import Confirm +from trezor.ui.confirm import CONFIRMED, Confirm from trezor.ui.text import Text from trezor.utils import consteq from apps.common import storage -from apps.common.confirm import require_confirm +from apps.common.confirm import confirm if False: from typing import Optional @@ -18,6 +18,7 @@ class SdProtectCancelled(Exception): pass +SD_CARD_HOT_SWAPPABLE = False SD_SALT_LEN_BYTES = const(32) SD_SALT_AUTH_TAG_LEN_BYTES = const(16) SD_SALT_AUTH_KEY_LEN_BYTES = const(16) @@ -27,22 +28,53 @@ async def _wrong_card_dialog(ctx: Optional[wire.Context]) -> None: text = Text("SD card protection", ui.ICON_WRONG) text.bold("Wrong SD card.") text.br_half() - text.normal("Please unplug the", "device and insert a", "different card.") - if ctx is None: - await Confirm(text, confirm=None, cancel="Close") + if SD_CARD_HOT_SWAPPABLE: + text.normal("Please insert the", "correct SD card for", "this device.") + btn_confirm = "Retry" + btn_cancel = "Abort" else: - await require_confirm(ctx, text, confirm=None, cancel="Close") + text.normal("Please unplug the", "device and insert the", "correct SD card.") + btn_confirm = None + btn_cancel = "Close" + + if ctx is None: + if await Confirm(text, confirm=btn_confirm, cancel=btn_cancel) is not CONFIRMED: + raise SdProtectCancelled + else: + if not await confirm(ctx, text, confirm=btn_confirm, cancel=btn_cancel): + raise wire.ProcessError("Wrong SD card.") async def _insert_card_dialog(ctx: Optional[wire.Context]) -> None: - text = Text("SD card protection") + text = Text("SD card protection", ui.ICON_WRONG) text.bold("SD card required.") text.br_half() - text.normal("Please unplug the", "device and insert your", "SD card.") + if SD_CARD_HOT_SWAPPABLE: + text.normal("Please insert your", "SD card.") + btn_confirm = "Retry" + btn_cancel = "Abort" + else: + text.normal("Please unplug the", "device and insert your", "SD card.") + btn_confirm = None + btn_cancel = "Close" + + if ctx is None: + if await Confirm(text, confirm=btn_confirm, cancel=btn_cancel) is not CONFIRMED: + raise SdProtectCancelled + else: + if not await confirm(ctx, text, confirm=btn_confirm, cancel=btn_cancel): + raise wire.ProcessError("SD card required.") + + +async def _write_failed_dialog(ctx: Optional[wire.Context]) -> None: + text = Text("SD card protection", ui.ICON_WRONG, ui.RED) + text.normal("Failed to write data to", "the SD card.") if ctx is None: await Confirm(text, confirm=None, cancel="Close") + raise OSError else: - await require_confirm(ctx, text, confirm=None, cancel="Close") + await confirm(ctx, text, confirm=None, cancel="Close") + raise wire.ProcessError("Failed to write to SD card.") def _get_device_dir() -> str: @@ -62,63 +94,64 @@ async def request_sd_salt( salt_path = _get_salt_path() new_salt_path = _get_salt_path(True) - sd = io.SDCard() - fs = io.FatFS() - if not sd.power(True): - await _insert_card_dialog(ctx) - raise SdProtectCancelled + while True: + sd = io.SDCard() + fs = io.FatFS() + while not sd.power(True): + await _insert_card_dialog(ctx) - try: - fs.mount() - - # Load salt if it exists. - salt = None # type: Optional[bytearray] try: - with fs.open(salt_path, "r") as f: - salt = bytearray(SD_SALT_LEN_BYTES) - salt_tag = bytearray(SD_SALT_AUTH_TAG_LEN_BYTES) - f.read(salt) - f.read(salt_tag) - except OSError: - salt = None + fs.mount() - if salt is not None and consteq( - hmac.new(salt_auth_key, salt, sha256).digest()[:SD_SALT_AUTH_TAG_LEN_BYTES], - salt_tag, - ): - return salt - - # Load salt.new if it exists. - new_salt = None # type: Optional[bytearray] - try: - with fs.open(new_salt_path, "r") as f: - new_salt = bytearray(SD_SALT_LEN_BYTES) - new_salt_tag = bytearray(SD_SALT_AUTH_TAG_LEN_BYTES) - f.read(new_salt) - f.read(new_salt_tag) - except OSError: - new_salt = None - - if new_salt is not None and consteq( - hmac.new(salt_auth_key, new_salt, sha256).digest()[ - :SD_SALT_AUTH_TAG_LEN_BYTES - ], - new_salt_tag, - ): - # SD salt regeneration was interrupted earlier. Bring into consistent state. - # TODO Possibly overwrite salt file with random data. + # Load salt if it exists. + salt = None # type: Optional[bytearray] try: - fs.unlink(salt_path) + with fs.open(salt_path, "r") as f: + salt = bytearray(SD_SALT_LEN_BYTES) + salt_tag = bytearray(SD_SALT_AUTH_TAG_LEN_BYTES) + f.read(salt) + f.read(salt_tag) except OSError: - pass - fs.rename(new_salt_path, salt_path) - return new_salt - finally: - fs.unmount() - sd.power(False) + salt = None - await _wrong_card_dialog(ctx) - raise SdProtectCancelled + if salt is not None and consteq( + hmac.new(salt_auth_key, salt, sha256).digest()[ + :SD_SALT_AUTH_TAG_LEN_BYTES + ], + salt_tag, + ): + return salt + + # Load salt.new if it exists. + new_salt = None # type: Optional[bytearray] + try: + with fs.open(new_salt_path, "r") as f: + new_salt = bytearray(SD_SALT_LEN_BYTES) + new_salt_tag = bytearray(SD_SALT_AUTH_TAG_LEN_BYTES) + f.read(new_salt) + f.read(new_salt_tag) + except OSError: + new_salt = None + + if new_salt is not None and consteq( + hmac.new(salt_auth_key, new_salt, sha256).digest()[ + :SD_SALT_AUTH_TAG_LEN_BYTES + ], + new_salt_tag, + ): + # SD salt regeneration was interrupted earlier. Bring into consistent state. + # TODO Possibly overwrite salt file with random data. + try: + fs.unlink(salt_path) + except OSError: + pass + fs.rename(new_salt_path, salt_path) + return new_salt + finally: + fs.unmount() + sd.power(False) + + await _wrong_card_dialog(ctx) async def set_sd_salt( @@ -127,9 +160,8 @@ async def set_sd_salt( salt_path = _get_salt_path(new) sd = io.SDCard() - if not sd.power(True): + while not sd.power(True): await _insert_card_dialog(ctx) - raise SdProtectCancelled fs = io.FatFS() @@ -140,9 +172,13 @@ async def set_sd_salt( with fs.open(salt_path, "w") as f: f.write(salt) f.write(salt_tag) - finally: + except Exception: fs.unmount() sd.power(False) + await _write_failed_dialog(ctx) + + fs.unmount() + sd.power(False) async def stage_sd_salt( @@ -158,8 +194,7 @@ async def commit_sd_salt(ctx: Optional[wire.Context]) -> None: sd = io.SDCard() fs = io.FatFS() if not sd.power(True): - await _insert_card_dialog(ctx) - raise SdProtectCancelled + raise IOError try: fs.mount() @@ -180,8 +215,7 @@ async def remove_sd_salt(ctx: Optional[wire.Context]) -> None: sd = io.SDCard() fs = io.FatFS() if not sd.power(True): - await _insert_card_dialog(ctx) - raise SdProtectCancelled + raise IOError try: fs.mount() diff --git a/core/src/apps/management/sd_protect.py b/core/src/apps/management/sd_protect.py index fce5608daa..2baa3de536 100644 --- a/core/src/apps/management/sd_protect.py +++ b/core/src/apps/management/sd_protect.py @@ -62,10 +62,7 @@ async def sd_protect_enable(ctx: wire.Context, msg: SdProtect) -> Success: salt_tag = hmac.new(salt_auth_key, salt, sha256).digest()[ :SD_SALT_AUTH_TAG_LEN_BYTES ] - try: - await set_sd_salt(ctx, salt, salt_tag) - except Exception: - raise wire.ProcessError("Failed to write to SD card") + await set_sd_salt(ctx, salt, salt_tag) if not config.change_pin(pin, pin, None, salt): # Wrong PIN. Clean up the prepared salt file. @@ -131,10 +128,7 @@ async def sd_protect_refresh(ctx: wire.Context, msg: SdProtect) -> Success: new_salt_tag = hmac.new(new_salt_auth_key, new_salt, sha256).digest()[ :SD_SALT_AUTH_TAG_LEN_BYTES ] - try: - await stage_sd_salt(ctx, new_salt, new_salt_tag) - except Exception: - raise wire.ProcessError("Failed to write to SD card") + await stage_sd_salt(ctx, new_salt, new_salt_tag) if not config.change_pin(pin_to_int(pin), pin_to_int(pin), old_salt, new_salt): await show_pin_invalid(ctx)