mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 15:38:11 +00:00
Merge pull request #720 from trezor/andrewkozlik/wipe-code
Implement wipe code feature.
This commit is contained in:
commit
6ca0288092
@ -33,6 +33,7 @@ message Failure {
|
||||
Failure_NotEnoughFunds = 10;
|
||||
Failure_NotInitialized = 11;
|
||||
Failure_PinMismatch = 12;
|
||||
Failure_WipeCodeMismatch = 13;
|
||||
Failure_FirmwareError = 99;
|
||||
}
|
||||
}
|
||||
@ -90,6 +91,8 @@ message PinMatrixRequest {
|
||||
PinMatrixRequestType_Current = 1;
|
||||
PinMatrixRequestType_NewFirst = 2;
|
||||
PinMatrixRequestType_NewSecond = 3;
|
||||
PinMatrixRequestType_WipeCodeFirst = 4;
|
||||
PinMatrixRequestType_WipeCodeSecond = 5;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,6 +87,7 @@ message Features {
|
||||
optional BackupType backup_type = 31; // type of device backup (BIP-39 / SLIP-39 basic / SLIP-39 advanced)
|
||||
optional bool sd_card_present = 32; // is SD card present
|
||||
optional bool sd_protection = 33; // is SD Protect enabled
|
||||
optional bool wipe_code_protection = 34; // is wipe code protection enabled
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,6 +142,16 @@ message ChangePin {
|
||||
optional bool remove = 1; // is PIN removal requested?
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Starts workflow for setting/removing the wipe code
|
||||
* @start
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message ChangeWipeCode {
|
||||
optional bool remove = 1; // is wipe code removal requested?
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Starts workflow for enabling/regenerating/disabling SD card protection
|
||||
* @start
|
||||
|
@ -64,6 +64,7 @@ enum MessageType {
|
||||
MessageType_SdProtect = 79 [(wire_in) = true];
|
||||
MessageType_GetNextU2FCounter = 80 [(wire_in) = true];
|
||||
MessageType_NextU2FCounter = 81 [(wire_out) = true];
|
||||
MessageType_ChangeWipeCode = 82 [(wire_in) = true];
|
||||
|
||||
// Bootloader
|
||||
MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true];
|
||||
|
@ -173,6 +173,49 @@ STATIC mp_obj_t mod_trezorconfig_change_pin(size_t n_args,
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorconfig_change_pin_obj, 4,
|
||||
4, mod_trezorconfig_change_pin);
|
||||
|
||||
/// def has_wipe_code() -> bool:
|
||||
/// """
|
||||
/// Returns True if storage has a configured wipe code, False otherwise.
|
||||
/// """
|
||||
STATIC mp_obj_t mod_trezorconfig_has_wipe_code(void) {
|
||||
if (sectrue != storage_has_wipe_code()) {
|
||||
return mp_const_false;
|
||||
}
|
||||
return mp_const_true;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorconfig_has_wipe_code_obj,
|
||||
mod_trezorconfig_has_wipe_code);
|
||||
|
||||
/// def change_wipe_code(
|
||||
/// pin: int,
|
||||
/// ext_salt: Optional[bytes],
|
||||
/// wipe_code: int,
|
||||
/// ) -> bool:
|
||||
/// """
|
||||
/// Change wipe code. Returns True on success, False on failure.
|
||||
/// """
|
||||
STATIC mp_obj_t mod_trezorconfig_change_wipe_code(size_t n_args,
|
||||
const mp_obj_t *args) {
|
||||
uint32_t pin = trezor_obj_get_uint(args[0]);
|
||||
uint32_t wipe_code = trezor_obj_get_uint(args[2]);
|
||||
mp_buffer_info_t ext_salt_b;
|
||||
const uint8_t *ext_salt = NULL;
|
||||
if (args[1] != mp_const_none) {
|
||||
mp_get_buffer_raise(args[1], &ext_salt_b, MP_BUFFER_READ);
|
||||
if (ext_salt_b.len != EXTERNAL_SALT_SIZE)
|
||||
mp_raise_msg(&mp_type_ValueError, "Invalid length of external salt.");
|
||||
ext_salt = ext_salt_b.buf;
|
||||
}
|
||||
|
||||
if (sectrue != storage_change_wipe_code(pin, ext_salt, wipe_code)) {
|
||||
return mp_const_false;
|
||||
}
|
||||
return mp_const_true;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
|
||||
mod_trezorconfig_change_wipe_code_obj, 3, 3,
|
||||
mod_trezorconfig_change_wipe_code);
|
||||
|
||||
/// def get(app: int, key: int, public: bool = False) -> Optional[bytes]:
|
||||
/// """
|
||||
/// Gets the value of the given key for the given app (or None if not set).
|
||||
@ -324,6 +367,10 @@ STATIC const mp_rom_map_elem_t mp_module_trezorconfig_globals_table[] = {
|
||||
MP_ROM_PTR(&mod_trezorconfig_get_pin_rem_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_change_pin),
|
||||
MP_ROM_PTR(&mod_trezorconfig_change_pin_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_has_wipe_code),
|
||||
MP_ROM_PTR(&mod_trezorconfig_has_wipe_code_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_change_wipe_code),
|
||||
MP_ROM_PTR(&mod_trezorconfig_change_wipe_code_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&mod_trezorconfig_get_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&mod_trezorconfig_set_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_delete), MP_ROM_PTR(&mod_trezorconfig_delete_obj)},
|
||||
|
@ -46,6 +46,6 @@
|
||||
/*
|
||||
* Current storage version.
|
||||
*/
|
||||
#define NORCOW_VERSION ((uint32_t)0x00000001)
|
||||
#define NORCOW_VERSION ((uint32_t)0x00000002)
|
||||
|
||||
#endif
|
||||
|
@ -60,6 +60,24 @@ def change_pin(
|
||||
"""
|
||||
|
||||
|
||||
# extmod/modtrezorconfig/modtrezorconfig.c
|
||||
def has_wipe_code() -> bool:
|
||||
"""
|
||||
Returns True if storage has a configured wipe code, False otherwise.
|
||||
"""
|
||||
|
||||
|
||||
# extmod/modtrezorconfig/modtrezorconfig.c
|
||||
def change_wipe_code(
|
||||
pin: int,
|
||||
ext_salt: Optional[bytes],
|
||||
wipe_code: int,
|
||||
) -> bool:
|
||||
"""
|
||||
Change wipe code. Returns True on success, False on failure.
|
||||
"""
|
||||
|
||||
|
||||
# extmod/modtrezorconfig/modtrezorconfig.c
|
||||
def get(app: int, key: int, public: bool = False) -> Optional[bytes]:
|
||||
"""
|
||||
|
@ -117,3 +117,11 @@ async def show_pin_invalid(ctx: wire.Context) -> None:
|
||||
text = Text("Wrong PIN", ui.ICON_WRONG, ui.RED)
|
||||
text.normal("The PIN you entered is", "invalid.")
|
||||
await confirm(ctx, text, confirm=None, cancel="Close")
|
||||
|
||||
|
||||
async def show_pin_matches_wipe_code(ctx: wire.Context) -> None:
|
||||
from apps.common.confirm import confirm
|
||||
|
||||
text = Text("Invalid PIN", ui.ICON_WRONG, ui.RED)
|
||||
text.normal("The new PIN must be", "different from your", "wipe code.")
|
||||
await confirm(ctx, text, confirm=None, cancel="Close")
|
||||
|
@ -70,6 +70,7 @@ def get_features() -> Features:
|
||||
]
|
||||
f.sd_card_present = io.SDCard().present()
|
||||
f.sd_protection = storage.sd_salt.is_enabled()
|
||||
f.wipe_code_protection = config.has_wipe_code()
|
||||
return f
|
||||
|
||||
|
||||
|
@ -13,3 +13,4 @@ def boot() -> None:
|
||||
wire.add(MessageType.SetU2FCounter, __name__, "set_u2f_counter")
|
||||
wire.add(MessageType.GetNextU2FCounter, __name__, "get_next_u2f_counter")
|
||||
wire.add(MessageType.SdProtect, __name__, "sd_protect")
|
||||
wire.add(MessageType.ChangeWipeCode, __name__, "change_wipe_code")
|
||||
|
@ -10,6 +10,7 @@ from apps.common.request_pin import (
|
||||
request_pin_and_sd_salt,
|
||||
request_pin_confirm,
|
||||
show_pin_invalid,
|
||||
show_pin_matches_wipe_code,
|
||||
)
|
||||
|
||||
if False:
|
||||
@ -40,7 +41,10 @@ async def change_pin(ctx: wire.Context, msg: ChangePin) -> Success:
|
||||
|
||||
# write into storage
|
||||
if not config.change_pin(pin_to_int(curpin), pin_to_int(newpin), salt, salt):
|
||||
await show_pin_invalid(ctx)
|
||||
if newpin:
|
||||
await show_pin_matches_wipe_code(ctx)
|
||||
else:
|
||||
await show_pin_invalid(ctx)
|
||||
raise wire.PinInvalid("PIN invalid")
|
||||
|
||||
if newpin:
|
||||
|
116
core/src/apps/management/change_wipe_code.py
Normal file
116
core/src/apps/management/change_wipe_code.py
Normal file
@ -0,0 +1,116 @@
|
||||
from storage import is_initialized
|
||||
from trezor import config, ui, wire
|
||||
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.layout import show_success
|
||||
from apps.common.request_pin import (
|
||||
request_pin_ack,
|
||||
request_pin_and_sd_salt,
|
||||
show_pin_invalid,
|
||||
)
|
||||
|
||||
if False:
|
||||
from trezor.messages.ChangeWipeCode import ChangeWipeCode
|
||||
|
||||
|
||||
async def change_wipe_code(ctx: wire.Context, msg: ChangeWipeCode) -> Success:
|
||||
if not is_initialized():
|
||||
raise wire.NotInitialized("Device is not initialized")
|
||||
|
||||
# Confirm that user wants to set or remove the wipe code.
|
||||
has_wipe_code = config.has_wipe_code()
|
||||
await _require_confirm_action(ctx, msg, has_wipe_code)
|
||||
|
||||
# Get the unlocking PIN.
|
||||
pin, salt = await request_pin_and_sd_salt(ctx)
|
||||
|
||||
if not msg.remove:
|
||||
# Pre-check the entered PIN.
|
||||
if config.has_pin() and not config.check_pin(pin_to_int(pin), salt):
|
||||
await show_pin_invalid(ctx)
|
||||
raise wire.PinInvalid("PIN invalid")
|
||||
|
||||
# Get new wipe code.
|
||||
wipe_code = await _request_wipe_code_confirm(ctx, pin)
|
||||
else:
|
||||
wipe_code = ""
|
||||
|
||||
# Write into storage.
|
||||
if not config.change_wipe_code(pin_to_int(pin), salt, pin_to_int(wipe_code)):
|
||||
await show_pin_invalid(ctx)
|
||||
raise wire.PinInvalid("PIN invalid")
|
||||
|
||||
if wipe_code:
|
||||
if has_wipe_code:
|
||||
msg_screen = "changed the wipe code."
|
||||
msg_wire = "Wipe code changed"
|
||||
else:
|
||||
msg_screen = "set the wipe code."
|
||||
msg_wire = "Wipe code set"
|
||||
else:
|
||||
msg_screen = "disabled the wipe code."
|
||||
msg_wire = "Wipe code removed"
|
||||
|
||||
await show_success(ctx, ("You have successfully", msg_screen))
|
||||
return Success(message=msg_wire)
|
||||
|
||||
|
||||
def _require_confirm_action(
|
||||
ctx: wire.Context, msg: ChangeWipeCode, has_wipe_code: bool
|
||||
) -> None:
|
||||
if msg.remove and has_wipe_code:
|
||||
text = Text("Disable wipe code", ui.ICON_CONFIG)
|
||||
text.normal("Do you really want to")
|
||||
text.bold("disable wipe code")
|
||||
text.bold("protection?")
|
||||
return require_confirm(ctx, text)
|
||||
|
||||
if not msg.remove and has_wipe_code:
|
||||
text = Text("Change wipe code", ui.ICON_CONFIG)
|
||||
text.normal("Do you really want to")
|
||||
text.bold("change the wipe code?")
|
||||
return require_confirm(ctx, text)
|
||||
|
||||
if not msg.remove and not has_wipe_code:
|
||||
text = Text("Set wipe code", ui.ICON_CONFIG)
|
||||
text.normal("Do you really want to")
|
||||
text.bold("set the wipe code?")
|
||||
return require_confirm(ctx, text)
|
||||
|
||||
# Removing non-existing wipe code.
|
||||
raise wire.ProcessError("Wipe code protection is already disabled")
|
||||
|
||||
|
||||
async def _request_wipe_code_confirm(ctx: wire.Context, pin: str) -> str:
|
||||
while True:
|
||||
code1 = await request_pin_ack(ctx, "Enter new wipe code")
|
||||
if code1 == pin:
|
||||
await _wipe_code_invalid()
|
||||
continue
|
||||
|
||||
code2 = await request_pin_ack(ctx, "Re-enter new wipe code")
|
||||
if code1 == code2:
|
||||
return code1
|
||||
await _wipe_code_mismatch()
|
||||
|
||||
|
||||
async def _wipe_code_invalid() -> None:
|
||||
text = Text("Invalid wipe code", ui.ICON_WRONG, ui.RED)
|
||||
text.normal("The wipe code must be", "different from your PIN.")
|
||||
text.normal("")
|
||||
text.normal("Please try again.")
|
||||
popup = Popup(text, 3000) # show for 3 seconds
|
||||
await popup
|
||||
|
||||
|
||||
async def _wipe_code_mismatch() -> None:
|
||||
text = Text("Code mismatch", ui.ICON_WRONG, ui.RED)
|
||||
text.normal("The wipe codes you", "entered do not match.")
|
||||
text.normal("")
|
||||
text.normal("Please try again.")
|
||||
popup = Popup(text, 3000) # show for 3 seconds
|
||||
await popup
|
26
core/src/trezor/messages/ChangeWipeCode.py
Normal file
26
core/src/trezor/messages/ChangeWipeCode.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class ChangeWipeCode(p.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 82
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
remove: bool = None,
|
||||
) -> None:
|
||||
self.remove = remove
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('remove', p.BoolType, 0),
|
||||
}
|
@ -6,7 +6,7 @@ if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
EnumTypeFailureType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 99]
|
||||
EnumTypeFailureType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 99]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@ -25,6 +25,6 @@ class Failure(p.MessageType):
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('code', p.EnumType("FailureType", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 99)), 0),
|
||||
1: ('code', p.EnumType("FailureType", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 99)), 0),
|
||||
2: ('message', p.UnicodeType, 0),
|
||||
}
|
||||
|
@ -15,4 +15,5 @@ ProcessError = 9 # type: Literal[9]
|
||||
NotEnoughFunds = 10 # type: Literal[10]
|
||||
NotInitialized = 11 # type: Literal[11]
|
||||
PinMismatch = 12 # type: Literal[12]
|
||||
WipeCodeMismatch = 13 # type: Literal[13]
|
||||
FirmwareError = 99 # type: Literal[99]
|
||||
|
@ -49,6 +49,7 @@ class Features(p.MessageType):
|
||||
backup_type: EnumTypeBackupType = None,
|
||||
sd_card_present: bool = None,
|
||||
sd_protection: bool = None,
|
||||
wipe_code_protection: bool = None,
|
||||
) -> None:
|
||||
self.vendor = vendor
|
||||
self.major_version = major_version
|
||||
@ -82,6 +83,7 @@ class Features(p.MessageType):
|
||||
self.backup_type = backup_type
|
||||
self.sd_card_present = sd_card_present
|
||||
self.sd_protection = sd_protection
|
||||
self.wipe_code_protection = wipe_code_protection
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
@ -118,4 +120,5 @@ class Features(p.MessageType):
|
||||
31: ('backup_type', p.EnumType("BackupType", (0, 1, 2)), 0),
|
||||
32: ('sd_card_present', p.BoolType, 0),
|
||||
33: ('sd_protection', p.BoolType, 0),
|
||||
34: ('wipe_code_protection', p.BoolType, 0),
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ SetU2FCounter = 63 # type: Literal[63]
|
||||
SdProtect = 79 # type: Literal[79]
|
||||
GetNextU2FCounter = 80 # type: Literal[80]
|
||||
NextU2FCounter = 81 # type: Literal[81]
|
||||
ChangeWipeCode = 82 # type: Literal[82]
|
||||
FirmwareErase = 6 # type: Literal[6]
|
||||
FirmwareUpload = 7 # type: Literal[7]
|
||||
FirmwareRequest = 8 # type: Literal[8]
|
||||
|
@ -71,6 +71,11 @@ class PinMismatch(Error):
|
||||
super().__init__(FailureType.PinMismatch, message)
|
||||
|
||||
|
||||
class WipeCodeMismatch(Error):
|
||||
def __init__(self, message: str) -> None:
|
||||
super().__init__(FailureType.WipeCodeMismatch, message)
|
||||
|
||||
|
||||
class FirmwareError(Error):
|
||||
def __init__(self, message: str) -> None:
|
||||
super().__init__(FailureType.FirmwareError, message)
|
||||
|
@ -785,6 +785,22 @@ bool config_getPin(char *dest, uint16_t dest_size) {
|
||||
}
|
||||
#endif
|
||||
|
||||
bool config_hasWipeCode(void) { return sectrue == storage_has_wipe_code(); }
|
||||
|
||||
bool config_changeWipeCode(const char *pin, const char *wipe_code) {
|
||||
uint32_t wipe_code_int = pin_to_int(wipe_code);
|
||||
if (wipe_code_int == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char oldTiny = usbTiny(1);
|
||||
secbool ret = storage_change_wipe_code(pin_to_int(pin), NULL, wipe_code_int);
|
||||
usbTiny(oldTiny);
|
||||
|
||||
memzero(&wipe_code_int, sizeof(wipe_code_int));
|
||||
return sectrue == ret;
|
||||
}
|
||||
|
||||
void session_cachePassphrase(const char *passphrase) {
|
||||
strlcpy(sessionPassphrase, passphrase, sizeof(sessionPassphrase));
|
||||
sessionPassphraseCached = sectrue;
|
||||
|
@ -129,6 +129,9 @@ bool config_hasPin(void);
|
||||
bool config_changePin(const char *old_pin, const char *new_pin);
|
||||
bool session_isUnlocked(void);
|
||||
|
||||
bool config_hasWipeCode(void);
|
||||
bool config_changeWipeCode(const char *pin, const char *wipe_code);
|
||||
|
||||
uint32_t config_nextU2FCounter(void);
|
||||
void config_setU2FCounter(uint32_t u2fcounter);
|
||||
|
||||
|
@ -167,6 +167,9 @@ void fsm_sendFailure(FailureType code, const char *text)
|
||||
case FailureType_Failure_PinMismatch:
|
||||
text = _("PIN mismatch");
|
||||
break;
|
||||
case FailureType_Failure_WipeCodeMismatch:
|
||||
text = _("Wipe code mismatch");
|
||||
break;
|
||||
case FailureType_Failure_FirmwareError:
|
||||
text = _("Firmware error");
|
||||
break;
|
||||
|
@ -52,6 +52,7 @@ void fsm_msgInitialize(const Initialize *msg);
|
||||
void fsm_msgGetFeatures(const GetFeatures *msg);
|
||||
void fsm_msgPing(const Ping *msg);
|
||||
void fsm_msgChangePin(const ChangePin *msg);
|
||||
void fsm_msgChangeWipeCode(const ChangeWipeCode *msg);
|
||||
void fsm_msgWipeDevice(const WipeDevice *msg);
|
||||
void fsm_msgGetEntropy(const GetEntropy *msg);
|
||||
#if DEBUG_LINK
|
||||
|
@ -82,6 +82,10 @@ void fsm_msgGetFeatures(const GetFeatures *msg) {
|
||||
resp->has_flags = config_getFlags(&(resp->flags));
|
||||
resp->has_model = true;
|
||||
strlcpy(resp->model, "1", sizeof(resp->model));
|
||||
if (session_isUnlocked()) {
|
||||
resp->has_wipe_code_protection = true;
|
||||
resp->wipe_code_protection = config_hasWipeCode();
|
||||
}
|
||||
|
||||
#if BITCOIN_ONLY
|
||||
resp->capabilities_count = 2;
|
||||
@ -176,6 +180,52 @@ void fsm_msgChangePin(const ChangePin *msg) {
|
||||
layoutHome();
|
||||
}
|
||||
|
||||
void fsm_msgChangeWipeCode(const ChangeWipeCode *msg) {
|
||||
CHECK_INITIALIZED
|
||||
|
||||
bool removal = msg->has_remove && msg->remove;
|
||||
bool has_wipe_code = config_hasWipeCode();
|
||||
|
||||
if (removal) {
|
||||
// Note that if storage is locked, then config_hasWipeCode() returns false.
|
||||
if (has_wipe_code || !session_isUnlocked()) {
|
||||
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
|
||||
_("Do you really want to"), _("disable wipe code"),
|
||||
_("protection?"), NULL, NULL, NULL);
|
||||
} else {
|
||||
fsm_sendSuccess(_("Wipe code removed"));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (has_wipe_code) {
|
||||
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
|
||||
_("Do you really want to"), _("change the current"),
|
||||
_("wipe code?"), NULL, NULL, NULL);
|
||||
} else {
|
||||
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
|
||||
_("Do you really want to"), _("set a new wipe code?"),
|
||||
NULL, NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
||||
layoutHome();
|
||||
return;
|
||||
}
|
||||
|
||||
if (protectChangeWipeCode(removal)) {
|
||||
if (removal) {
|
||||
fsm_sendSuccess(_("Wipe code removed"));
|
||||
} else if (has_wipe_code) {
|
||||
fsm_sendSuccess(_("Wipe code changed"));
|
||||
} else {
|
||||
fsm_sendSuccess(_("Wipe code set"));
|
||||
}
|
||||
}
|
||||
|
||||
layoutHome();
|
||||
}
|
||||
|
||||
void fsm_msgWipeDevice(const WipeDevice *msg) {
|
||||
(void)msg;
|
||||
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
|
||||
|
@ -270,6 +270,80 @@ bool protectChangePin(bool removal) {
|
||||
bool ret = config_changePin(old_pin, new_pin);
|
||||
memzero(old_pin, sizeof(old_pin));
|
||||
memzero(new_pin, sizeof(new_pin));
|
||||
if (ret == false) {
|
||||
if (removal) {
|
||||
fsm_sendFailure(FailureType_Failure_PinInvalid, NULL);
|
||||
} else {
|
||||
fsm_sendFailure(FailureType_Failure_ProcessError,
|
||||
_("The new PIN must be different from your wipe code."));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool protectChangeWipeCode(bool removal) {
|
||||
static CONFIDENTIAL char pin[MAX_PIN_LEN + 1] = "";
|
||||
static CONFIDENTIAL char wipe_code[MAX_PIN_LEN + 1] = "";
|
||||
const char *input = NULL;
|
||||
|
||||
if (config_hasPin()) {
|
||||
input = requestPin(PinMatrixRequestType_PinMatrixRequestType_Current,
|
||||
_("Please enter your PIN:"));
|
||||
if (input == NULL) {
|
||||
fsm_sendFailure(FailureType_Failure_PinCancelled, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If removing, defer the check to config_changeWipeCode().
|
||||
if (!removal) {
|
||||
usbTiny(1);
|
||||
bool ret = config_unlock(input);
|
||||
usbTiny(0);
|
||||
if (ret == false) {
|
||||
fsm_sendFailure(FailureType_Failure_PinInvalid, NULL);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
strlcpy(pin, input, sizeof(pin));
|
||||
}
|
||||
|
||||
if (!removal) {
|
||||
input = requestPin(PinMatrixRequestType_PinMatrixRequestType_WipeCodeFirst,
|
||||
_("Enter new wipe code:"));
|
||||
if (input == NULL) {
|
||||
memzero(pin, sizeof(pin));
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
||||
return false;
|
||||
}
|
||||
if (strncmp(pin, input, sizeof(pin)) == 0) {
|
||||
memzero(pin, sizeof(pin));
|
||||
fsm_sendFailure(FailureType_Failure_ProcessError,
|
||||
_("The wipe code must be different from your PIN."));
|
||||
return false;
|
||||
}
|
||||
strlcpy(wipe_code, input, sizeof(wipe_code));
|
||||
|
||||
input = requestPin(PinMatrixRequestType_PinMatrixRequestType_WipeCodeSecond,
|
||||
_("Re-enter new wipe code:"));
|
||||
if (input == NULL) {
|
||||
memzero(pin, sizeof(pin));
|
||||
memzero(wipe_code, sizeof(wipe_code));
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strncmp(wipe_code, input, sizeof(wipe_code)) != 0) {
|
||||
memzero(pin, sizeof(pin));
|
||||
memzero(wipe_code, sizeof(wipe_code));
|
||||
fsm_sendFailure(FailureType_Failure_WipeCodeMismatch, NULL);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ret = config_changeWipeCode(pin, wipe_code);
|
||||
memzero(pin, sizeof(pin));
|
||||
memzero(wipe_code, sizeof(wipe_code));
|
||||
if (ret == false) {
|
||||
fsm_sendFailure(FailureType_Failure_PinInvalid, NULL);
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ secbool protectPinUiCallback(uint32_t wait, uint32_t progress,
|
||||
const char* message);
|
||||
bool protectPin(bool use_cached);
|
||||
bool protectChangePin(bool removal);
|
||||
bool protectChangeWipeCode(bool removal);
|
||||
bool protectPassphrase(void);
|
||||
|
||||
extern bool protectAbortedByCancel;
|
||||
|
@ -36,6 +36,6 @@
|
||||
/*
|
||||
* Current storage version.
|
||||
*/
|
||||
#define NORCOW_VERSION ((uint32_t)0x00000001)
|
||||
#define NORCOW_VERSION ((uint32_t)0x00000002)
|
||||
|
||||
#endif
|
||||
|
@ -362,6 +362,7 @@ Device settings.
|
||||
label Set new device label.
|
||||
passphrase Enable, disable or configure passphrase protection.
|
||||
pin Set, change or remove PIN.
|
||||
wipe-code Set or remove the wipe code.
|
||||
|
||||
Stellar commands.
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
@ -41,6 +41,19 @@ def pin(connect, remove):
|
||||
return device.change_pin(connect(), remove)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("-r", "--remove", is_flag=True)
|
||||
@click.pass_obj
|
||||
def wipe_code(connect, remove):
|
||||
"""Set or remove the wipe code.
|
||||
|
||||
The wipe code functions as a "self-destruct PIN". If the wipe code is ever
|
||||
entered into any PIN entry dialog, then all private data will be immediately
|
||||
removed and the device will be reset to factory defaults.
|
||||
"""
|
||||
return device.change_wipe_code(connect(), remove)
|
||||
|
||||
|
||||
@cli.command()
|
||||
# keep the deprecated -l/--label option, make it do nothing
|
||||
@click.option("-l", "--label", "_ignore", is_flag=True, hidden=True, expose_value=False)
|
||||
|
@ -90,6 +90,13 @@ def change_pin(client, remove=False):
|
||||
return ret
|
||||
|
||||
|
||||
@expect(messages.Success, field="message")
|
||||
def change_wipe_code(client, remove=False):
|
||||
ret = client.call(messages.ChangeWipeCode(remove=remove))
|
||||
client.init_device() # Re-read features
|
||||
return ret
|
||||
|
||||
|
||||
@expect(messages.Success, field="message")
|
||||
def sd_protect(client, operation):
|
||||
ret = client.call(messages.SdProtect(operation=operation))
|
||||
|
26
python/src/trezorlib/messages/ChangeWipeCode.py
Normal file
26
python/src/trezorlib/messages/ChangeWipeCode.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class ChangeWipeCode(p.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 82
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
remove: bool = None,
|
||||
) -> None:
|
||||
self.remove = remove
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('remove', p.BoolType, 0),
|
||||
}
|
@ -6,7 +6,7 @@ if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
EnumTypeFailureType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 99]
|
||||
EnumTypeFailureType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 99]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@ -25,6 +25,6 @@ class Failure(p.MessageType):
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('code', p.EnumType("FailureType", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 99)), 0),
|
||||
1: ('code', p.EnumType("FailureType", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 99)), 0),
|
||||
2: ('message', p.UnicodeType, 0),
|
||||
}
|
||||
|
@ -15,4 +15,5 @@ ProcessError = 9 # type: Literal[9]
|
||||
NotEnoughFunds = 10 # type: Literal[10]
|
||||
NotInitialized = 11 # type: Literal[11]
|
||||
PinMismatch = 12 # type: Literal[12]
|
||||
WipeCodeMismatch = 13 # type: Literal[13]
|
||||
FirmwareError = 99 # type: Literal[99]
|
||||
|
@ -49,6 +49,7 @@ class Features(p.MessageType):
|
||||
backup_type: EnumTypeBackupType = None,
|
||||
sd_card_present: bool = None,
|
||||
sd_protection: bool = None,
|
||||
wipe_code_protection: bool = None,
|
||||
) -> None:
|
||||
self.vendor = vendor
|
||||
self.major_version = major_version
|
||||
@ -82,6 +83,7 @@ class Features(p.MessageType):
|
||||
self.backup_type = backup_type
|
||||
self.sd_card_present = sd_card_present
|
||||
self.sd_protection = sd_protection
|
||||
self.wipe_code_protection = wipe_code_protection
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
@ -118,4 +120,5 @@ class Features(p.MessageType):
|
||||
31: ('backup_type', p.EnumType("BackupType", (0, 1, 2)), 0),
|
||||
32: ('sd_card_present', p.BoolType, 0),
|
||||
33: ('sd_protection', p.BoolType, 0),
|
||||
34: ('wipe_code_protection', p.BoolType, 0),
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ SetU2FCounter = 63 # type: Literal[63]
|
||||
SdProtect = 79 # type: Literal[79]
|
||||
GetNextU2FCounter = 80 # type: Literal[80]
|
||||
NextU2FCounter = 81 # type: Literal[81]
|
||||
ChangeWipeCode = 82 # type: Literal[82]
|
||||
FirmwareErase = 6 # type: Literal[6]
|
||||
FirmwareUpload = 7 # type: Literal[7]
|
||||
FirmwareRequest = 8 # type: Literal[8]
|
||||
|
@ -6,7 +6,7 @@ if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
EnumTypePinMatrixRequestType = Literal[1, 2, 3]
|
||||
EnumTypePinMatrixRequestType = Literal[1, 2, 3, 4, 5]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@ -23,5 +23,5 @@ class PinMatrixRequest(p.MessageType):
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('type', p.EnumType("PinMatrixRequestType", (1, 2, 3)), 0),
|
||||
1: ('type', p.EnumType("PinMatrixRequestType", (1, 2, 3, 4, 5)), 0),
|
||||
}
|
||||
|
@ -6,3 +6,5 @@ if False:
|
||||
Current = 1 # type: Literal[1]
|
||||
NewFirst = 2 # type: Literal[2]
|
||||
NewSecond = 3 # type: Literal[3]
|
||||
WipeCodeFirst = 4 # type: Literal[4]
|
||||
WipeCodeSecond = 5 # type: Literal[5]
|
||||
|
@ -31,6 +31,7 @@ from .CardanoTxInputType import CardanoTxInputType
|
||||
from .CardanoTxOutputType import CardanoTxOutputType
|
||||
from .CardanoTxRequest import CardanoTxRequest
|
||||
from .ChangePin import ChangePin
|
||||
from .ChangeWipeCode import ChangeWipeCode
|
||||
from .CipherKeyValue import CipherKeyValue
|
||||
from .CipheredKeyValue import CipheredKeyValue
|
||||
from .ClearSession import ClearSession
|
||||
|
@ -45,6 +45,8 @@ PIN_GENERIC = None
|
||||
PIN_CURRENT = PinMatrixRequestType.Current
|
||||
PIN_NEW = PinMatrixRequestType.NewFirst
|
||||
PIN_CONFIRM = PinMatrixRequestType.NewSecond
|
||||
WIPE_CODE_NEW = PinMatrixRequestType.WipeCodeFirst
|
||||
WIPE_CODE_CONFIRM = PinMatrixRequestType.WipeCodeSecond
|
||||
|
||||
|
||||
def echo(*args, **kwargs):
|
||||
@ -74,6 +76,10 @@ class ClickUI:
|
||||
desc = "new PIN"
|
||||
elif code == PIN_CONFIRM:
|
||||
desc = "new PIN again"
|
||||
elif code == WIPE_CODE_NEW:
|
||||
desc = "new wipe code"
|
||||
elif code == WIPE_CODE_CONFIRM:
|
||||
desc = "new wipe code again"
|
||||
else:
|
||||
desc = "PIN"
|
||||
|
||||
@ -88,7 +94,7 @@ class ClickUI:
|
||||
except click.Abort:
|
||||
raise Cancelled from None
|
||||
if not pin.isdigit():
|
||||
echo("Non-numerical PIN provided, please try again")
|
||||
echo("Non-numerical value provided, please try again")
|
||||
else:
|
||||
return pin
|
||||
|
||||
|
2
storage/.gitignore
vendored
2
storage/.gitignore
vendored
@ -1,2 +1,4 @@
|
||||
*.o
|
||||
*.d
|
||||
*.so
|
||||
.hypothesis
|
||||
|
@ -302,6 +302,7 @@ void norcow_init(uint32_t *norcow_version) {
|
||||
flash_init();
|
||||
secbool found = secfalse;
|
||||
*norcow_version = 0;
|
||||
norcow_active_sector = 0;
|
||||
// detect active sector - starts with magic and has highest version
|
||||
for (uint8_t i = 0; i < NORCOW_SECTOR_COUNT; i++) {
|
||||
uint32_t offset = 0;
|
||||
@ -332,13 +333,16 @@ void norcow_init(uint32_t *norcow_version) {
|
||||
* Wipe the storage
|
||||
*/
|
||||
void norcow_wipe(void) {
|
||||
erase_sector(0, sectrue);
|
||||
for (uint8_t i = 1; i < NORCOW_SECTOR_COUNT; i++) {
|
||||
erase_sector(i, secfalse);
|
||||
// Erase the active sector first, because it contains sensitive data.
|
||||
erase_sector(norcow_active_sector, sectrue);
|
||||
|
||||
for (uint8_t i = 0; i < NORCOW_SECTOR_COUNT; i++) {
|
||||
if (i != norcow_active_sector) {
|
||||
erase_sector(i, secfalse);
|
||||
}
|
||||
}
|
||||
norcow_active_sector = 0;
|
||||
norcow_active_version = NORCOW_VERSION;
|
||||
norcow_write_sector = 0;
|
||||
norcow_write_sector = norcow_active_sector;
|
||||
norcow_free_offset = NORCOW_STORAGE_START;
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "chacha20poly1305/rfc7539.h"
|
||||
@ -50,6 +51,12 @@
|
||||
// Norcow storage key of the storage authentication tag.
|
||||
#define STORAGE_TAG_KEY ((APP_STORAGE << 8) | 0x05)
|
||||
|
||||
// Norcow storage key of the wipe code data.
|
||||
#define WIPE_CODE_DATA_KEY ((APP_STORAGE << 8) | 0x06)
|
||||
|
||||
// Norcow storage key of the storage upgrade flag.
|
||||
#define STORAGE_UPGRADED_KEY ((APP_STORAGE << 8) | 0x07)
|
||||
|
||||
// The PIN value corresponding to an empty PIN.
|
||||
#define PIN_EMPTY 1
|
||||
|
||||
@ -104,6 +111,24 @@
|
||||
// The length of the ChaCha20 block in bytes.
|
||||
#define CHACHA20_BLOCK_SIZE 64
|
||||
|
||||
// The length of the wipe code in bytes.
|
||||
#define WIPE_CODE_SIZE (sizeof(uint32_t))
|
||||
|
||||
// The byte length of the salt used in checking the wipe code.
|
||||
#define WIPE_CODE_SALT_SIZE 8
|
||||
|
||||
// The byte length of the tag used in checking the wipe code.
|
||||
#define WIPE_CODE_TAG_SIZE 8
|
||||
|
||||
// The total length of the WIPE_CODE_DATA_KEY entry.
|
||||
#define WIPE_CODE_DATA_SIZE \
|
||||
(WIPE_CODE_SIZE + WIPE_CODE_SALT_SIZE + WIPE_CODE_TAG_SIZE)
|
||||
|
||||
// The value corresponding to an unconfigured wipe code.
|
||||
// NOTE: This is intentionally different from PIN_EMPTY so that we don't need
|
||||
// special handling when both the PIN and wipe code are not set.
|
||||
#define WIPE_CODE_EMPTY 0
|
||||
|
||||
// The length of the counter tail in words.
|
||||
#define COUNTER_TAIL_WORDS 2
|
||||
|
||||
@ -129,6 +154,8 @@ static uint8_t hardware_salt[HARDWARE_SALT_SIZE] = {0};
|
||||
static uint32_t norcow_active_version = 0;
|
||||
static const uint8_t TRUE_BYTE = 0x01;
|
||||
static const uint8_t FALSE_BYTE = 0x00;
|
||||
static const uint32_t TRUE_WORD = 0xC35A69A5;
|
||||
static const uint32_t FALSE_WORD = 0x3CA5965A;
|
||||
|
||||
static void __handle_fault(const char *msg, const char *file, int line,
|
||||
const char *func);
|
||||
@ -159,19 +186,25 @@ static secbool secequal(const void *ptr1, const void *ptr2, size_t n) {
|
||||
return diff ? secfalse : sectrue;
|
||||
}
|
||||
|
||||
static secbool secequal32(const uint32_t *ptr1, const uint32_t *ptr2,
|
||||
size_t n) {
|
||||
static secbool secequal32(const void *ptr1, const void *ptr2, size_t n) {
|
||||
assert(n % sizeof(uint32_t) == 0);
|
||||
assert((uintptr_t)ptr1 % sizeof(uint32_t) == 0);
|
||||
assert((uintptr_t)ptr2 % sizeof(uint32_t) == 0);
|
||||
|
||||
size_t wn = n / sizeof(uint32_t);
|
||||
const uint32_t *p1 = (const uint32_t *)ptr1;
|
||||
const uint32_t *p2 = (const uint32_t *)ptr2;
|
||||
uint32_t diff = 0;
|
||||
size_t i = 0;
|
||||
for (i = 0; i < n; ++i) {
|
||||
for (i = 0; i < wn; ++i) {
|
||||
uint32_t mask = random32();
|
||||
diff |= (*ptr1 + mask - *ptr2) ^ mask;
|
||||
++ptr1;
|
||||
++ptr2;
|
||||
diff |= (*p1 + mask - *p2) ^ mask;
|
||||
++p1;
|
||||
++p2;
|
||||
}
|
||||
|
||||
// Check loop completion in case of a fault injection attack.
|
||||
if (i != n) {
|
||||
if (i != wn) {
|
||||
handle_fault("loop completion check");
|
||||
}
|
||||
|
||||
@ -323,6 +356,83 @@ static secbool auth_get(uint16_t key, const void **val, uint16_t *len) {
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
static secbool set_wipe_code(uint32_t wipe_code) {
|
||||
if (wipe_code == PIN_EMPTY) {
|
||||
// This is to avoid having to check pin != PIN_EMPTY when checking the wipe
|
||||
// code.
|
||||
wipe_code = WIPE_CODE_EMPTY;
|
||||
}
|
||||
|
||||
// The format of the WIPE_CODE_DATA_KEY entry is:
|
||||
// wipe code (4 bytes), random salt (16 bytes), authentication tag (16 bytes)
|
||||
// NOTE: We allocate extra space for the HMAC computation.
|
||||
uint8_t wipe_code_data[WIPE_CODE_SIZE + WIPE_CODE_SALT_SIZE +
|
||||
SHA256_DIGEST_LENGTH] = {0};
|
||||
uint8_t *salt = wipe_code_data + WIPE_CODE_SIZE;
|
||||
uint8_t *tag = wipe_code_data + WIPE_CODE_SIZE + WIPE_CODE_SALT_SIZE;
|
||||
memcpy(wipe_code_data, &wipe_code, sizeof(wipe_code));
|
||||
memzero(&wipe_code, sizeof(wipe_code));
|
||||
random_buffer(salt, WIPE_CODE_SALT_SIZE);
|
||||
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, wipe_code_data, WIPE_CODE_SIZE, tag);
|
||||
return norcow_set(WIPE_CODE_DATA_KEY, wipe_code_data, WIPE_CODE_DATA_SIZE);
|
||||
}
|
||||
|
||||
static secbool is_not_wipe_code(uint32_t pin) {
|
||||
uint8_t wipe_code[WIPE_CODE_SIZE] = {0};
|
||||
uint8_t salt[WIPE_CODE_SALT_SIZE] = {0};
|
||||
uint8_t stored_tag[WIPE_CODE_TAG_SIZE] = {0};
|
||||
uint8_t computed_tag1[SHA256_DIGEST_LENGTH] = {0};
|
||||
uint8_t computed_tag2[SHA256_DIGEST_LENGTH] = {0};
|
||||
|
||||
// Read the wipe code data from the storage.
|
||||
const void *wipe_code_data = NULL;
|
||||
uint16_t len = 0;
|
||||
if (sectrue != norcow_get(WIPE_CODE_DATA_KEY, &wipe_code_data, &len) ||
|
||||
len != WIPE_CODE_DATA_SIZE) {
|
||||
handle_fault("no wipe code");
|
||||
return secfalse;
|
||||
}
|
||||
memcpy(wipe_code, wipe_code_data, sizeof(wipe_code));
|
||||
memcpy(salt, (uint8_t *)wipe_code_data + WIPE_CODE_SIZE, sizeof(salt));
|
||||
memcpy(stored_tag,
|
||||
(uint8_t *)wipe_code_data + WIPE_CODE_SIZE + WIPE_CODE_SALT_SIZE,
|
||||
sizeof(stored_tag));
|
||||
|
||||
// Check integrity in case of flash read manipulation attack.
|
||||
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, wipe_code, sizeof(wipe_code),
|
||||
computed_tag1);
|
||||
memzero(wipe_code, sizeof(wipe_code));
|
||||
if (sectrue != secequal(stored_tag, computed_tag1, sizeof(stored_tag))) {
|
||||
handle_fault("wipe code tag");
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
// Prepare the authentication tag of the entered PIN.
|
||||
wait_random();
|
||||
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, (const uint8_t *)&pin, WIPE_CODE_SIZE,
|
||||
computed_tag1);
|
||||
|
||||
// Recompute to check for fault injection attack.
|
||||
wait_random();
|
||||
hmac_sha256(salt, WIPE_CODE_SALT_SIZE, (const uint8_t *)&pin, WIPE_CODE_SIZE,
|
||||
computed_tag2);
|
||||
memzero(salt, sizeof(salt));
|
||||
if (sectrue !=
|
||||
secequal(computed_tag1, computed_tag2, sizeof(computed_tag1))) {
|
||||
handle_fault("wipe code fault");
|
||||
return secfalse;
|
||||
}
|
||||
memzero(&pin, sizeof(pin));
|
||||
|
||||
// Compare wipe code with the entered PIN via the authentication tag.
|
||||
wait_random();
|
||||
if (secfalse != secequal(stored_tag, computed_tag1, sizeof(stored_tag))) {
|
||||
return secfalse;
|
||||
}
|
||||
memzero(stored_tag, sizeof(stored_tag));
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
static void derive_kek(uint32_t pin, const uint8_t *random_salt,
|
||||
const uint8_t *ext_salt,
|
||||
uint8_t kek[SHA256_DIGEST_LENGTH],
|
||||
@ -383,6 +493,13 @@ static void derive_kek(uint32_t pin, const uint8_t *random_salt,
|
||||
}
|
||||
|
||||
static secbool set_pin(uint32_t pin, const uint8_t *ext_salt) {
|
||||
// Fail if the PIN is the same as the wipe code. Ignore during upgrade.
|
||||
if (norcow_active_version != 0 && sectrue != is_not_wipe_code(pin)) {
|
||||
memzero(&pin, sizeof(pin));
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
// Encrypt the cached keys using the new PIN and set the new PVC.
|
||||
uint8_t buffer[RANDOM_SALT_SIZE + KEYS_SIZE + POLY1305_TAG_SIZE] = {0};
|
||||
uint8_t *rand_salt = buffer;
|
||||
uint8_t *ekeys = buffer + RANDOM_SALT_SIZE;
|
||||
@ -516,7 +633,11 @@ static void init_wiped_storage(void) {
|
||||
ensure(auth_init(), "set_storage_auth_tag failed");
|
||||
ensure(storage_set_encrypted(VERSION_KEY, &version, sizeof(version)),
|
||||
"set_storage_version failed");
|
||||
ensure(norcow_set(STORAGE_UPGRADED_KEY, &FALSE_WORD, sizeof(FALSE_WORD)),
|
||||
"set_storage_not_upgraded failed");
|
||||
ensure(pin_logs_init(0), "init_pin_logs failed");
|
||||
ensure(set_wipe_code(WIPE_CODE_EMPTY), "set_wipe_code failed");
|
||||
|
||||
ui_total = DERIVE_SECS;
|
||||
ui_rem = ui_total;
|
||||
ui_message = PROCESSING_MSG;
|
||||
@ -738,6 +859,49 @@ void storage_lock(void) {
|
||||
memzero(authentication_sum, sizeof(authentication_sum));
|
||||
}
|
||||
|
||||
secbool check_storage_version(void) {
|
||||
uint32_t version = 0;
|
||||
uint16_t len = 0;
|
||||
if (sectrue !=
|
||||
storage_get_encrypted(VERSION_KEY, &version, sizeof(version), &len) ||
|
||||
len != sizeof(version)) {
|
||||
handle_fault("storage version check");
|
||||
return secfalse;
|
||||
}
|
||||
const void *storage_upgraded = NULL;
|
||||
if (sectrue != norcow_get(STORAGE_UPGRADED_KEY, &storage_upgraded, &len) ||
|
||||
len != sizeof(TRUE_WORD)) {
|
||||
handle_fault("storage version check");
|
||||
return secfalse;
|
||||
}
|
||||
if (version > norcow_active_version) {
|
||||
// Attack: Storage was downgraded.
|
||||
storage_wipe();
|
||||
handle_fault("storage version check");
|
||||
return secfalse;
|
||||
} else if (version < norcow_active_version) {
|
||||
// Storage was upgraded.
|
||||
if (*(const uint32_t *)storage_upgraded != TRUE_WORD) {
|
||||
// Attack: The upgrade process was bypassed.
|
||||
storage_wipe();
|
||||
handle_fault("storage version check");
|
||||
return secfalse;
|
||||
}
|
||||
norcow_set(STORAGE_UPGRADED_KEY, &FALSE_WORD, sizeof(FALSE_WORD));
|
||||
storage_set_encrypted(VERSION_KEY, &norcow_active_version,
|
||||
sizeof(norcow_active_version));
|
||||
} else {
|
||||
// Standard operation. The storage was neither upgraded nor downgraded.
|
||||
if (*(const uint32_t *)storage_upgraded != FALSE_WORD) {
|
||||
// Attack: The upgrade process was launched when it shouldn't have been.
|
||||
storage_wipe();
|
||||
handle_fault("storage version check");
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) {
|
||||
const void *buffer = NULL;
|
||||
uint16_t len = 0;
|
||||
@ -765,8 +929,7 @@ static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) {
|
||||
rfc7539_finish(&ctx, 0, KEYS_SIZE, tag);
|
||||
memzero(&ctx, sizeof(ctx));
|
||||
wait_random();
|
||||
if (secequal32((const uint32_t *)tag, pvc, PVC_SIZE / sizeof(uint32_t)) !=
|
||||
sectrue) {
|
||||
if (secequal32(tag, pvc, PVC_SIZE) != sectrue) {
|
||||
memzero(keys, sizeof(keys));
|
||||
memzero(tag, sizeof(tag));
|
||||
return secfalse;
|
||||
@ -774,19 +937,10 @@ static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) {
|
||||
memcpy(cached_keys, keys, sizeof(keys));
|
||||
memzero(keys, sizeof(keys));
|
||||
memzero(tag, sizeof(tag));
|
||||
|
||||
// Check that the authenticated version number matches the norcow version.
|
||||
// NOTE: storage_get_encrypted() calls auth_get(), which initializes the
|
||||
// authentication_sum.
|
||||
uint32_t version = 0;
|
||||
if (sectrue !=
|
||||
storage_get_encrypted(VERSION_KEY, &version, sizeof(version), &len) ||
|
||||
len != sizeof(version) || version != norcow_active_version) {
|
||||
handle_fault("storage version check");
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
return sectrue;
|
||||
// NOTE: This also initializes the authentication_sum by calling
|
||||
// storage_get_encrypted() which calls auth_get().
|
||||
return check_storage_version();
|
||||
}
|
||||
|
||||
static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
|
||||
@ -794,6 +948,13 @@ static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
// Check whether the user entered the wipe code.
|
||||
if (sectrue != is_not_wipe_code(pin)) {
|
||||
storage_wipe();
|
||||
error_shutdown("You have entered the", "wipe code. All private",
|
||||
"data has been erased.", NULL);
|
||||
}
|
||||
|
||||
// Get the pin failure counter
|
||||
uint32_t ctr = 0;
|
||||
if (sectrue != pin_get_fails(&ctr)) {
|
||||
@ -860,7 +1021,7 @@ static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
// Check that the PIN was correct.
|
||||
// Check whether the entered PIN is correct.
|
||||
if (sectrue != decrypt_dek(kek, keiv)) {
|
||||
// Wipe storage if too many failures
|
||||
wait_random();
|
||||
@ -1175,6 +1336,36 @@ secbool storage_change_pin(uint32_t oldpin, uint32_t newpin,
|
||||
return ret;
|
||||
}
|
||||
|
||||
secbool storage_has_wipe_code(void) {
|
||||
if (sectrue != initialized || sectrue != unlocked) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
return is_not_wipe_code(WIPE_CODE_EMPTY);
|
||||
}
|
||||
|
||||
secbool storage_change_wipe_code(uint32_t pin, const uint8_t *ext_salt,
|
||||
uint32_t wipe_code) {
|
||||
if (sectrue != initialized || (pin != PIN_EMPTY && pin == wipe_code)) {
|
||||
memzero(&pin, sizeof(pin));
|
||||
memzero(&wipe_code, sizeof(wipe_code));
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
ui_total = DERIVE_SECS;
|
||||
ui_rem = ui_total;
|
||||
ui_message = (pin != PIN_EMPTY && wipe_code == PIN_EMPTY) ? VERIFYING_PIN_MSG
|
||||
: PROCESSING_MSG;
|
||||
|
||||
secbool ret = secfalse;
|
||||
if (sectrue == unlock(pin, ext_salt)) {
|
||||
ret = set_wipe_code(wipe_code);
|
||||
}
|
||||
memzero(&pin, sizeof(pin));
|
||||
memzero(&wipe_code, sizeof(wipe_code));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void storage_wipe(void) {
|
||||
norcow_wipe();
|
||||
norcow_active_version = NORCOW_VERSION;
|
||||
@ -1253,6 +1444,7 @@ static secbool storage_upgrade(void) {
|
||||
uint16_t key = 0;
|
||||
uint16_t len = 0;
|
||||
const void *val = NULL;
|
||||
secbool ret = secfalse;
|
||||
|
||||
if (norcow_active_version == 0) {
|
||||
random_buffer(cached_keys, sizeof(cached_keys));
|
||||
@ -1261,7 +1453,7 @@ static secbool storage_upgrade(void) {
|
||||
auth_init();
|
||||
|
||||
// Set the new storage version number.
|
||||
uint32_t version = NORCOW_VERSION;
|
||||
uint32_t version = 1;
|
||||
if (sectrue !=
|
||||
storage_set_encrypted(VERSION_KEY, &version, sizeof(version))) {
|
||||
return secfalse;
|
||||
@ -1289,7 +1481,6 @@ static secbool storage_upgrade(void) {
|
||||
continue;
|
||||
}
|
||||
|
||||
secbool ret = secfalse;
|
||||
if (((key >> 8) & FLAG_PUBLIC) != 0) {
|
||||
ret = norcow_set(key, val, len);
|
||||
} else {
|
||||
@ -1304,6 +1495,23 @@ static secbool storage_upgrade(void) {
|
||||
unlocked = secfalse;
|
||||
memzero(cached_keys, sizeof(cached_keys));
|
||||
} else {
|
||||
// Copy all entries.
|
||||
uint32_t offset = 0;
|
||||
while (sectrue == norcow_get_next(&offset, &key, &val, &len)) {
|
||||
if (sectrue != norcow_set(key, val, len)) {
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (norcow_active_version <= 1) {
|
||||
if (sectrue != set_wipe_code(WIPE_CODE_EMPTY)) {
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
|
||||
if (sectrue !=
|
||||
norcow_set(STORAGE_UPGRADED_KEY, &TRUE_WORD, sizeof(TRUE_WORD))) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
|
@ -45,16 +45,19 @@ void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt,
|
||||
void storage_wipe(void);
|
||||
secbool storage_is_unlocked(void);
|
||||
void storage_lock(void);
|
||||
secbool storage_unlock(const uint32_t pin, const uint8_t *ext_salt);
|
||||
secbool storage_unlock(uint32_t pin, const uint8_t *ext_salt);
|
||||
secbool storage_has_pin(void);
|
||||
secbool storage_pin_fails_increase(void);
|
||||
uint32_t storage_get_pin_rem(void);
|
||||
secbool storage_change_pin(const uint32_t oldpin, const uint32_t newpin,
|
||||
secbool storage_change_pin(uint32_t oldpin, uint32_t newpin,
|
||||
const uint8_t *old_ext_salt,
|
||||
const uint8_t *new_ext_salt);
|
||||
secbool storage_has_wipe_code(void);
|
||||
secbool storage_change_wipe_code(uint32_t pin, const uint8_t *ext_salt,
|
||||
uint32_t wipe_code);
|
||||
secbool storage_get(const uint16_t key, void *val, const uint16_t max_len,
|
||||
uint16_t *len);
|
||||
secbool storage_set(const uint16_t key, const void *val, uint16_t len);
|
||||
secbool storage_set(const uint16_t key, const void *val, const uint16_t len);
|
||||
secbool storage_delete(const uint16_t key);
|
||||
secbool storage_set_counter(const uint16_t key, const uint32_t count);
|
||||
secbool storage_next_counter(const uint16_t key, uint32_t *count);
|
||||
|
Binary file not shown.
@ -40,6 +40,6 @@
|
||||
/*
|
||||
* Current storage version.
|
||||
*/
|
||||
#define NORCOW_VERSION ((uint32_t)0x00000001)
|
||||
#define NORCOW_VERSION ((uint32_t)0x00000002)
|
||||
|
||||
#endif
|
||||
|
@ -15,6 +15,12 @@ VERSION_KEY = (PIN_APP_ID << 8) | 0x04
|
||||
# Norcow storage key of the storage authentication tag.
|
||||
SAT_KEY = (PIN_APP_ID << 8) | 0x05
|
||||
|
||||
# Norcow storage key of the wipe code data.
|
||||
WIPE_CODE_DATA_KEY = (PIN_APP_ID << 8) | 0x06
|
||||
|
||||
# Norcow storage key of the storage upgrade flag.
|
||||
STORAGE_UPGRADED_KEY = (PIN_APP_ID << 8) | 0x07
|
||||
|
||||
# The PIN value corresponding to an empty PIN.
|
||||
PIN_EMPTY = 1
|
||||
|
||||
@ -46,6 +52,17 @@ KEK_SIZE = 32
|
||||
# The length of KEIV in bytes.
|
||||
KEIV_SIZE = 12
|
||||
|
||||
# The byte length of the salt used in checking the wipe code.
|
||||
WIPE_CODE_SALT_SIZE = 8
|
||||
|
||||
# The byte length of the tag used in checking the wipe code.
|
||||
WIPE_CODE_TAG_SIZE = 8
|
||||
|
||||
# The value corresponding to an unconfigured wipe code.
|
||||
# NOTE: This is intentionally different from PIN_EMPTY so that we don't need
|
||||
# special handling when both the PIN and wipe code are not set.
|
||||
WIPE_CODE_EMPTY = 0
|
||||
|
||||
# Size of counter. 4B integer and 8B tail.
|
||||
COUNTER_TAIL = 12
|
||||
COUNTER_TAIL_SIZE = 8
|
||||
@ -88,6 +105,8 @@ WORD_SIZE = 4
|
||||
# Boolean values are stored as a simple 0/1 int.
|
||||
TRUE_BYTE = b"\x01"
|
||||
FALSE_BYTE = b"\x00"
|
||||
TRUE_WORD = b"\xA5\x69\x5A\xC3"
|
||||
FALSE_WORD = b"\x5A\x96\xA5\x3C"
|
||||
|
||||
# ----- Crypto ----- #
|
||||
|
||||
@ -106,7 +125,7 @@ NORCOW_SECTOR_SIZE = 64 * 1024
|
||||
NORCOW_MAGIC = b"NRC2"
|
||||
|
||||
# Norcow version, set in the storage header, but also as an encrypted item.
|
||||
NORCOW_VERSION = b"\x01\x00\x00\x00"
|
||||
NORCOW_VERSION = b"\x02\x00\x00\x00"
|
||||
|
||||
# Norcow magic combined with the version, which is stored as its negation.
|
||||
NORCOW_MAGIC_AND_VERSION = NORCOW_MAGIC + bytes(
|
||||
|
@ -15,6 +15,7 @@ def align4_data(data):
|
||||
class Norcow:
|
||||
def __init__(self):
|
||||
self.sectors = None
|
||||
self.active_sector = 0
|
||||
|
||||
def init(self):
|
||||
if self.sectors:
|
||||
@ -26,7 +27,10 @@ class Norcow:
|
||||
else:
|
||||
self.wipe()
|
||||
|
||||
def wipe(self, sector: int = 0):
|
||||
def wipe(self, sector: int = None):
|
||||
if sector is None:
|
||||
sector = self.active_sector
|
||||
|
||||
self.sectors = [
|
||||
bytearray([0xFF] * consts.NORCOW_SECTOR_SIZE)
|
||||
for _ in range(consts.NORCOW_SECTOR_COUNT)
|
||||
|
@ -38,8 +38,10 @@ class Storage:
|
||||
self.sak = prng.random_buffer(consts.SAK_SIZE)
|
||||
|
||||
self.nc.set(consts.SAT_KEY, crypto.init_hmacs(self.sak))
|
||||
self._set_encrypt(consts.VERSION_KEY, b"\x01\x00\x00\x00")
|
||||
self._set_encrypt(consts.VERSION_KEY, b"\x02\x00\x00\x00")
|
||||
self.nc.set(consts.STORAGE_UPGRADED_KEY, consts.FALSE_WORD)
|
||||
self.pin_log.init()
|
||||
self._set_wipe_code(consts.WIPE_CODE_EMPTY)
|
||||
self._set_pin(consts.PIN_EMPTY)
|
||||
self.unlocked = False
|
||||
|
||||
@ -59,6 +61,14 @@ class Storage:
|
||||
else:
|
||||
self._set_bool(consts.PIN_NOT_SET_KEY, False)
|
||||
|
||||
def _set_wipe_code(self, wipe_code: int):
|
||||
if wipe_code == consts.PIN_EMPTY:
|
||||
wipe_code = consts.WIPE_CODE_EMPTY
|
||||
wipe_code_bytes = wipe_code.to_bytes(4, "little")
|
||||
salt = prng.random_buffer(consts.WIPE_CODE_SALT_SIZE)
|
||||
tag = crypto._hmac(salt, wipe_code_bytes)[: consts.WIPE_CODE_TAG_SIZE]
|
||||
self.nc.set(consts.WIPE_CODE_DATA_KEY, wipe_code_bytes + salt + tag)
|
||||
|
||||
def wipe(self):
|
||||
self.nc.wipe()
|
||||
self._init_pin()
|
||||
|
@ -9,7 +9,7 @@ def test_norcow_set():
|
||||
n.init()
|
||||
n.set(0x0001, b"123")
|
||||
data = n._dump()[0][:256]
|
||||
assert data[:8] == b"NRC2\xFE\xFF\xFF\xFF"
|
||||
assert data[:8] == consts.NORCOW_MAGIC_AND_VERSION
|
||||
assert data[8:10] == b"\x01\x00" # app + key
|
||||
assert data[10:12] == b"\x03\x00" # length
|
||||
assert data[12:15] == b"123" # data
|
||||
@ -18,7 +18,7 @@ def test_norcow_set():
|
||||
n.wipe()
|
||||
n.set(0x0901, b"hello")
|
||||
data = n._dump()[0][:256]
|
||||
assert data[:8] == b"NRC2\xFE\xFF\xFF\xFF"
|
||||
assert data[:8] == consts.NORCOW_MAGIC_AND_VERSION
|
||||
assert data[8:10] == b"\x01\x09" # app + key
|
||||
assert data[10:12] == b"\x05\x00" # length
|
||||
assert data[12:17] == b"hello" # data
|
||||
@ -63,7 +63,8 @@ def test_norcow_get_item():
|
||||
assert value == b"123"
|
||||
assert (
|
||||
n._dump()[0][:40].hex()
|
||||
== "4e524332feffffff010003003132330002000300343536000101030037383900ffffffffffffffff"
|
||||
== consts.NORCOW_MAGIC_AND_VERSION.hex()
|
||||
+ "010003003132330002000300343536000101030037383900ffffffffffffffff"
|
||||
)
|
||||
|
||||
# replacing item with the same value (update)
|
||||
@ -72,7 +73,8 @@ def test_norcow_get_item():
|
||||
assert value == b"789"
|
||||
assert (
|
||||
n._dump()[0][:40].hex()
|
||||
== "4e524332feffffff010003003132330002000300343536000101030037383900ffffffffffffffff"
|
||||
== consts.NORCOW_MAGIC_AND_VERSION.hex()
|
||||
+ "010003003132330002000300343536000101030037383900ffffffffffffffff"
|
||||
)
|
||||
|
||||
# replacing item with value with less 1 bits than before (update)
|
||||
@ -81,7 +83,8 @@ def test_norcow_get_item():
|
||||
assert value == b"788"
|
||||
assert (
|
||||
n._dump()[0][:40].hex()
|
||||
== "4e524332feffffff010003003132330002000300343536000101030037383800ffffffffffffffff"
|
||||
== consts.NORCOW_MAGIC_AND_VERSION.hex()
|
||||
+ "010003003132330002000300343536000101030037383800ffffffffffffffff"
|
||||
)
|
||||
|
||||
# replacing item with value with more 1 bits than before (wipe and new entry)
|
||||
@ -90,7 +93,8 @@ def test_norcow_get_item():
|
||||
assert value == b"787"
|
||||
assert (
|
||||
n._dump()[0][:44].hex()
|
||||
== "4e524332feffffff0100030031323300020003003435360000000300000000000101030037383700ffffffff"
|
||||
== consts.NORCOW_MAGIC_AND_VERSION.hex()
|
||||
+ "0100030031323300020003003435360000000300000000000101030037383700ffffffff"
|
||||
)
|
||||
|
||||
n.set(0x0002, b"world")
|
||||
|
229
tests/device_tests/test_msg_change_wipe_code_t1.py
Normal file
229
tests/device_tests/test_msg_change_wipe_code_t1.py
Normal file
@ -0,0 +1,229 @@
|
||||
# This file is part of the Trezor project.
|
||||
#
|
||||
# Copyright (C) 2012-2019 SatoshiLabs and contributors
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License version 3
|
||||
# as published by the Free Software Foundation.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the License along with this library.
|
||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
|
||||
import pytest
|
||||
|
||||
from trezorlib import messages
|
||||
|
||||
PIN4 = "1234"
|
||||
WIPE_CODE4 = "4321"
|
||||
WIPE_CODE6 = "456789"
|
||||
|
||||
pytestmark = pytest.mark.skip_t2
|
||||
|
||||
|
||||
def _set_wipe_code(client, wipe_code):
|
||||
# Set/change wipe code.
|
||||
ret = client.call_raw(messages.ChangeWipeCode())
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
|
||||
# Confirm intent to set/change wipe code.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
if client.features.pin_protection:
|
||||
# Send current PIN.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.read_pin_encoded()
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# Send the new wipe code for the first time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(wipe_code)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# Send the new wipe code for the second time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(wipe_code)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# Now we're done.
|
||||
assert isinstance(ret, messages.Success)
|
||||
|
||||
|
||||
def _remove_wipe_code(client):
|
||||
# Remove wipe code
|
||||
ret = client.call_raw(messages.ChangeWipeCode(remove=True))
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
|
||||
# Confirm intent to remove wipe code.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Send current PIN.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.read_pin_encoded()
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# Now we're done.
|
||||
assert isinstance(ret, messages.Success)
|
||||
|
||||
|
||||
def _check_wipe_code(client, wipe_code):
|
||||
# Try to change the PIN to the current wipe code value. The operation should fail.
|
||||
ret = client.call_raw(messages.ChangePin())
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
|
||||
# Confirm intent to change PIN.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Send current PIN.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.read_pin_encoded()
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# Send the new wipe code for the first time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(wipe_code)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# Send the new wipe code for the second time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(wipe_code)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# Expect failure.
|
||||
assert isinstance(ret, messages.Failure)
|
||||
|
||||
|
||||
@pytest.mark.setup_client(pin=PIN4)
|
||||
def test_set_remove_wipe_code(client):
|
||||
# Check that wipe code protection status is not revealed in locked state.
|
||||
assert client.features.wipe_code_protection is None
|
||||
|
||||
# Test set wipe code.
|
||||
_set_wipe_code(client, WIPE_CODE4)
|
||||
|
||||
# Check that there's wipe code protection now.
|
||||
client.init_device()
|
||||
assert client.features.wipe_code_protection is True
|
||||
|
||||
# Check that the wipe code is correct.
|
||||
_check_wipe_code(client, WIPE_CODE4)
|
||||
|
||||
# Test change wipe code.
|
||||
_set_wipe_code(client, WIPE_CODE6)
|
||||
|
||||
# Check that there's still wipe code protection now.
|
||||
client.init_device()
|
||||
assert client.features.wipe_code_protection is True
|
||||
|
||||
# Check that the PIN is correct.
|
||||
_check_wipe_code(client, WIPE_CODE6)
|
||||
|
||||
# Test remove wipe code.
|
||||
_remove_wipe_code(client)
|
||||
|
||||
# Check that there's no wipe code protection now.
|
||||
client.init_device()
|
||||
assert client.features.wipe_code_protection is False
|
||||
|
||||
|
||||
def test_set_wipe_code_mismatch(client):
|
||||
# Check that there is no wipe code protection.
|
||||
assert client.features.wipe_code_protection is False
|
||||
|
||||
# Let's set a new wipe code.
|
||||
ret = client.call_raw(messages.ChangeWipeCode())
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
|
||||
# Confirm intent to set wipe code.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Send the new wipe code for the first time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(WIPE_CODE4)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# Send the new wipe code for the second time, but different.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
wipe_code_encoded = client.debug.encode_pin(WIPE_CODE6)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=wipe_code_encoded))
|
||||
|
||||
# The operation should fail, because the wipe codes are different.
|
||||
assert isinstance(ret, messages.Failure)
|
||||
assert ret.code == messages.FailureType.WipeCodeMismatch
|
||||
|
||||
# Check that there is no wipe code protection.
|
||||
client.init_device()
|
||||
assert client.features.wipe_code_protection is False
|
||||
|
||||
|
||||
@pytest.mark.setup_client(pin=PIN4)
|
||||
def test_set_wipe_code_to_pin(client):
|
||||
# Check that wipe code protection status is not revealed in locked state.
|
||||
assert client.features.wipe_code_protection is None
|
||||
|
||||
# Let's try setting the wipe code to the curent PIN value.
|
||||
ret = client.call_raw(messages.ChangeWipeCode())
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
|
||||
# Confirm intent to set wipe code.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Send current PIN.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.read_pin_encoded()
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# Send the new wipe code.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.read_pin_encoded()
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# The operation should fail, because the wipe code must be different from the PIN.
|
||||
assert isinstance(ret, messages.Failure)
|
||||
assert ret.code == messages.FailureType.ProcessError
|
||||
|
||||
# Check that there is no wipe code protection.
|
||||
client.init_device()
|
||||
assert client.features.wipe_code_protection is False
|
||||
|
||||
|
||||
def test_set_pin_to_wipe_code(client):
|
||||
# Set wipe code.
|
||||
_set_wipe_code(client, WIPE_CODE4)
|
||||
|
||||
# Try to set the PIN to the current wipe code value.
|
||||
ret = client.call_raw(messages.ChangePin())
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
|
||||
# Confirm intent to set PIN.
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Send the new PIN for the first time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.encode_pin(WIPE_CODE4)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# Send the new PIN for the second time.
|
||||
assert isinstance(ret, messages.PinMatrixRequest)
|
||||
pin_encoded = client.debug.encode_pin(WIPE_CODE4)
|
||||
ret = client.call_raw(messages.PinMatrixAck(pin=pin_encoded))
|
||||
|
||||
# The operation should fail, because the PIN must be different from the wipe code.
|
||||
assert isinstance(ret, messages.Failure)
|
||||
assert ret.code == messages.FailureType.ProcessError
|
||||
|
||||
# Check that there is no PIN protection.
|
||||
client.init_device()
|
||||
assert client.features.pin_protection is False
|
||||
ret = client.call_raw(messages.Ping(pin_protection=True))
|
||||
assert isinstance(ret, messages.Success)
|
260
tests/device_tests/test_msg_change_wipe_code_t2.py
Normal file
260
tests/device_tests/test_msg_change_wipe_code_t2.py
Normal file
@ -0,0 +1,260 @@
|
||||
# This file is part of the Trezor project.
|
||||
#
|
||||
# Copyright (C) 2012-2019 SatoshiLabs and contributors
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License version 3
|
||||
# as published by the Free Software Foundation.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the License along with this library.
|
||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
|
||||
import pytest
|
||||
|
||||
from trezorlib import device, messages
|
||||
from trezorlib.exceptions import Cancelled, TrezorFailure
|
||||
|
||||
PIN4 = "1234"
|
||||
WIPE_CODE4 = "4321"
|
||||
WIPE_CODE6 = "456789"
|
||||
|
||||
pytestmark = pytest.mark.skip_t1
|
||||
|
||||
|
||||
def _input_flow_set_pin(debug, pin):
|
||||
yield # do you want to set a new pin?
|
||||
print("set pin?")
|
||||
debug.press_yes()
|
||||
yield # enter new pin
|
||||
print(f"enter pin {pin}")
|
||||
debug.input(pin)
|
||||
yield # enter new pin again
|
||||
print(f"reenter pin {pin}")
|
||||
debug.input(pin)
|
||||
yield # success
|
||||
print("success")
|
||||
debug.press_yes()
|
||||
|
||||
|
||||
def _input_flow_change_pin(debug, old_pin, new_pin):
|
||||
yield # do you want to change pin?
|
||||
debug.press_yes()
|
||||
yield # enter current pin
|
||||
debug.input(old_pin)
|
||||
yield # enter new pin
|
||||
debug.input(new_pin)
|
||||
yield # enter new pin again
|
||||
debug.input(new_pin)
|
||||
yield # success
|
||||
debug.press_yes()
|
||||
|
||||
|
||||
def _input_flow_clear_pin(debug, old_pin):
|
||||
yield # do you want to remove pin?
|
||||
debug.press_yes()
|
||||
yield # enter current pin
|
||||
debug.input(old_pin)
|
||||
yield # success
|
||||
debug.press_yes()
|
||||
|
||||
|
||||
def _input_flow_set_wipe_code(debug, pin, wipe_code):
|
||||
yield # do you want to set/change the wipe_code?
|
||||
debug.press_yes()
|
||||
if pin is not None:
|
||||
yield # enter current pin
|
||||
debug.input(pin)
|
||||
yield # enter new wipe code
|
||||
debug.input(wipe_code)
|
||||
yield # enter new wipe code again
|
||||
debug.input(wipe_code)
|
||||
yield # success
|
||||
debug.press_yes()
|
||||
|
||||
|
||||
def _input_flow_remove_wipe_code(debug, pin):
|
||||
yield # do you want to remove wipe code?
|
||||
debug.press_yes()
|
||||
yield # enter current pin
|
||||
debug.input(pin)
|
||||
yield # success
|
||||
debug.press_yes()
|
||||
|
||||
|
||||
def _check_wipe_code(client, pin, wipe_code):
|
||||
client.clear_session()
|
||||
assert client.features.wipe_code_protection is True
|
||||
|
||||
# Try to change the PIN to the current wipe code value. The operation should fail.
|
||||
with client, pytest.raises(TrezorFailure):
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()] * 5
|
||||
+ [messages.Failure(code=messages.FailureType.PinInvalid)]
|
||||
)
|
||||
client.set_input_flow(_input_flow_change_pin(client.debug, pin, wipe_code))
|
||||
device.change_pin(client)
|
||||
|
||||
|
||||
@pytest.mark.setup_client(pin=PIN4)
|
||||
def test_set_remove_wipe_code(client):
|
||||
# Test set wipe code.
|
||||
assert client.features.wipe_code_protection is False
|
||||
|
||||
with client:
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()] * 5 + [messages.Success(), messages.Features()]
|
||||
)
|
||||
client.set_input_flow(_input_flow_set_wipe_code(client.debug, PIN4, WIPE_CODE4))
|
||||
|
||||
device.change_wipe_code(client)
|
||||
|
||||
client.init_device()
|
||||
assert client.features.wipe_code_protection is True
|
||||
_check_wipe_code(client, PIN4, WIPE_CODE4)
|
||||
|
||||
# Test change wipe code.
|
||||
with client:
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()] * 5 + [messages.Success(), messages.Features()]
|
||||
)
|
||||
client.set_input_flow(_input_flow_set_wipe_code(client.debug, PIN4, WIPE_CODE6))
|
||||
|
||||
device.change_wipe_code(client)
|
||||
|
||||
client.init_device()
|
||||
assert client.features.wipe_code_protection is True
|
||||
_check_wipe_code(client, PIN4, WIPE_CODE6)
|
||||
|
||||
# Test remove wipe code.
|
||||
with client:
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()] * 3 + [messages.Success(), messages.Features()]
|
||||
)
|
||||
client.set_input_flow(_input_flow_clear_pin(client.debug, PIN4))
|
||||
|
||||
device.change_wipe_code(client, remove=True)
|
||||
|
||||
client.init_device()
|
||||
assert client.features.wipe_code_protection is False
|
||||
|
||||
|
||||
def test_set_wipe_code_mismatch(client):
|
||||
# Let's set a wipe code.
|
||||
def input_flow():
|
||||
yield # do you want to set the wipe code?
|
||||
client.debug.press_yes()
|
||||
yield # enter new wipe code
|
||||
client.debug.input(WIPE_CODE4)
|
||||
yield # enter new wipe code again (but different)
|
||||
client.debug.input(WIPE_CODE6)
|
||||
|
||||
# failed retry
|
||||
yield # enter new wipe code
|
||||
client.cancel()
|
||||
|
||||
with client, pytest.raises(Cancelled):
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()] * 4 + [messages.Failure()]
|
||||
)
|
||||
client.set_input_flow(input_flow)
|
||||
|
||||
device.change_wipe_code(client)
|
||||
|
||||
# Check that there's still no wipe code protection now
|
||||
client.init_device()
|
||||
assert client.features.wipe_code_protection is False
|
||||
|
||||
|
||||
@pytest.mark.setup_client(pin=PIN4)
|
||||
def test_set_wipe_code_to_pin(client):
|
||||
def input_flow():
|
||||
yield # do you want to set the wipe code?
|
||||
client.debug.press_yes()
|
||||
yield # enter current pin
|
||||
client.debug.input(PIN4)
|
||||
yield # enter new wipe code (same as PIN)
|
||||
client.debug.input(PIN4)
|
||||
|
||||
# failed retry
|
||||
yield # enter new wipe code
|
||||
client.debug.input(WIPE_CODE4)
|
||||
yield # enter new wipe code again
|
||||
client.debug.input(WIPE_CODE4)
|
||||
yield # success
|
||||
client.debug.press_yes()
|
||||
|
||||
with client:
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()] * 6 + [messages.Success(), messages.Features()]
|
||||
)
|
||||
client.set_input_flow(input_flow)
|
||||
|
||||
device.change_wipe_code(client)
|
||||
|
||||
client.init_device()
|
||||
assert client.features.wipe_code_protection is True
|
||||
_check_wipe_code(client, PIN4, WIPE_CODE4)
|
||||
|
||||
|
||||
def test_set_pin_to_wipe_code(client):
|
||||
# Set wipe code.
|
||||
with client:
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()] * 4 + [messages.Success(), messages.Features()]
|
||||
)
|
||||
client.set_input_flow(_input_flow_set_wipe_code(client.debug, None, WIPE_CODE4))
|
||||
|
||||
device.change_wipe_code(client)
|
||||
|
||||
# Try to set the PIN to the current wipe code value.
|
||||
with client, pytest.raises(TrezorFailure):
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()] * 4
|
||||
+ [messages.Failure(code=messages.FailureType.PinInvalid)]
|
||||
)
|
||||
client.set_input_flow(_input_flow_set_pin(client.debug, WIPE_CODE4))
|
||||
device.change_pin(client)
|
||||
|
||||
|
||||
@pytest.mark.setup_client(pin=PIN4)
|
||||
def test_wipe_code_activate(client):
|
||||
import time
|
||||
|
||||
device_id = client.features.device_id
|
||||
|
||||
# Set wipe code.
|
||||
with client:
|
||||
client.set_expected_responses(
|
||||
[messages.ButtonRequest()] * 5 + [messages.Success(), messages.Features()]
|
||||
)
|
||||
client.set_input_flow(_input_flow_set_wipe_code(client.debug, PIN4, WIPE_CODE4))
|
||||
|
||||
device.change_wipe_code(client)
|
||||
|
||||
# Try to change the PIN.
|
||||
ret = client.call_raw(messages.ChangePin(remove=False))
|
||||
|
||||
# Confirm change PIN.
|
||||
assert isinstance(ret, messages.ButtonRequest)
|
||||
client.debug.press_yes()
|
||||
ret = client.call_raw(messages.ButtonAck())
|
||||
|
||||
# Enter the wipe code instead of the current PIN
|
||||
assert ret == messages.ButtonRequest(code=messages.ButtonRequestType.Other)
|
||||
client.debug.input(WIPE_CODE4)
|
||||
client._raw_write(messages.ButtonAck())
|
||||
|
||||
# Allow the device to display wipe code popup and restart.
|
||||
time.sleep(7)
|
||||
|
||||
# Check that the device has been wiped.
|
||||
client.init_device()
|
||||
assert client.features.initialized is False
|
||||
assert client.features.pin_protection is False
|
||||
assert client.features.wipe_code_protection is False
|
||||
assert client.features.device_id != device_id
|
Loading…
Reference in New Issue
Block a user