1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-14 03:30:02 +00:00

Merge pull request #720 from trezor/andrewkozlik/wipe-code

Implement wipe code feature.
This commit is contained in:
Andrew Kozlik 2019-12-04 15:24:49 +01:00 committed by GitHub
commit 6ca0288092
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1250 additions and 52 deletions

View File

@ -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;
}
}

View File

@ -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

View File

@ -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];

View File

@ -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)},

View File

@ -46,6 +46,6 @@
/*
* Current storage version.
*/
#define NORCOW_VERSION ((uint32_t)0x00000001)
#define NORCOW_VERSION ((uint32_t)0x00000002)
#endif

View File

@ -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]:
"""

View File

@ -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")

View File

@ -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

View File

@ -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")

View File

@ -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:

View 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

View 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),
}

View File

@ -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),
}

View File

@ -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]

View File

@ -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),
}

View File

@ -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]

View File

@ -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)

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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,

View File

@ -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);
}

View File

@ -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;

View File

@ -36,6 +36,6 @@
/*
* Current storage version.
*/
#define NORCOW_VERSION ((uint32_t)0x00000001)
#define NORCOW_VERSION ((uint32_t)0x00000002)
#endif

View File

@ -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.
~~~~~~~~~~~~~~~~~

View File

@ -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)

View File

@ -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))

View 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),
}

View File

@ -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),
}

View File

@ -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]

View File

@ -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),
}

View File

@ -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]

View File

@ -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),
}

View File

@ -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]

View File

@ -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

View File

@ -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
View File

@ -1,2 +1,4 @@
*.o
*.d
*.so
.hypothesis

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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.

View File

@ -40,6 +40,6 @@
/*
* Current storage version.
*/
#define NORCOW_VERSION ((uint32_t)0x00000001)
#define NORCOW_VERSION ((uint32_t)0x00000002)
#endif

View File

@ -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(

View File

@ -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)

View File

@ -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()

View File

@ -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")

View 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)

View 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