mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-19 12:58:13 +00:00
Merge branch 'andrewkozlik/sd-protect'
This commit is contained in:
commit
5b11d9a65d
@ -131,6 +131,24 @@ message ChangePin {
|
|||||||
optional bool remove = 1; // is PIN removal requested?
|
optional bool remove = 1; // is PIN removal requested?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Starts workflow for enabling/regenerating/disabling SD card protection
|
||||||
|
* @start
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message SdProtect {
|
||||||
|
optional SdProtectOperationType operation = 1;
|
||||||
|
/**
|
||||||
|
* Structure representing SD card protection operation
|
||||||
|
*/
|
||||||
|
enum SdProtectOperationType {
|
||||||
|
DISABLE = 0;
|
||||||
|
ENABLE = 1;
|
||||||
|
REFRESH = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request: Test if the device is alive, device sends back the message in Success response
|
* Request: Test if the device is alive, device sends back the message in Success response
|
||||||
* @start
|
* @start
|
||||||
|
@ -61,6 +61,7 @@ enum MessageType {
|
|||||||
MessageType_WordAck = 47 [(wire_in) = true];
|
MessageType_WordAck = 47 [(wire_in) = true];
|
||||||
MessageType_GetFeatures = 55 [(wire_in) = true];
|
MessageType_GetFeatures = 55 [(wire_in) = true];
|
||||||
MessageType_SetU2FCounter = 63 [(wire_in) = true];
|
MessageType_SetU2FCounter = 63 [(wire_in) = true];
|
||||||
|
MessageType_SdProtect = 79 [(wire_in) = true];
|
||||||
|
|
||||||
// Bootloader
|
// Bootloader
|
||||||
MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true];
|
MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true];
|
||||||
|
@ -67,29 +67,38 @@ STATIC mp_obj_t mod_trezorconfig_init(size_t n_args, const mp_obj_t *args) {
|
|||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorconfig_init_obj, 0, 1,
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorconfig_init_obj, 0, 1,
|
||||||
mod_trezorconfig_init);
|
mod_trezorconfig_init);
|
||||||
|
|
||||||
/// def unlock(pin: int) -> bool:
|
/// def unlock(pin: int, ext_salt: Optional[bytes]) -> bool:
|
||||||
/// """
|
/// """
|
||||||
/// Attempts to unlock the storage with given PIN. Returns True on
|
/// Attempts to unlock the storage with the given PIN and external salt.
|
||||||
/// success, False on failure.
|
/// Returns True on success, False on failure.
|
||||||
/// """
|
/// """
|
||||||
STATIC mp_obj_t mod_trezorconfig_unlock(mp_obj_t pin) {
|
STATIC mp_obj_t mod_trezorconfig_unlock(mp_obj_t pin, mp_obj_t ext_salt) {
|
||||||
uint32_t pin_i = trezor_obj_get_uint(pin);
|
uint32_t pin_i = trezor_obj_get_uint(pin);
|
||||||
if (sectrue != storage_unlock(pin_i)) {
|
mp_buffer_info_t ext_salt_b;
|
||||||
|
ext_salt_b.buf = NULL;
|
||||||
|
if (ext_salt != mp_const_none) {
|
||||||
|
mp_get_buffer_raise(ext_salt, &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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sectrue != storage_unlock(pin_i, ext_salt_b.buf)) {
|
||||||
return mp_const_false;
|
return mp_const_false;
|
||||||
}
|
}
|
||||||
return mp_const_true;
|
return mp_const_true;
|
||||||
}
|
}
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorconfig_unlock_obj,
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorconfig_unlock_obj,
|
||||||
mod_trezorconfig_unlock);
|
mod_trezorconfig_unlock);
|
||||||
|
|
||||||
/// def check_pin(pin: int) -> bool:
|
/// def check_pin(pin: int, ext_salt: Optional[bytes]) -> bool:
|
||||||
/// """
|
/// """
|
||||||
/// Check the given PIN. Returns True on success, False on failure.
|
/// Check the given PIN with the given external salt.
|
||||||
|
/// Returns True on success, False on failure.
|
||||||
/// """
|
/// """
|
||||||
STATIC mp_obj_t mod_trezorconfig_check_pin(mp_obj_t pin) {
|
STATIC mp_obj_t mod_trezorconfig_check_pin(mp_obj_t pin, mp_obj_t ext_salt) {
|
||||||
return mod_trezorconfig_unlock(pin);
|
return mod_trezorconfig_unlock(pin, ext_salt);
|
||||||
}
|
}
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorconfig_check_pin_obj,
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorconfig_check_pin_obj,
|
||||||
mod_trezorconfig_check_pin);
|
mod_trezorconfig_check_pin);
|
||||||
|
|
||||||
/// def lock() -> None:
|
/// def lock() -> None:
|
||||||
@ -126,20 +135,43 @@ STATIC mp_obj_t mod_trezorconfig_get_pin_rem(void) {
|
|||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorconfig_get_pin_rem_obj,
|
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorconfig_get_pin_rem_obj,
|
||||||
mod_trezorconfig_get_pin_rem);
|
mod_trezorconfig_get_pin_rem);
|
||||||
|
|
||||||
/// def change_pin(pin: int, newpin: int) -> bool:
|
/// def change_pin(
|
||||||
|
/// oldpin: int,
|
||||||
|
/// newpin: int,
|
||||||
|
/// old_ext_salt: Optional[bytes],
|
||||||
|
/// new_ext_salt: Optional[bytes],
|
||||||
|
/// ) -> bool:
|
||||||
/// """
|
/// """
|
||||||
/// Change PIN. Returns True on success, False on failure.
|
/// Change PIN and external salt. Returns True on success, False on failure.
|
||||||
/// """
|
/// """
|
||||||
STATIC mp_obj_t mod_trezorconfig_change_pin(mp_obj_t pin, mp_obj_t newpin) {
|
STATIC mp_obj_t mod_trezorconfig_change_pin(size_t n_args,
|
||||||
uint32_t pin_i = trezor_obj_get_uint(pin);
|
const mp_obj_t *args) {
|
||||||
uint32_t newpin_i = trezor_obj_get_uint(newpin);
|
uint32_t oldpin = trezor_obj_get_uint(args[0]);
|
||||||
if (sectrue != storage_change_pin(pin_i, newpin_i)) {
|
uint32_t newpin = trezor_obj_get_uint(args[1]);
|
||||||
|
mp_buffer_info_t ext_salt_b;
|
||||||
|
const uint8_t *old_ext_salt = NULL;
|
||||||
|
if (args[2] != mp_const_none) {
|
||||||
|
mp_get_buffer_raise(args[2], &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.");
|
||||||
|
old_ext_salt = ext_salt_b.buf;
|
||||||
|
}
|
||||||
|
const uint8_t *new_ext_salt = NULL;
|
||||||
|
if (args[3] != mp_const_none) {
|
||||||
|
mp_get_buffer_raise(args[3], &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.");
|
||||||
|
new_ext_salt = ext_salt_b.buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sectrue !=
|
||||||
|
storage_change_pin(oldpin, newpin, old_ext_salt, new_ext_salt)) {
|
||||||
return mp_const_false;
|
return mp_const_false;
|
||||||
}
|
}
|
||||||
return mp_const_true;
|
return mp_const_true;
|
||||||
}
|
}
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorconfig_change_pin_obj,
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorconfig_change_pin_obj, 4,
|
||||||
mod_trezorconfig_change_pin);
|
4, mod_trezorconfig_change_pin);
|
||||||
|
|
||||||
/// def get(app: int, key: int, public: bool = False) -> Optional[bytes]:
|
/// def get(app: int, key: int, public: bool = False) -> Optional[bytes]:
|
||||||
/// """
|
/// """
|
||||||
|
@ -12,17 +12,18 @@ def init(
|
|||||||
|
|
||||||
|
|
||||||
# extmod/modtrezorconfig/modtrezorconfig.c
|
# extmod/modtrezorconfig/modtrezorconfig.c
|
||||||
def unlock(pin: int) -> bool:
|
def unlock(pin: int, ext_salt: Optional[bytes]) -> bool:
|
||||||
"""
|
"""
|
||||||
Attempts to unlock the storage with given PIN. Returns True on
|
Attempts to unlock the storage with the given PIN and external salt.
|
||||||
success, False on failure.
|
Returns True on success, False on failure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
# extmod/modtrezorconfig/modtrezorconfig.c
|
# extmod/modtrezorconfig/modtrezorconfig.c
|
||||||
def check_pin(pin: int) -> bool:
|
def check_pin(pin: int, ext_salt: Optional[bytes]) -> bool:
|
||||||
"""
|
"""
|
||||||
Check the given PIN. Returns True on success, False on failure.
|
Check the given PIN with the given external salt.
|
||||||
|
Returns True on success, False on failure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -48,9 +49,14 @@ def get_pin_rem() -> int:
|
|||||||
|
|
||||||
|
|
||||||
# extmod/modtrezorconfig/modtrezorconfig.c
|
# extmod/modtrezorconfig/modtrezorconfig.c
|
||||||
def change_pin(pin: int, newpin: int) -> bool:
|
def change_pin(
|
||||||
|
oldpin: int,
|
||||||
|
newpin: int,
|
||||||
|
old_ext_salt: Optional[bytes],
|
||||||
|
new_ext_salt: Optional[bytes],
|
||||||
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Change PIN. Returns True on success, False on failure.
|
Change PIN and external salt. Returns True on success, False on failure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,17 @@
|
|||||||
from trezor import loop
|
from trezor import config, loop, ui, wire
|
||||||
|
from trezor.messages import ButtonRequestType
|
||||||
|
from trezor.messages.ButtonAck import ButtonAck
|
||||||
|
from trezor.messages.ButtonRequest import ButtonRequest
|
||||||
|
from trezor.pin import pin_to_int
|
||||||
from trezor.ui.pin import CANCELLED, PinDialog
|
from trezor.ui.pin import CANCELLED, PinDialog
|
||||||
|
from trezor.ui.popup import Popup
|
||||||
|
from trezor.ui.text import Text
|
||||||
|
|
||||||
|
from apps.common.sd_salt import request_sd_salt
|
||||||
|
from apps.common.storage import device
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from typing import Any, Optional, Tuple
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
from apps.debug import input_signal
|
from apps.debug import input_signal
|
||||||
@ -9,6 +21,10 @@ class PinCancelled(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PinInvalid(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def request_pin(
|
async def request_pin(
|
||||||
prompt: str = "Enter your PIN",
|
prompt: str = "Enter your PIN",
|
||||||
attempts_remaining: int = None,
|
attempts_remaining: int = None,
|
||||||
@ -31,3 +47,68 @@ async def request_pin(
|
|||||||
if result is CANCELLED:
|
if result is CANCELLED:
|
||||||
raise PinCancelled
|
raise PinCancelled
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
async def request_pin_ack(ctx: wire.Context, *args: Any, **kwargs: Any) -> str:
|
||||||
|
try:
|
||||||
|
await ctx.call(ButtonRequest(code=ButtonRequestType.Other), ButtonAck)
|
||||||
|
return await ctx.wait(request_pin(*args, **kwargs))
|
||||||
|
except PinCancelled:
|
||||||
|
raise wire.ActionCancelled("Cancelled")
|
||||||
|
|
||||||
|
|
||||||
|
async def request_pin_confirm(ctx: wire.Context, *args: Any, **kwargs: Any) -> str:
|
||||||
|
while True:
|
||||||
|
pin1 = await request_pin_ack(ctx, "Enter new PIN", *args, **kwargs)
|
||||||
|
pin2 = await request_pin_ack(ctx, "Re-enter new PIN", *args, **kwargs)
|
||||||
|
if pin1 == pin2:
|
||||||
|
return pin1
|
||||||
|
await pin_mismatch()
|
||||||
|
|
||||||
|
|
||||||
|
async def pin_mismatch() -> None:
|
||||||
|
text = Text("PIN mismatch", ui.ICON_WRONG, ui.RED)
|
||||||
|
text.normal("The PINs you entered", "do not match.")
|
||||||
|
text.normal("")
|
||||||
|
text.normal("Please try again.")
|
||||||
|
popup = Popup(text, 3000) # show for 3 seconds
|
||||||
|
await popup
|
||||||
|
|
||||||
|
|
||||||
|
async def request_pin_and_sd_salt(
|
||||||
|
ctx: wire.Context, prompt: str = "Enter your PIN", allow_cancel: bool = True
|
||||||
|
) -> Tuple[str, Optional[bytearray]]:
|
||||||
|
salt_auth_key = device.get_sd_salt_auth_key()
|
||||||
|
if salt_auth_key is not None:
|
||||||
|
salt = await request_sd_salt(ctx, salt_auth_key) # type: Optional[bytearray]
|
||||||
|
else:
|
||||||
|
salt = None
|
||||||
|
|
||||||
|
if config.has_pin():
|
||||||
|
pin = await request_pin_ack(ctx, prompt, config.get_pin_rem(), allow_cancel)
|
||||||
|
else:
|
||||||
|
pin = ""
|
||||||
|
|
||||||
|
return pin, salt
|
||||||
|
|
||||||
|
|
||||||
|
async def verify_user_pin(
|
||||||
|
prompt: str = "Enter your PIN", allow_cancel: bool = True, retry: bool = True
|
||||||
|
) -> None:
|
||||||
|
salt_auth_key = device.get_sd_salt_auth_key()
|
||||||
|
if salt_auth_key is not None:
|
||||||
|
salt = await request_sd_salt(None, salt_auth_key) # type: Optional[bytearray]
|
||||||
|
else:
|
||||||
|
salt = None
|
||||||
|
|
||||||
|
if not config.has_pin() and not config.check_pin(pin_to_int(""), salt):
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
while retry:
|
||||||
|
pin = await request_pin(prompt, config.get_pin_rem(), allow_cancel)
|
||||||
|
if config.check_pin(pin_to_int(pin), salt):
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
prompt = "Wrong PIN, enter again"
|
||||||
|
|
||||||
|
raise PinInvalid
|
||||||
|
193
core/src/apps/common/sd_salt.py
Normal file
193
core/src/apps/common/sd_salt.py
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
from micropython import const
|
||||||
|
|
||||||
|
from trezor import io, ui, wire
|
||||||
|
from trezor.crypto import hmac
|
||||||
|
from trezor.crypto.hashlib import sha256
|
||||||
|
from trezor.ui.confirm import Confirm
|
||||||
|
from trezor.ui.text import Text
|
||||||
|
from trezor.utils import consteq
|
||||||
|
|
||||||
|
from apps.common import storage
|
||||||
|
from apps.common.confirm import require_confirm
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class SdProtectCancelled(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
SD_SALT_LEN_BYTES = const(32)
|
||||||
|
SD_SALT_AUTH_TAG_LEN_BYTES = const(16)
|
||||||
|
SD_SALT_AUTH_KEY_LEN_BYTES = const(16)
|
||||||
|
|
||||||
|
|
||||||
|
async def wrong_card_dialog(ctx: Optional[wire.Context]) -> None:
|
||||||
|
text = Text("SD card protection", ui.ICON_WRONG)
|
||||||
|
text.bold("Wrong SD card.")
|
||||||
|
text.br_half()
|
||||||
|
text.normal("Please unplug the", "device and insert a", "different card.")
|
||||||
|
if ctx is None:
|
||||||
|
await Confirm(text, confirm=None)
|
||||||
|
else:
|
||||||
|
await require_confirm(ctx, text, confirm=None)
|
||||||
|
|
||||||
|
|
||||||
|
async def insert_card_dialog(ctx: Optional[wire.Context]) -> None:
|
||||||
|
text = Text("SD card protection")
|
||||||
|
text.bold("SD card required.")
|
||||||
|
text.br_half()
|
||||||
|
text.normal("Please unplug the", "device and insert your", "SD card.")
|
||||||
|
if ctx is None:
|
||||||
|
await Confirm(text, confirm=None)
|
||||||
|
else:
|
||||||
|
await require_confirm(ctx, text, confirm=None)
|
||||||
|
|
||||||
|
|
||||||
|
async def request_sd_salt(
|
||||||
|
ctx: Optional[wire.Context], salt_auth_key: bytes
|
||||||
|
) -> bytearray:
|
||||||
|
device_dir = "/trezor/device_%s" % storage.device.get_device_id()
|
||||||
|
salt_path = "%s/salt" % device_dir
|
||||||
|
new_salt_path = "%s/salt.new" % device_dir
|
||||||
|
|
||||||
|
sd = io.SDCard()
|
||||||
|
fs = io.FatFS()
|
||||||
|
if not sd.power(True):
|
||||||
|
await insert_card_dialog(ctx)
|
||||||
|
raise SdProtectCancelled
|
||||||
|
|
||||||
|
try:
|
||||||
|
fs.mount()
|
||||||
|
|
||||||
|
# Load salt if it exists.
|
||||||
|
try:
|
||||||
|
with fs.open(salt_path, "r") as f:
|
||||||
|
salt = bytearray(SD_SALT_LEN_BYTES) # type: Optional[bytearray]
|
||||||
|
salt_tag = bytearray(SD_SALT_AUTH_TAG_LEN_BYTES)
|
||||||
|
f.read(salt)
|
||||||
|
f.read(salt_tag)
|
||||||
|
except OSError:
|
||||||
|
salt = None
|
||||||
|
|
||||||
|
if salt is not None and consteq(
|
||||||
|
hmac.new(salt_auth_key, salt, sha256).digest()[:SD_SALT_AUTH_TAG_LEN_BYTES],
|
||||||
|
salt_tag,
|
||||||
|
):
|
||||||
|
return salt
|
||||||
|
|
||||||
|
# Load salt.new if it exists.
|
||||||
|
try:
|
||||||
|
with fs.open(new_salt_path, "r") as f:
|
||||||
|
new_salt = bytearray(SD_SALT_LEN_BYTES) # type: Optional[bytearray]
|
||||||
|
new_salt_tag = bytearray(SD_SALT_AUTH_TAG_LEN_BYTES)
|
||||||
|
f.read(new_salt)
|
||||||
|
f.read(new_salt_tag)
|
||||||
|
except OSError:
|
||||||
|
new_salt = None
|
||||||
|
|
||||||
|
if new_salt is not None and consteq(
|
||||||
|
hmac.new(salt_auth_key, new_salt, sha256).digest()[
|
||||||
|
:SD_SALT_AUTH_TAG_LEN_BYTES
|
||||||
|
],
|
||||||
|
new_salt_tag,
|
||||||
|
):
|
||||||
|
# SD salt regeneration was interrupted earlier. Bring into consistent state.
|
||||||
|
# TODO Possibly overwrite salt file with random data.
|
||||||
|
try:
|
||||||
|
fs.unlink(salt_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
fs.rename(new_salt_path, salt_path)
|
||||||
|
return new_salt
|
||||||
|
finally:
|
||||||
|
fs.unmount()
|
||||||
|
sd.power(False)
|
||||||
|
|
||||||
|
await wrong_card_dialog(ctx)
|
||||||
|
raise SdProtectCancelled
|
||||||
|
|
||||||
|
|
||||||
|
async def set_sd_salt(
|
||||||
|
ctx: Optional[wire.Context], salt: bytes, salt_tag: bytes, filename: str = "salt"
|
||||||
|
) -> None:
|
||||||
|
device_dir = "/trezor/device_%s" % storage.device.get_device_id()
|
||||||
|
salt_path = "%s/%s" % (device_dir, filename)
|
||||||
|
|
||||||
|
sd = io.SDCard()
|
||||||
|
fs = io.FatFS()
|
||||||
|
if not sd.power(True):
|
||||||
|
await insert_card_dialog(ctx)
|
||||||
|
raise SdProtectCancelled
|
||||||
|
|
||||||
|
try:
|
||||||
|
fs.mount()
|
||||||
|
|
||||||
|
try:
|
||||||
|
fs.mkdir("/trezor")
|
||||||
|
except OSError:
|
||||||
|
# Directory already exists.
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
fs.mkdir(device_dir)
|
||||||
|
except OSError:
|
||||||
|
# Directory already exists.
|
||||||
|
pass
|
||||||
|
|
||||||
|
with fs.open(salt_path, "w") as f:
|
||||||
|
f.write(salt)
|
||||||
|
f.write(salt_tag)
|
||||||
|
finally:
|
||||||
|
fs.unmount()
|
||||||
|
sd.power(False)
|
||||||
|
|
||||||
|
|
||||||
|
async def stage_sd_salt(
|
||||||
|
ctx: Optional[wire.Context], salt: bytes, salt_tag: bytes
|
||||||
|
) -> None:
|
||||||
|
await set_sd_salt(ctx, salt, salt_tag, "salt.new")
|
||||||
|
|
||||||
|
|
||||||
|
async def commit_sd_salt(ctx: Optional[wire.Context]) -> None:
|
||||||
|
device_dir = "/trezor/device_%s" % storage.device.get_device_id()
|
||||||
|
salt_path = "%s/salt" % device_dir
|
||||||
|
new_salt_path = "%s/salt.new" % device_dir
|
||||||
|
|
||||||
|
sd = io.SDCard()
|
||||||
|
fs = io.FatFS()
|
||||||
|
if not sd.power(True):
|
||||||
|
await insert_card_dialog(ctx)
|
||||||
|
raise SdProtectCancelled
|
||||||
|
|
||||||
|
try:
|
||||||
|
fs.mount()
|
||||||
|
# TODO Possibly overwrite salt file with random data.
|
||||||
|
try:
|
||||||
|
fs.unlink(salt_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
fs.rename(new_salt_path, salt_path)
|
||||||
|
finally:
|
||||||
|
fs.unmount()
|
||||||
|
sd.power(False)
|
||||||
|
|
||||||
|
|
||||||
|
async def remove_sd_salt(ctx: Optional[wire.Context]) -> None:
|
||||||
|
device_dir = "/trezor/device_%s" % storage.device.get_device_id()
|
||||||
|
salt_path = "%s/salt" % device_dir
|
||||||
|
|
||||||
|
sd = io.SDCard()
|
||||||
|
fs = io.FatFS()
|
||||||
|
if not sd.power(True):
|
||||||
|
await insert_card_dialog(ctx)
|
||||||
|
raise SdProtectCancelled
|
||||||
|
|
||||||
|
try:
|
||||||
|
fs.mount()
|
||||||
|
# TODO Possibly overwrite salt file with random data.
|
||||||
|
fs.unlink(salt_path)
|
||||||
|
finally:
|
||||||
|
fs.unmount()
|
||||||
|
sd.power(False)
|
@ -28,8 +28,8 @@ def get(app: int, key: int, public: bool = False) -> Optional[bytes]:
|
|||||||
return config.get(app, key, public)
|
return config.get(app, key, public)
|
||||||
|
|
||||||
|
|
||||||
def delete(app: int, key: int) -> None:
|
def delete(app: int, key: int, public: bool = False) -> None:
|
||||||
config.delete(app, key)
|
config.delete(app, key, public)
|
||||||
|
|
||||||
|
|
||||||
def set_true_or_delete(app: int, key: int, value: bool) -> None:
|
def set_true_or_delete(app: int, key: int, value: bool) -> None:
|
||||||
|
@ -3,6 +3,7 @@ from ubinascii import hexlify
|
|||||||
|
|
||||||
from trezor.crypto import random
|
from trezor.crypto import random
|
||||||
|
|
||||||
|
from apps.common.sd_salt import SD_SALT_AUTH_KEY_LEN_BYTES
|
||||||
from apps.common.storage import common
|
from apps.common.storage import common
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
@ -31,6 +32,7 @@ _MNEMONIC_TYPE = const(0x0E) # int
|
|||||||
_ROTATION = const(0x0F) # int
|
_ROTATION = const(0x0F) # int
|
||||||
_SLIP39_IDENTIFIER = const(0x10) # bool
|
_SLIP39_IDENTIFIER = const(0x10) # bool
|
||||||
_SLIP39_ITERATION_EXPONENT = const(0x11) # int
|
_SLIP39_ITERATION_EXPONENT = const(0x11) # int
|
||||||
|
_SD_SALT_AUTH_KEY = const(0x12) # bytes
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
HOMESCREEN_MAXSIZE = 16384
|
HOMESCREEN_MAXSIZE = 16384
|
||||||
@ -234,3 +236,25 @@ def get_slip39_iteration_exponent() -> Optional[int]:
|
|||||||
The device's actual SLIP-39 iteration exponent used in passphrase derivation.
|
The device's actual SLIP-39 iteration exponent used in passphrase derivation.
|
||||||
"""
|
"""
|
||||||
return common.get_uint8(_NAMESPACE, _SLIP39_ITERATION_EXPONENT)
|
return common.get_uint8(_NAMESPACE, _SLIP39_ITERATION_EXPONENT)
|
||||||
|
|
||||||
|
|
||||||
|
def get_sd_salt_auth_key() -> Optional[bytes]:
|
||||||
|
"""
|
||||||
|
The key used to check the authenticity of the SD card salt.
|
||||||
|
"""
|
||||||
|
auth_key = common.get(_NAMESPACE, _SD_SALT_AUTH_KEY, public=True)
|
||||||
|
if auth_key is not None and len(auth_key) != SD_SALT_AUTH_KEY_LEN_BYTES:
|
||||||
|
raise ValueError
|
||||||
|
return auth_key
|
||||||
|
|
||||||
|
|
||||||
|
def set_sd_salt_auth_key(auth_key: Optional[bytes]) -> None:
|
||||||
|
"""
|
||||||
|
The key used to check the authenticity of the SD card salt.
|
||||||
|
"""
|
||||||
|
if auth_key is not None:
|
||||||
|
if len(auth_key) != SD_SALT_AUTH_KEY_LEN_BYTES:
|
||||||
|
raise ValueError
|
||||||
|
return common.set(_NAMESPACE, _SD_SALT_AUTH_KEY, auth_key, public=True)
|
||||||
|
else:
|
||||||
|
return common.delete(_NAMESPACE, _SD_SALT_AUTH_KEY, public=True)
|
||||||
|
@ -14,3 +14,4 @@ def boot() -> None:
|
|||||||
wire.add(MessageType.ApplyFlags, __name__, "apply_flags")
|
wire.add(MessageType.ApplyFlags, __name__, "apply_flags")
|
||||||
wire.add(MessageType.ChangePin, __name__, "change_pin")
|
wire.add(MessageType.ChangePin, __name__, "change_pin")
|
||||||
wire.add(MessageType.SetU2FCounter, __name__, "set_u2f_counter")
|
wire.add(MessageType.SetU2FCounter, __name__, "set_u2f_counter")
|
||||||
|
wire.add(MessageType.SdProtect, __name__, "sd_protect")
|
||||||
|
@ -1,34 +1,26 @@
|
|||||||
from trezor import config, ui, wire
|
from trezor import config, ui, wire
|
||||||
from trezor.messages import ButtonRequestType
|
|
||||||
from trezor.messages.ButtonAck import ButtonAck
|
|
||||||
from trezor.messages.ButtonRequest import ButtonRequest
|
|
||||||
from trezor.messages.Success import Success
|
from trezor.messages.Success import Success
|
||||||
from trezor.pin import pin_to_int
|
from trezor.pin import pin_to_int
|
||||||
from trezor.ui.popup import Popup
|
|
||||||
from trezor.ui.text import Text
|
from trezor.ui.text import Text
|
||||||
|
|
||||||
from apps.common.confirm import require_confirm
|
from apps.common.confirm import require_confirm
|
||||||
from apps.common.request_pin import PinCancelled, request_pin
|
from apps.common.request_pin import request_pin_and_sd_salt, request_pin_confirm
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import Any
|
|
||||||
from trezor.messages.ChangePin import ChangePin
|
from trezor.messages.ChangePin import ChangePin
|
||||||
|
|
||||||
|
|
||||||
async def change_pin(ctx: wire.Context, msg: ChangePin) -> Success:
|
async def change_pin(ctx: wire.Context, msg: ChangePin) -> Success:
|
||||||
|
|
||||||
# confirm that user wants to change the pin
|
# confirm that user wants to change the pin
|
||||||
await require_confirm_change_pin(ctx, msg)
|
await require_confirm_change_pin(ctx, msg)
|
||||||
|
|
||||||
# get current pin, return failure if invalid
|
# get old pin
|
||||||
if config.has_pin():
|
curpin, salt = await request_pin_and_sd_salt(ctx, "Enter old PIN")
|
||||||
curpin = await request_pin_ack(ctx, "Enter old PIN", config.get_pin_rem())
|
|
||||||
# if removing, defer check to change_pin()
|
# if changing pin, pre-check the entered pin before getting new pin
|
||||||
if not msg.remove:
|
if curpin and not msg.remove:
|
||||||
if not config.check_pin(pin_to_int(curpin)):
|
if not config.check_pin(pin_to_int(curpin), salt):
|
||||||
raise wire.PinInvalid("PIN invalid")
|
raise wire.PinInvalid("PIN invalid")
|
||||||
else:
|
|
||||||
curpin = ""
|
|
||||||
|
|
||||||
# get new pin
|
# get new pin
|
||||||
if not msg.remove:
|
if not msg.remove:
|
||||||
@ -37,7 +29,7 @@ async def change_pin(ctx: wire.Context, msg: ChangePin) -> Success:
|
|||||||
newpin = ""
|
newpin = ""
|
||||||
|
|
||||||
# write into storage
|
# write into storage
|
||||||
if not config.change_pin(pin_to_int(curpin), pin_to_int(newpin)):
|
if not config.change_pin(pin_to_int(curpin), pin_to_int(newpin), salt, salt):
|
||||||
raise wire.PinInvalid("PIN invalid")
|
raise wire.PinInvalid("PIN invalid")
|
||||||
|
|
||||||
if newpin:
|
if newpin:
|
||||||
@ -66,29 +58,3 @@ def require_confirm_change_pin(ctx: wire.Context, msg: ChangePin) -> None:
|
|||||||
text.normal("Do you really want to")
|
text.normal("Do you really want to")
|
||||||
text.bold("enable PIN protection?")
|
text.bold("enable PIN protection?")
|
||||||
return require_confirm(ctx, text)
|
return require_confirm(ctx, text)
|
||||||
|
|
||||||
|
|
||||||
async def request_pin_confirm(ctx: wire.Context, *args: Any, **kwargs: Any) -> str:
|
|
||||||
while True:
|
|
||||||
pin1 = await request_pin_ack(ctx, "Enter new PIN", *args, **kwargs)
|
|
||||||
pin2 = await request_pin_ack(ctx, "Re-enter new PIN", *args, **kwargs)
|
|
||||||
if pin1 == pin2:
|
|
||||||
return pin1
|
|
||||||
await pin_mismatch()
|
|
||||||
|
|
||||||
|
|
||||||
async def request_pin_ack(ctx: wire.Context, *args: Any, **kwargs: Any) -> str:
|
|
||||||
try:
|
|
||||||
await ctx.call(ButtonRequest(code=ButtonRequestType.Other), ButtonAck)
|
|
||||||
return await ctx.wait(request_pin(*args, **kwargs))
|
|
||||||
except PinCancelled:
|
|
||||||
raise wire.ActionCancelled("Cancelled")
|
|
||||||
|
|
||||||
|
|
||||||
async def pin_mismatch() -> None:
|
|
||||||
text = Text("PIN mismatch", ui.ICON_WRONG, ui.RED)
|
|
||||||
text.normal("The PINs you entered", "do not match.")
|
|
||||||
text.normal("")
|
|
||||||
text.normal("Please try again.")
|
|
||||||
popup = Popup(text, 3000) # show for 3 seconds
|
|
||||||
await popup
|
|
||||||
|
@ -55,6 +55,6 @@ async def load_device(ctx, msg):
|
|||||||
use_passphrase=msg.passphrase_protection, label=msg.label
|
use_passphrase=msg.passphrase_protection, label=msg.label
|
||||||
)
|
)
|
||||||
if msg.pin:
|
if msg.pin:
|
||||||
config.change_pin(pin_to_int(""), pin_to_int(msg.pin))
|
config.change_pin(pin_to_int(""), pin_to_int(msg.pin), None, None)
|
||||||
|
|
||||||
return Success(message="Device loaded")
|
return Success(message="Device loaded")
|
||||||
|
@ -6,7 +6,7 @@ from trezor.ui.text import Text
|
|||||||
|
|
||||||
from apps.common import storage
|
from apps.common import storage
|
||||||
from apps.common.confirm import require_confirm
|
from apps.common.confirm import require_confirm
|
||||||
from apps.management.change_pin import request_pin_ack, request_pin_confirm
|
from apps.common.request_pin import request_pin_and_sd_salt, request_pin_confirm
|
||||||
from apps.management.recovery_device.homescreen import recovery_process
|
from apps.management.recovery_device.homescreen import recovery_process
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
@ -24,13 +24,10 @@ async def recovery_device(ctx: wire.Context, msg: RecoveryDevice) -> Success:
|
|||||||
|
|
||||||
await _continue_dialog(ctx, msg)
|
await _continue_dialog(ctx, msg)
|
||||||
|
|
||||||
# for dry run pin needs to entered
|
# for dry run pin needs to be entered
|
||||||
if msg.dry_run:
|
if msg.dry_run:
|
||||||
if config.has_pin():
|
curpin, salt = await request_pin_and_sd_salt(ctx, "Enter PIN")
|
||||||
curpin = await request_pin_ack(ctx, "Enter PIN", config.get_pin_rem())
|
if not config.check_pin(pin_to_int(curpin), salt):
|
||||||
else:
|
|
||||||
curpin = ""
|
|
||||||
if not config.check_pin(pin_to_int(curpin)):
|
|
||||||
raise wire.PinInvalid("PIN invalid")
|
raise wire.PinInvalid("PIN invalid")
|
||||||
|
|
||||||
# set up pin if requested
|
# set up pin if requested
|
||||||
@ -38,7 +35,7 @@ async def recovery_device(ctx: wire.Context, msg: RecoveryDevice) -> Success:
|
|||||||
if msg.dry_run:
|
if msg.dry_run:
|
||||||
raise wire.ProcessError("Can't setup PIN during dry_run recovery.")
|
raise wire.ProcessError("Can't setup PIN during dry_run recovery.")
|
||||||
newpin = await request_pin_confirm(ctx, allow_cancel=False)
|
newpin = await request_pin_confirm(ctx, allow_cancel=False)
|
||||||
config.change_pin(pin_to_int(""), pin_to_int(newpin))
|
config.change_pin(pin_to_int(""), pin_to_int(newpin), None, None)
|
||||||
|
|
||||||
if msg.u2f_counter:
|
if msg.u2f_counter:
|
||||||
storage.device.set_u2f_counter(msg.u2f_counter)
|
storage.device.set_u2f_counter(msg.u2f_counter)
|
||||||
|
@ -10,7 +10,7 @@ from trezor.ui.text import Text
|
|||||||
|
|
||||||
from apps.common import mnemonic, storage
|
from apps.common import mnemonic, storage
|
||||||
from apps.common.confirm import require_confirm
|
from apps.common.confirm import require_confirm
|
||||||
from apps.management.change_pin import request_pin_confirm
|
from apps.common.request_pin import request_pin_confirm
|
||||||
from apps.management.common import layout
|
from apps.management.common import layout
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
@ -71,7 +71,7 @@ async def reset_device(ctx: wire.Context, msg: ResetDevice) -> Success:
|
|||||||
await backup_bip39_wallet(ctx, secret)
|
await backup_bip39_wallet(ctx, secret)
|
||||||
|
|
||||||
# write PIN into storage
|
# write PIN into storage
|
||||||
if not config.change_pin(pin_to_int(""), pin_to_int(newpin)):
|
if not config.change_pin(pin_to_int(""), pin_to_int(newpin), None, None):
|
||||||
raise wire.ProcessError("Could not change PIN")
|
raise wire.ProcessError("Could not change PIN")
|
||||||
|
|
||||||
# write settings and master secret into storage
|
# write settings and master secret into storage
|
||||||
|
169
core/src/apps/management/sd_protect.py
Normal file
169
core/src/apps/management/sd_protect.py
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
from trezor import config, ui, wire
|
||||||
|
from trezor.crypto import hmac, random
|
||||||
|
from trezor.crypto.hashlib import sha256
|
||||||
|
from trezor.messages import SdProtectOperationType
|
||||||
|
from trezor.messages.Success import Success
|
||||||
|
from trezor.pin import pin_to_int
|
||||||
|
from trezor.ui.text import Text
|
||||||
|
|
||||||
|
from apps.common.confirm import require_confirm
|
||||||
|
from apps.common.request_pin import request_pin_ack, request_pin_and_sd_salt
|
||||||
|
from apps.common.sd_salt import (
|
||||||
|
SD_SALT_AUTH_KEY_LEN_BYTES,
|
||||||
|
SD_SALT_AUTH_TAG_LEN_BYTES,
|
||||||
|
SD_SALT_LEN_BYTES,
|
||||||
|
commit_sd_salt,
|
||||||
|
remove_sd_salt,
|
||||||
|
set_sd_salt,
|
||||||
|
stage_sd_salt,
|
||||||
|
)
|
||||||
|
from apps.common.storage import device, is_initialized
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from trezor.messages.SdProtect import SdProtect
|
||||||
|
|
||||||
|
|
||||||
|
async def sd_protect(ctx: wire.Context, msg: SdProtect) -> Success:
|
||||||
|
if not is_initialized():
|
||||||
|
raise wire.ProcessError("Device is not initialized")
|
||||||
|
|
||||||
|
if msg.operation == SdProtectOperationType.ENABLE:
|
||||||
|
return await sd_protect_enable(ctx, msg)
|
||||||
|
elif msg.operation == SdProtectOperationType.DISABLE:
|
||||||
|
return await sd_protect_disable(ctx, msg)
|
||||||
|
elif msg.operation == SdProtectOperationType.REFRESH:
|
||||||
|
return await sd_protect_refresh(ctx, msg)
|
||||||
|
else:
|
||||||
|
raise wire.ProcessError("Unknown operation")
|
||||||
|
|
||||||
|
|
||||||
|
async def sd_protect_enable(ctx: wire.Context, msg: SdProtect) -> Success:
|
||||||
|
salt_auth_key = device.get_sd_salt_auth_key()
|
||||||
|
if salt_auth_key is not None:
|
||||||
|
raise wire.ProcessError("SD card protection already enabled")
|
||||||
|
|
||||||
|
# Confirm that user wants to proceed with the operation.
|
||||||
|
await require_confirm_sd_protect(ctx, msg)
|
||||||
|
|
||||||
|
# Get the current PIN.
|
||||||
|
if config.has_pin():
|
||||||
|
pin = pin_to_int(await request_pin_ack(ctx, "Enter PIN", config.get_pin_rem()))
|
||||||
|
else:
|
||||||
|
pin = pin_to_int("")
|
||||||
|
|
||||||
|
# Check PIN and prepare salt file.
|
||||||
|
salt = random.bytes(SD_SALT_LEN_BYTES)
|
||||||
|
salt_auth_key = random.bytes(SD_SALT_AUTH_KEY_LEN_BYTES)
|
||||||
|
salt_tag = hmac.new(salt_auth_key, salt, sha256).digest()[
|
||||||
|
:SD_SALT_AUTH_TAG_LEN_BYTES
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
await set_sd_salt(ctx, salt, salt_tag)
|
||||||
|
except Exception:
|
||||||
|
raise wire.ProcessError("Failed to write to SD card")
|
||||||
|
|
||||||
|
if not config.change_pin(pin, pin, None, salt):
|
||||||
|
# Wrong PIN. Clean up the prepared salt file.
|
||||||
|
try:
|
||||||
|
await remove_sd_salt(ctx)
|
||||||
|
except Exception:
|
||||||
|
# The cleanup is not necessary for the correct functioning of
|
||||||
|
# SD-protection. If it fails for any reason, we suppress the
|
||||||
|
# exception, because primarily we need to raise wire.PinInvalid.
|
||||||
|
pass
|
||||||
|
raise wire.PinInvalid("PIN invalid")
|
||||||
|
|
||||||
|
device.set_sd_salt_auth_key(salt_auth_key)
|
||||||
|
|
||||||
|
return Success(message="SD card protection enabled")
|
||||||
|
|
||||||
|
|
||||||
|
async def sd_protect_disable(ctx: wire.Context, msg: SdProtect) -> Success:
|
||||||
|
if device.get_sd_salt_auth_key() is None:
|
||||||
|
raise wire.ProcessError("SD card protection not enabled")
|
||||||
|
|
||||||
|
# Confirm that user wants to proceed with the operation.
|
||||||
|
await require_confirm_sd_protect(ctx, msg)
|
||||||
|
|
||||||
|
# Get the current PIN and salt from the SD card.
|
||||||
|
pin, salt = await request_pin_and_sd_salt(ctx, "Enter PIN")
|
||||||
|
|
||||||
|
# Check PIN and remove salt.
|
||||||
|
if not config.change_pin(pin_to_int(pin), pin_to_int(pin), salt, None):
|
||||||
|
raise wire.PinInvalid("PIN invalid")
|
||||||
|
|
||||||
|
device.set_sd_salt_auth_key(None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Clean up.
|
||||||
|
await remove_sd_salt(ctx)
|
||||||
|
except Exception:
|
||||||
|
# The cleanup is not necessary for the correct functioning of
|
||||||
|
# SD-protection. If it fails for any reason, we suppress the exception,
|
||||||
|
# because overall SD-protection was successfully disabled.
|
||||||
|
pass
|
||||||
|
|
||||||
|
return Success(message="SD card protection disabled")
|
||||||
|
|
||||||
|
|
||||||
|
async def sd_protect_refresh(ctx: wire.Context, msg: SdProtect) -> Success:
|
||||||
|
if device.get_sd_salt_auth_key() is None:
|
||||||
|
raise wire.ProcessError("SD card protection not enabled")
|
||||||
|
|
||||||
|
# Confirm that user wants to proceed with the operation.
|
||||||
|
await require_confirm_sd_protect(ctx, msg)
|
||||||
|
|
||||||
|
# Get the current PIN and salt from the SD card.
|
||||||
|
pin, old_salt = await request_pin_and_sd_salt(ctx, "Enter PIN")
|
||||||
|
|
||||||
|
# Check PIN and change salt.
|
||||||
|
new_salt = random.bytes(SD_SALT_LEN_BYTES)
|
||||||
|
new_salt_auth_key = random.bytes(SD_SALT_AUTH_KEY_LEN_BYTES)
|
||||||
|
new_salt_tag = hmac.new(new_salt_auth_key, new_salt, sha256).digest()[
|
||||||
|
:SD_SALT_AUTH_TAG_LEN_BYTES
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
await stage_sd_salt(ctx, new_salt, new_salt_tag)
|
||||||
|
except Exception:
|
||||||
|
raise wire.ProcessError("Failed to write to SD card")
|
||||||
|
|
||||||
|
if not config.change_pin(pin_to_int(pin), pin_to_int(pin), old_salt, new_salt):
|
||||||
|
raise wire.PinInvalid("PIN invalid")
|
||||||
|
|
||||||
|
device.set_sd_salt_auth_key(new_salt_auth_key)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Clean up.
|
||||||
|
await commit_sd_salt(ctx)
|
||||||
|
except Exception:
|
||||||
|
# If the cleanup fails, then request_sd_salt() will bring the SD card
|
||||||
|
# into a consistent state. We suppress the exception, because overall
|
||||||
|
# SD-protection was successfully refreshed.
|
||||||
|
pass
|
||||||
|
|
||||||
|
return Success(message="SD card protection refreshed")
|
||||||
|
|
||||||
|
|
||||||
|
def require_confirm_sd_protect(ctx: wire.Context, msg: SdProtect) -> None:
|
||||||
|
if msg.operation == SdProtectOperationType.ENABLE:
|
||||||
|
text = Text("SD card protection", ui.ICON_CONFIG)
|
||||||
|
text.normal(
|
||||||
|
"Do you really want to", "secure your device with", "SD card protection?"
|
||||||
|
)
|
||||||
|
elif msg.operation == SdProtectOperationType.DISABLE:
|
||||||
|
text = Text("SD card protection", ui.ICON_CONFIG)
|
||||||
|
text.normal(
|
||||||
|
"Do you really want to", "remove SD card", "protection from your", "device?"
|
||||||
|
)
|
||||||
|
elif msg.operation == SdProtectOperationType.REFRESH:
|
||||||
|
text = Text("SD card protection", ui.ICON_CONFIG)
|
||||||
|
text.normal(
|
||||||
|
"Do you really want to",
|
||||||
|
"replace the current",
|
||||||
|
"SD card secret with a",
|
||||||
|
"newly generated one?",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise wire.ProcessError("Unknown operation")
|
||||||
|
|
||||||
|
return require_confirm(ctx, text)
|
@ -512,20 +512,15 @@ class KeepaliveCallback:
|
|||||||
send_cmd_sync(cmd_keepalive(self.cid, _KEEPALIVE_STATUS_PROCESSING), self.iface)
|
send_cmd_sync(cmd_keepalive(self.cid, _KEEPALIVE_STATUS_PROCESSING), self.iface)
|
||||||
|
|
||||||
|
|
||||||
async def check_pin(keepalive_callback: KeepaliveCallback) -> bool:
|
async def verify_user(keepalive_callback: KeepaliveCallback) -> bool:
|
||||||
from apps.common.request_pin import PinCancelled, request_pin
|
from apps.common.request_pin import verify_user_pin, PinCancelled, PinInvalid
|
||||||
import trezor.pin
|
import trezor.pin
|
||||||
|
|
||||||
try:
|
try:
|
||||||
trezor.pin.keepalive_callback = keepalive_callback
|
trezor.pin.keepalive_callback = keepalive_callback
|
||||||
if config.has_pin():
|
await verify_user_pin()
|
||||||
pin = await request_pin("Enter your PIN", config.get_pin_rem())
|
|
||||||
while config.unlock(trezor.pin.pin_to_int(pin)) is not True:
|
|
||||||
pin = await request_pin("Wrong PIN, enter again", config.get_pin_rem())
|
|
||||||
ret = True
|
ret = True
|
||||||
else:
|
except (PinCancelled, PinInvalid):
|
||||||
ret = config.unlock(trezor.pin.pin_to_int(""))
|
|
||||||
except PinCancelled:
|
|
||||||
ret = False
|
ret = False
|
||||||
finally:
|
finally:
|
||||||
trezor.pin.keepalive_callback = None
|
trezor.pin.keepalive_callback = None
|
||||||
@ -695,7 +690,7 @@ class Fido2ConfirmMakeCredential(Fido2State, ConfirmInfo):
|
|||||||
if not await confirm(content):
|
if not await confirm(content):
|
||||||
return False
|
return False
|
||||||
if self._user_verification:
|
if self._user_verification:
|
||||||
return await check_pin(KeepaliveCallback(self.cid, self.iface))
|
return await verify_user(KeepaliveCallback(self.cid, self.iface))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def on_confirm(self) -> None:
|
async def on_confirm(self) -> None:
|
||||||
@ -764,7 +759,7 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
|
|||||||
if await ConfirmPageable(self, content) is not CONFIRMED:
|
if await ConfirmPageable(self, content) is not CONFIRMED:
|
||||||
return False
|
return False
|
||||||
if self._user_verification:
|
if self._user_verification:
|
||||||
return await check_pin(KeepaliveCallback(self.cid, self.iface))
|
return await verify_user(KeepaliveCallback(self.cid, self.iface))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def on_confirm(self) -> None:
|
async def on_confirm(self) -> None:
|
||||||
|
@ -1,23 +1,40 @@
|
|||||||
from trezor import config, log, loop, res, ui
|
from trezor import config, io, log, loop, res, ui, utils
|
||||||
from trezor.pin import pin_to_int, show_pin_timeout
|
from trezor.pin import pin_to_int, show_pin_timeout
|
||||||
|
|
||||||
from apps.common import storage
|
from apps.common import storage
|
||||||
from apps.common.request_pin import request_pin
|
from apps.common.request_pin import request_pin
|
||||||
|
from apps.common.sd_salt import request_sd_salt
|
||||||
|
from apps.common.storage import device
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
async def bootscreen() -> None:
|
async def bootscreen() -> None:
|
||||||
ui.display.orientation(storage.device.get_rotation())
|
ui.display.orientation(storage.device.get_rotation())
|
||||||
|
salt_auth_key = device.get_sd_salt_auth_key()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
if salt_auth_key is not None or config.has_pin():
|
||||||
|
await lockscreen()
|
||||||
|
|
||||||
|
if salt_auth_key is not None:
|
||||||
|
salt = await request_sd_salt(
|
||||||
|
None, salt_auth_key
|
||||||
|
) # type: Optional[bytearray]
|
||||||
|
else:
|
||||||
|
salt = None
|
||||||
|
|
||||||
if not config.has_pin():
|
if not config.has_pin():
|
||||||
config.unlock(pin_to_int(""))
|
config.unlock(pin_to_int(""), salt)
|
||||||
storage.init_unlocked()
|
storage.init_unlocked()
|
||||||
return
|
return
|
||||||
await lockscreen()
|
|
||||||
label = "Enter your PIN"
|
label = "Enter your PIN"
|
||||||
while True:
|
while True:
|
||||||
pin = await request_pin(label, config.get_pin_rem())
|
pin = await request_pin(label, config.get_pin_rem())
|
||||||
if config.unlock(pin_to_int(pin)):
|
if config.unlock(pin_to_int(pin), salt):
|
||||||
storage.init_unlocked()
|
storage.init_unlocked()
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@ -55,6 +72,19 @@ async def lockscreen() -> None:
|
|||||||
await ui.click()
|
await ui.click()
|
||||||
|
|
||||||
|
|
||||||
|
if utils.EMULATOR:
|
||||||
|
# Ensure the emulated SD card is FAT32 formatted.
|
||||||
|
sd = io.SDCard()
|
||||||
|
fs = io.FatFS()
|
||||||
|
sd.power(True)
|
||||||
|
try:
|
||||||
|
fs.mount()
|
||||||
|
except OSError:
|
||||||
|
fs.mkfs()
|
||||||
|
else:
|
||||||
|
fs.unmount()
|
||||||
|
sd.power(False)
|
||||||
|
|
||||||
ui.display.backlight(ui.BACKLIGHT_NONE)
|
ui.display.backlight(ui.BACKLIGHT_NONE)
|
||||||
ui.backlight_fade(ui.BACKLIGHT_NORMAL)
|
ui.backlight_fade(ui.BACKLIGHT_NORMAL)
|
||||||
config.init(show_pin_timeout)
|
config.init(show_pin_timeout)
|
||||||
|
@ -33,6 +33,7 @@ WordRequest = 46
|
|||||||
WordAck = 47
|
WordAck = 47
|
||||||
GetFeatures = 55
|
GetFeatures = 55
|
||||||
SetU2FCounter = 63
|
SetU2FCounter = 63
|
||||||
|
SdProtect = 79
|
||||||
FirmwareErase = 6
|
FirmwareErase = 6
|
||||||
FirmwareUpload = 7
|
FirmwareUpload = 7
|
||||||
FirmwareRequest = 8
|
FirmwareRequest = 8
|
||||||
|
28
core/src/trezor/messages/SdProtect.py
Normal file
28
core/src/trezor/messages/SdProtect.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Automatically generated by pb2py
|
||||||
|
# fmt: off
|
||||||
|
import protobuf as p
|
||||||
|
|
||||||
|
if __debug__:
|
||||||
|
try:
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
from typing_extensions import Literal # noqa: F401
|
||||||
|
EnumTypeSdProtectOperationType = Literal[0, 1, 2]
|
||||||
|
except ImportError:
|
||||||
|
Dict, List, Optional = None, None, None # type: ignore
|
||||||
|
EnumTypeSdProtectOperationType = None # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class SdProtect(p.MessageType):
|
||||||
|
MESSAGE_WIRE_TYPE = 79
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
operation: EnumTypeSdProtectOperationType = None,
|
||||||
|
) -> None:
|
||||||
|
self.operation = operation
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_fields(cls) -> Dict:
|
||||||
|
return {
|
||||||
|
1: ('operation', p.EnumType("SdProtectOperationType", (0, 1, 2)), 0),
|
||||||
|
}
|
5
core/src/trezor/messages/SdProtectOperationType.py
Normal file
5
core/src/trezor/messages/SdProtectOperationType.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Automatically generated by pb2py
|
||||||
|
# fmt: off
|
||||||
|
DISABLE = 0
|
||||||
|
ENABLE = 1
|
||||||
|
REFRESH = 2
|
@ -12,7 +12,7 @@ from trezor.ui.button import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import Iterable
|
from typing import Iterable, Optional
|
||||||
|
|
||||||
|
|
||||||
def digit_area(i: int) -> ui.Area:
|
def digit_area(i: int) -> ui.Area:
|
||||||
@ -30,7 +30,7 @@ def generate_digits() -> Iterable[int]:
|
|||||||
|
|
||||||
|
|
||||||
class PinInput(ui.Component):
|
class PinInput(ui.Component):
|
||||||
def __init__(self, prompt: str, subprompt: str, pin: str) -> None:
|
def __init__(self, prompt: str, subprompt: Optional[str], pin: str) -> None:
|
||||||
self.prompt = prompt
|
self.prompt = prompt
|
||||||
self.subprompt = subprompt
|
self.subprompt = subprompt
|
||||||
self.pin = pin
|
self.pin = pin
|
||||||
@ -82,7 +82,11 @@ CANCELLED = object()
|
|||||||
|
|
||||||
class PinDialog(ui.Layout):
|
class PinDialog(ui.Layout):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, prompt: str, subprompt: str, allow_cancel: bool = True, maxlength: int = 9
|
self,
|
||||||
|
prompt: str,
|
||||||
|
subprompt: Optional[str],
|
||||||
|
allow_cancel: bool = True,
|
||||||
|
maxlength: int = 9,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.maxlength = maxlength
|
self.maxlength = maxlength
|
||||||
self.input = PinInput(prompt, subprompt, "")
|
self.input = PinInput(prompt, subprompt, "")
|
||||||
|
@ -25,7 +25,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
|||||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||||
upy_pid=$!
|
upy_pid=$!
|
||||||
cd -
|
cd -
|
||||||
sleep 1
|
sleep 30
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# run tests
|
# run tests
|
||||||
|
@ -27,7 +27,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
|||||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||||
upy_pid=$!
|
upy_pid=$!
|
||||||
cd -
|
cd -
|
||||||
sleep 1
|
sleep 30
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DOCKER_ID=""
|
DOCKER_ID=""
|
||||||
|
@ -25,7 +25,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
|||||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||||
upy_pid=$!
|
upy_pid=$!
|
||||||
cd -
|
cd -
|
||||||
sleep 1
|
sleep 30
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# run tests
|
# run tests
|
||||||
|
@ -27,7 +27,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
def test_wipe(self):
|
def test_wipe(self):
|
||||||
config.init()
|
config.init()
|
||||||
config.wipe()
|
config.wipe()
|
||||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||||
config.set(1, 1, b'hello')
|
config.set(1, 1, b'hello')
|
||||||
config.set(1, 2, b'world')
|
config.set(1, 2, b'world')
|
||||||
v0 = config.get(1, 1)
|
v0 = config.get(1, 1)
|
||||||
@ -44,7 +44,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
for _ in range(128):
|
for _ in range(128):
|
||||||
config.init()
|
config.init()
|
||||||
config.wipe()
|
config.wipe()
|
||||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||||
appid, key = random_entry()
|
appid, key = random_entry()
|
||||||
value = random.bytes(16)
|
value = random.bytes(16)
|
||||||
config.set(appid, key, value)
|
config.set(appid, key, value)
|
||||||
@ -58,7 +58,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
def test_public(self):
|
def test_public(self):
|
||||||
config.init()
|
config.init()
|
||||||
config.wipe()
|
config.wipe()
|
||||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||||
|
|
||||||
appid, key = random_entry()
|
appid, key = random_entry()
|
||||||
|
|
||||||
@ -84,25 +84,59 @@ class TestConfig(unittest.TestCase):
|
|||||||
def test_change_pin(self):
|
def test_change_pin(self):
|
||||||
config.init()
|
config.init()
|
||||||
config.wipe()
|
config.wipe()
|
||||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
config.set(PINAPP, PINKEY, b'value')
|
config.set(PINAPP, PINKEY, b'value')
|
||||||
self.assertEqual(config.change_pin(pin_to_int('000'), pin_to_int('666')), False)
|
self.assertEqual(config.change_pin(pin_to_int('000'), pin_to_int('666'), None, None), False)
|
||||||
self.assertEqual(config.change_pin(pin_to_int(''), pin_to_int('000')), True)
|
self.assertEqual(config.change_pin(pin_to_int(''), pin_to_int('000'), None, None), True)
|
||||||
self.assertEqual(config.get(PINAPP, PINKEY), None)
|
self.assertEqual(config.get(PINAPP, PINKEY), None)
|
||||||
config.set(1, 1, b'value')
|
config.set(1, 1, b'value')
|
||||||
config.init()
|
config.init()
|
||||||
self.assertEqual(config.unlock(pin_to_int('000')), True)
|
self.assertEqual(config.unlock(pin_to_int('000'), None), True)
|
||||||
config.change_pin(pin_to_int('000'), pin_to_int(''))
|
config.change_pin(pin_to_int('000'), pin_to_int(''), None, None)
|
||||||
config.init()
|
config.init()
|
||||||
self.assertEqual(config.unlock(pin_to_int('000')), False)
|
self.assertEqual(config.unlock(pin_to_int('000'), None), False)
|
||||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||||
|
self.assertEqual(config.get(1, 1), b'value')
|
||||||
|
|
||||||
|
def test_change_sd_salt(self):
|
||||||
|
salt1 = b"0123456789abcdef0123456789abcdef"
|
||||||
|
salt2 = b"0123456789ABCDEF0123456789ABCDEF"
|
||||||
|
|
||||||
|
# Enable PIN and SD salt.
|
||||||
|
config.init()
|
||||||
|
config.wipe()
|
||||||
|
self.assertTrue(config.unlock(pin_to_int(''), None))
|
||||||
|
config.set(1, 1, b'value')
|
||||||
|
self.assertFalse(config.change_pin(pin_to_int(''), pin_to_int(''), salt1, None))
|
||||||
|
self.assertTrue(config.change_pin(pin_to_int(''), pin_to_int('000'), None, salt1))
|
||||||
|
self.assertEqual(config.get(1, 1), b'value')
|
||||||
|
|
||||||
|
# Disable PIN and change SD salt.
|
||||||
|
config.init()
|
||||||
|
self.assertFalse(config.unlock(pin_to_int('000'), None))
|
||||||
|
self.assertIsNone(config.get(1, 1))
|
||||||
|
self.assertTrue(config.unlock(pin_to_int('000'), salt1))
|
||||||
|
self.assertTrue(config.change_pin(pin_to_int('000'), pin_to_int(''), salt1, salt2))
|
||||||
|
self.assertEqual(config.get(1, 1), b'value')
|
||||||
|
|
||||||
|
# Disable SD salt.
|
||||||
|
config.init()
|
||||||
|
self.assertFalse(config.unlock(pin_to_int('000'), salt2))
|
||||||
|
self.assertIsNone(config.get(1, 1))
|
||||||
|
self.assertTrue(config.unlock(pin_to_int(''), salt2))
|
||||||
|
self.assertTrue(config.change_pin(pin_to_int(''), pin_to_int(''), salt2, None))
|
||||||
|
self.assertEqual(config.get(1, 1), b'value')
|
||||||
|
|
||||||
|
# Check that PIN and SD salt are disabled.
|
||||||
|
config.init()
|
||||||
|
self.assertTrue(config.unlock(pin_to_int(''), None))
|
||||||
self.assertEqual(config.get(1, 1), b'value')
|
self.assertEqual(config.get(1, 1), b'value')
|
||||||
|
|
||||||
def test_set_get(self):
|
def test_set_get(self):
|
||||||
config.init()
|
config.init()
|
||||||
config.wipe()
|
config.wipe()
|
||||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||||
for _ in range(32):
|
for _ in range(32):
|
||||||
appid, key = random_entry()
|
appid, key = random_entry()
|
||||||
value = random.bytes(128)
|
value = random.bytes(128)
|
||||||
@ -113,7 +147,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
def test_compact(self):
|
def test_compact(self):
|
||||||
config.init()
|
config.init()
|
||||||
config.wipe()
|
config.wipe()
|
||||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||||
appid, key = 1, 1
|
appid, key = 1, 1
|
||||||
for _ in range(259):
|
for _ in range(259):
|
||||||
value = random.bytes(259)
|
value = random.bytes(259)
|
||||||
@ -124,7 +158,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
def test_get_default(self):
|
def test_get_default(self):
|
||||||
config.init()
|
config.init()
|
||||||
config.wipe()
|
config.wipe()
|
||||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||||
for _ in range(128):
|
for _ in range(128):
|
||||||
appid, key = random_entry()
|
appid, key = random_entry()
|
||||||
value = config.get(appid, key)
|
value = config.get(appid, key)
|
||||||
|
@ -316,9 +316,9 @@ static secbool config_upgrade_v10(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
storage_init(NULL, HW_ENTROPY_DATA, HW_ENTROPY_LEN);
|
storage_init(NULL, HW_ENTROPY_DATA, HW_ENTROPY_LEN);
|
||||||
storage_unlock(PIN_EMPTY);
|
storage_unlock(PIN_EMPTY, NULL);
|
||||||
if (config.has_pin) {
|
if (config.has_pin) {
|
||||||
storage_change_pin(PIN_EMPTY, pin_to_int(config.pin));
|
storage_change_pin(PIN_EMPTY, pin_to_int(config.pin), NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (pin_wait != 0) {
|
while (pin_wait != 0) {
|
||||||
@ -386,7 +386,7 @@ void config_init(void) {
|
|||||||
|
|
||||||
// Auto-unlock storage if no PIN is set.
|
// Auto-unlock storage if no PIN is set.
|
||||||
if (storage_is_unlocked() == secfalse && storage_has_pin() == secfalse) {
|
if (storage_is_unlocked() == secfalse && storage_has_pin() == secfalse) {
|
||||||
storage_unlock(PIN_EMPTY);
|
storage_unlock(PIN_EMPTY, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t len = 0;
|
uint16_t len = 0;
|
||||||
@ -759,7 +759,7 @@ bool config_containsMnemonic(const char *mnemonic) {
|
|||||||
*/
|
*/
|
||||||
bool config_unlock(const char *pin) {
|
bool config_unlock(const char *pin) {
|
||||||
char oldTiny = usbTiny(1);
|
char oldTiny = usbTiny(1);
|
||||||
secbool ret = storage_unlock(pin_to_int(pin));
|
secbool ret = storage_unlock(pin_to_int(pin), NULL);
|
||||||
usbTiny(oldTiny);
|
usbTiny(oldTiny);
|
||||||
return sectrue == ret;
|
return sectrue == ret;
|
||||||
}
|
}
|
||||||
@ -773,7 +773,8 @@ bool config_changePin(const char *old_pin, const char *new_pin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char oldTiny = usbTiny(1);
|
char oldTiny = usbTiny(1);
|
||||||
secbool ret = storage_change_pin(pin_to_int(old_pin), new_pin_int);
|
secbool ret =
|
||||||
|
storage_change_pin(pin_to_int(old_pin), new_pin_int, NULL, NULL);
|
||||||
usbTiny(oldTiny);
|
usbTiny(oldTiny);
|
||||||
|
|
||||||
#if DEBUG_LINK
|
#if DEBUG_LINK
|
||||||
@ -925,7 +926,7 @@ void config_wipe(void) {
|
|||||||
char oldTiny = usbTiny(1);
|
char oldTiny = usbTiny(1);
|
||||||
storage_wipe();
|
storage_wipe();
|
||||||
if (storage_is_unlocked() != sectrue) {
|
if (storage_is_unlocked() != sectrue) {
|
||||||
storage_unlock(PIN_EMPTY);
|
storage_unlock(PIN_EMPTY, NULL);
|
||||||
}
|
}
|
||||||
usbTiny(oldTiny);
|
usbTiny(oldTiny);
|
||||||
random_buffer((uint8_t *)config_uuid, sizeof(config_uuid));
|
random_buffer((uint8_t *)config_uuid, sizeof(config_uuid));
|
||||||
|
@ -2,7 +2,7 @@ ifneq ($(V),1)
|
|||||||
Q := @
|
Q := @
|
||||||
endif
|
endif
|
||||||
|
|
||||||
SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple Tezos WebAuthn
|
SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple SdProtect Tezos WebAuthn
|
||||||
|
|
||||||
ifeq ($(BITCOIN_ONLY), 1)
|
ifeq ($(BITCOIN_ONLY), 1)
|
||||||
SKIPPED_MESSAGES += Ethereum Lisk NEM Stellar
|
SKIPPED_MESSAGES += Ethereum Lisk NEM Stellar
|
||||||
|
@ -24,7 +24,7 @@ Use the following command to see all options:
|
|||||||
cardano-get-address Get Cardano address.
|
cardano-get-address Get Cardano address.
|
||||||
cardano-get-public-key Get Cardano public key.
|
cardano-get-public-key Get Cardano public key.
|
||||||
cardano-sign-tx Sign Cardano transaction.
|
cardano-sign-tx Sign Cardano transaction.
|
||||||
change-pin Change new PIN or remove existing.
|
change-pin Set, change or remove PIN.
|
||||||
clear-session Clear session (remove cached PIN, passphrase, etc.).
|
clear-session Clear session (remove cached PIN, passphrase, etc.).
|
||||||
cosi-commit Ask device to commit to CoSi signing.
|
cosi-commit Ask device to commit to CoSi signing.
|
||||||
cosi-sign Ask device to sign using CoSi.
|
cosi-sign Ask device to sign using CoSi.
|
||||||
@ -66,6 +66,7 @@ Use the following command to see all options:
|
|||||||
reset-device Perform device setup and generate new seed.
|
reset-device Perform device setup and generate new seed.
|
||||||
ripple-get-address Get Ripple address
|
ripple-get-address Get Ripple address
|
||||||
ripple-sign-tx Sign Ripple transaction
|
ripple-sign-tx Sign Ripple transaction
|
||||||
|
sd-protect Secure the device with SD card protection.
|
||||||
self-test Perform a self-test.
|
self-test Perform a self-test.
|
||||||
set-auto-lock-delay Set auto-lock delay (in seconds).
|
set-auto-lock-delay Set auto-lock delay (in seconds).
|
||||||
set-flags Set device flags.
|
set-flags Set device flags.
|
||||||
|
@ -123,6 +123,14 @@ CHOICE_RESET_DEVICE_TYPE = ChoiceType(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CHOICE_SD_PROTECT_OPERATION_TYPE = ChoiceType(
|
||||||
|
{
|
||||||
|
"enable": proto.SdProtectOperationType.ENABLE,
|
||||||
|
"disable": proto.SdProtectOperationType.DISABLE,
|
||||||
|
"refresh": proto.SdProtectOperationType.REFRESH,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UnderscoreAgnosticGroup(click.Group):
|
class UnderscoreAgnosticGroup(click.Group):
|
||||||
"""Command group that normalizes dashes and underscores.
|
"""Command group that normalizes dashes and underscores.
|
||||||
@ -261,13 +269,35 @@ def get_features(connect):
|
|||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@cli.command(help="Change new PIN or remove existing.")
|
@cli.command(help="Set, change or remove PIN.")
|
||||||
@click.option("-r", "--remove", is_flag=True)
|
@click.option("-r", "--remove", is_flag=True)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def change_pin(connect, remove):
|
def change_pin(connect, remove):
|
||||||
return device.change_pin(connect(), remove)
|
return device.change_pin(connect(), remove)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.argument("operation", type=CHOICE_SD_PROTECT_OPERATION_TYPE)
|
||||||
|
@click.pass_obj
|
||||||
|
def sd_protect(connect, operation):
|
||||||
|
"""Secure the device with SD card protection.
|
||||||
|
|
||||||
|
When SD card protection is enabled, a randomly generated secret is stored
|
||||||
|
on the SD card. During every PIN checking and unlocking operation this
|
||||||
|
secret is combined with the entered PIN value to decrypt data stored on
|
||||||
|
the device. The SD card will thus be needed every time you unlock the
|
||||||
|
device. The options are:
|
||||||
|
|
||||||
|
\b
|
||||||
|
enable - Generate SD card secret and use it to protect the PIN and storage.
|
||||||
|
disable - Remove SD card secret protection.
|
||||||
|
refresh - Replace the current SD card secret with a new one.
|
||||||
|
"""
|
||||||
|
if connect().features.model == "1":
|
||||||
|
raise click.BadUsage("Trezor One does not support SD card protection.")
|
||||||
|
return device.sd_protect(connect(), operation)
|
||||||
|
|
||||||
|
|
||||||
@cli.command(help="Enable passphrase.")
|
@cli.command(help="Enable passphrase.")
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def enable_passphrase(connect):
|
def enable_passphrase(connect):
|
||||||
|
@ -90,6 +90,12 @@ def change_pin(client, remove=False):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
@expect(proto.Success, field="message")
|
||||||
|
def sd_protect(client, operation):
|
||||||
|
ret = client.call(proto.SdProtect(operation=operation))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@expect(proto.Success, field="message")
|
@expect(proto.Success, field="message")
|
||||||
def set_u2f_counter(client, u2f_counter):
|
def set_u2f_counter(client, u2f_counter):
|
||||||
ret = client.call(proto.SetU2FCounter(u2f_counter=u2f_counter))
|
ret = client.call(proto.SetU2FCounter(u2f_counter=u2f_counter))
|
||||||
|
@ -31,6 +31,7 @@ WordRequest = 46
|
|||||||
WordAck = 47
|
WordAck = 47
|
||||||
GetFeatures = 55
|
GetFeatures = 55
|
||||||
SetU2FCounter = 63
|
SetU2FCounter = 63
|
||||||
|
SdProtect = 79
|
||||||
FirmwareErase = 6
|
FirmwareErase = 6
|
||||||
FirmwareUpload = 7
|
FirmwareUpload = 7
|
||||||
FirmwareRequest = 8
|
FirmwareRequest = 8
|
||||||
|
28
python/src/trezorlib/messages/SdProtect.py
Normal file
28
python/src/trezorlib/messages/SdProtect.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Automatically generated by pb2py
|
||||||
|
# fmt: off
|
||||||
|
from .. import protobuf as p
|
||||||
|
|
||||||
|
if __debug__:
|
||||||
|
try:
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
from typing_extensions import Literal # noqa: F401
|
||||||
|
EnumTypeSdProtectOperationType = Literal[0, 1, 2]
|
||||||
|
except ImportError:
|
||||||
|
Dict, List, Optional = None, None, None # type: ignore
|
||||||
|
EnumTypeSdProtectOperationType = None # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class SdProtect(p.MessageType):
|
||||||
|
MESSAGE_WIRE_TYPE = 79
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
operation: EnumTypeSdProtectOperationType = None,
|
||||||
|
) -> None:
|
||||||
|
self.operation = operation
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_fields(cls) -> Dict:
|
||||||
|
return {
|
||||||
|
1: ('operation', p.EnumType("SdProtectOperationType", (0, 1, 2)), 0),
|
||||||
|
}
|
5
python/src/trezorlib/messages/SdProtectOperationType.py
Normal file
5
python/src/trezorlib/messages/SdProtectOperationType.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Automatically generated by pb2py
|
||||||
|
# fmt: off
|
||||||
|
DISABLE = 0
|
||||||
|
ENABLE = 1
|
||||||
|
REFRESH = 2
|
@ -201,6 +201,7 @@ from .RippleGetAddress import RippleGetAddress
|
|||||||
from .RipplePayment import RipplePayment
|
from .RipplePayment import RipplePayment
|
||||||
from .RippleSignTx import RippleSignTx
|
from .RippleSignTx import RippleSignTx
|
||||||
from .RippleSignedTx import RippleSignedTx
|
from .RippleSignedTx import RippleSignedTx
|
||||||
|
from .SdProtect import SdProtect
|
||||||
from .SelfTest import SelfTest
|
from .SelfTest import SelfTest
|
||||||
from .SetU2FCounter import SetU2FCounter
|
from .SetU2FCounter import SetU2FCounter
|
||||||
from .SignIdentity import SignIdentity
|
from .SignIdentity import SignIdentity
|
||||||
@ -274,6 +275,7 @@ from . import PinMatrixRequestType
|
|||||||
from . import RecoveryDeviceType
|
from . import RecoveryDeviceType
|
||||||
from . import RequestType
|
from . import RequestType
|
||||||
from . import ResetDeviceBackupType
|
from . import ResetDeviceBackupType
|
||||||
|
from . import SdProtectOperationType
|
||||||
from . import TezosBallotType
|
from . import TezosBallotType
|
||||||
from . import TezosContractType
|
from . import TezosContractType
|
||||||
from . import WordRequestType
|
from . import WordRequestType
|
||||||
|
@ -331,15 +331,26 @@ static secbool auth_get(uint16_t key, const void **val, uint16_t *len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void derive_kek(uint32_t pin, const uint8_t *random_salt,
|
static void derive_kek(uint32_t pin, const uint8_t *random_salt,
|
||||||
|
const uint8_t *ext_salt,
|
||||||
uint8_t kek[SHA256_DIGEST_LENGTH],
|
uint8_t kek[SHA256_DIGEST_LENGTH],
|
||||||
uint8_t keiv[SHA256_DIGEST_LENGTH]) {
|
uint8_t keiv[SHA256_DIGEST_LENGTH]) {
|
||||||
#if BYTE_ORDER == BIG_ENDIAN
|
#if BYTE_ORDER == BIG_ENDIAN
|
||||||
REVERSE32(pin, pin);
|
REVERSE32(pin, pin);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint8_t salt[HARDWARE_SALT_SIZE + RANDOM_SALT_SIZE];
|
uint8_t salt[HARDWARE_SALT_SIZE + RANDOM_SALT_SIZE + EXTERNAL_SALT_SIZE];
|
||||||
memcpy(salt, hardware_salt, HARDWARE_SALT_SIZE);
|
size_t salt_len = 0;
|
||||||
memcpy(salt + HARDWARE_SALT_SIZE, random_salt, RANDOM_SALT_SIZE);
|
|
||||||
|
memcpy(salt + salt_len, hardware_salt, HARDWARE_SALT_SIZE);
|
||||||
|
salt_len += HARDWARE_SALT_SIZE;
|
||||||
|
|
||||||
|
memcpy(salt + salt_len, random_salt, RANDOM_SALT_SIZE);
|
||||||
|
salt_len += RANDOM_SALT_SIZE;
|
||||||
|
|
||||||
|
if (ext_salt != NULL) {
|
||||||
|
memcpy(salt + salt_len, ext_salt, EXTERNAL_SALT_SIZE);
|
||||||
|
salt_len += EXTERNAL_SALT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t progress = (ui_total - ui_rem) * 1000 / ui_total;
|
uint32_t progress = (ui_total - ui_rem) * 1000 / ui_total;
|
||||||
if (ui_callback && ui_message) {
|
if (ui_callback && ui_message) {
|
||||||
@ -348,7 +359,7 @@ static void derive_kek(uint32_t pin, const uint8_t *random_salt,
|
|||||||
|
|
||||||
PBKDF2_HMAC_SHA256_CTX ctx;
|
PBKDF2_HMAC_SHA256_CTX ctx;
|
||||||
pbkdf2_hmac_sha256_Init(&ctx, (const uint8_t *)&pin, sizeof(pin), salt,
|
pbkdf2_hmac_sha256_Init(&ctx, (const uint8_t *)&pin, sizeof(pin), salt,
|
||||||
sizeof(salt), 1);
|
salt_len, 1);
|
||||||
for (int i = 1; i <= 5; i++) {
|
for (int i = 1; i <= 5; i++) {
|
||||||
pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10);
|
pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10);
|
||||||
if (ui_callback && ui_message) {
|
if (ui_callback && ui_message) {
|
||||||
@ -360,7 +371,7 @@ static void derive_kek(uint32_t pin, const uint8_t *random_salt,
|
|||||||
pbkdf2_hmac_sha256_Final(&ctx, kek);
|
pbkdf2_hmac_sha256_Final(&ctx, kek);
|
||||||
|
|
||||||
pbkdf2_hmac_sha256_Init(&ctx, (const uint8_t *)&pin, sizeof(pin), salt,
|
pbkdf2_hmac_sha256_Init(&ctx, (const uint8_t *)&pin, sizeof(pin), salt,
|
||||||
sizeof(salt), 2);
|
salt_len, 2);
|
||||||
for (int i = 6; i <= 10; i++) {
|
for (int i = 6; i <= 10; i++) {
|
||||||
pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10);
|
pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10);
|
||||||
if (ui_callback && ui_message) {
|
if (ui_callback && ui_message) {
|
||||||
@ -377,17 +388,17 @@ static void derive_kek(uint32_t pin, const uint8_t *random_salt,
|
|||||||
memzero(&salt, sizeof(salt));
|
memzero(&salt, sizeof(salt));
|
||||||
}
|
}
|
||||||
|
|
||||||
static secbool set_pin(uint32_t pin) {
|
static secbool set_pin(uint32_t pin, const uint8_t *ext_salt) {
|
||||||
uint8_t buffer[RANDOM_SALT_SIZE + KEYS_SIZE + POLY1305_TAG_SIZE];
|
uint8_t buffer[RANDOM_SALT_SIZE + KEYS_SIZE + POLY1305_TAG_SIZE];
|
||||||
uint8_t *salt = buffer;
|
uint8_t *rand_salt = buffer;
|
||||||
uint8_t *ekeys = buffer + RANDOM_SALT_SIZE;
|
uint8_t *ekeys = buffer + RANDOM_SALT_SIZE;
|
||||||
uint8_t *pvc = buffer + RANDOM_SALT_SIZE + KEYS_SIZE;
|
uint8_t *pvc = buffer + RANDOM_SALT_SIZE + KEYS_SIZE;
|
||||||
|
|
||||||
uint8_t kek[SHA256_DIGEST_LENGTH];
|
uint8_t kek[SHA256_DIGEST_LENGTH];
|
||||||
uint8_t keiv[SHA256_DIGEST_LENGTH];
|
uint8_t keiv[SHA256_DIGEST_LENGTH];
|
||||||
chacha20poly1305_ctx ctx;
|
chacha20poly1305_ctx ctx;
|
||||||
random_buffer(salt, RANDOM_SALT_SIZE);
|
random_buffer(rand_salt, RANDOM_SALT_SIZE);
|
||||||
derive_kek(pin, salt, kek, keiv);
|
derive_kek(pin, rand_salt, ext_salt, kek, keiv);
|
||||||
rfc7539_init(&ctx, kek, keiv);
|
rfc7539_init(&ctx, kek, keiv);
|
||||||
memzero(kek, sizeof(kek));
|
memzero(kek, sizeof(kek));
|
||||||
memzero(keiv, sizeof(keiv));
|
memzero(keiv, sizeof(keiv));
|
||||||
@ -515,7 +526,7 @@ static void init_wiped_storage(void) {
|
|||||||
ui_total = DERIVE_SECS;
|
ui_total = DERIVE_SECS;
|
||||||
ui_rem = ui_total;
|
ui_rem = ui_total;
|
||||||
ui_message = PROCESSING_MSG;
|
ui_message = PROCESSING_MSG;
|
||||||
ensure(set_pin(PIN_EMPTY), "init_pin failed");
|
ensure(set_pin(PIN_EMPTY, NULL), "init_pin failed");
|
||||||
if (unlocked != sectrue) {
|
if (unlocked != sectrue) {
|
||||||
memzero(cached_keys, sizeof(cached_keys));
|
memzero(cached_keys, sizeof(cached_keys));
|
||||||
}
|
}
|
||||||
@ -784,7 +795,7 @@ static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) {
|
|||||||
return sectrue;
|
return sectrue;
|
||||||
}
|
}
|
||||||
|
|
||||||
static secbool unlock(uint32_t pin) {
|
static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
|
||||||
if (sectrue != initialized) {
|
if (sectrue != initialized) {
|
||||||
return secfalse;
|
return secfalse;
|
||||||
}
|
}
|
||||||
@ -827,10 +838,10 @@ static secbool unlock(uint32_t pin) {
|
|||||||
|
|
||||||
// Read the random salt from EDEK_PVC_KEY and use it to derive the KEK and
|
// Read the random salt from EDEK_PVC_KEY and use it to derive the KEK and
|
||||||
// KEIV from the PIN.
|
// KEIV from the PIN.
|
||||||
const void *salt = NULL;
|
const void *rand_salt = NULL;
|
||||||
uint16_t len = 0;
|
uint16_t len = 0;
|
||||||
if (sectrue != initialized ||
|
if (sectrue != initialized ||
|
||||||
sectrue != norcow_get(EDEK_PVC_KEY, &salt, &len) ||
|
sectrue != norcow_get(EDEK_PVC_KEY, &rand_salt, &len) ||
|
||||||
len != RANDOM_SALT_SIZE + KEYS_SIZE + PVC_SIZE) {
|
len != RANDOM_SALT_SIZE + KEYS_SIZE + PVC_SIZE) {
|
||||||
memzero(&pin, sizeof(pin));
|
memzero(&pin, sizeof(pin));
|
||||||
handle_fault("no EDEK");
|
handle_fault("no EDEK");
|
||||||
@ -838,7 +849,7 @@ static secbool unlock(uint32_t pin) {
|
|||||||
}
|
}
|
||||||
uint8_t kek[SHA256_DIGEST_LENGTH];
|
uint8_t kek[SHA256_DIGEST_LENGTH];
|
||||||
uint8_t keiv[SHA256_DIGEST_LENGTH];
|
uint8_t keiv[SHA256_DIGEST_LENGTH];
|
||||||
derive_kek(pin, (const uint8_t *)salt, kek, keiv);
|
derive_kek(pin, (const uint8_t *)rand_salt, ext_salt, kek, keiv);
|
||||||
memzero(&pin, sizeof(pin));
|
memzero(&pin, sizeof(pin));
|
||||||
|
|
||||||
// First, we increase PIN fail counter in storage, even before checking the
|
// First, we increase PIN fail counter in storage, even before checking the
|
||||||
@ -875,7 +886,7 @@ static secbool unlock(uint32_t pin) {
|
|||||||
return pin_fails_reset();
|
return pin_fails_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
secbool storage_unlock(uint32_t pin) {
|
secbool storage_unlock(uint32_t pin, const uint8_t *ext_salt) {
|
||||||
ui_total = DERIVE_SECS;
|
ui_total = DERIVE_SECS;
|
||||||
ui_rem = ui_total;
|
ui_rem = ui_total;
|
||||||
if (pin == PIN_EMPTY) {
|
if (pin == PIN_EMPTY) {
|
||||||
@ -887,7 +898,7 @@ secbool storage_unlock(uint32_t pin) {
|
|||||||
} else {
|
} else {
|
||||||
ui_message = VERIFYING_PIN_MSG;
|
ui_message = VERIFYING_PIN_MSG;
|
||||||
}
|
}
|
||||||
return unlock(pin);
|
return unlock(pin, ext_salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1152,7 +1163,9 @@ uint32_t storage_get_pin_rem(void) {
|
|||||||
return PIN_MAX_TRIES - ctr;
|
return PIN_MAX_TRIES - ctr;
|
||||||
}
|
}
|
||||||
|
|
||||||
secbool storage_change_pin(uint32_t oldpin, 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) {
|
||||||
if (sectrue != initialized) {
|
if (sectrue != initialized) {
|
||||||
return secfalse;
|
return secfalse;
|
||||||
}
|
}
|
||||||
@ -1162,10 +1175,10 @@ secbool storage_change_pin(uint32_t oldpin, uint32_t newpin) {
|
|||||||
ui_message = (oldpin != PIN_EMPTY && newpin == PIN_EMPTY) ? VERIFYING_PIN_MSG
|
ui_message = (oldpin != PIN_EMPTY && newpin == PIN_EMPTY) ? VERIFYING_PIN_MSG
|
||||||
: PROCESSING_MSG;
|
: PROCESSING_MSG;
|
||||||
|
|
||||||
if (sectrue != unlock(oldpin)) {
|
if (sectrue != unlock(oldpin, old_ext_salt)) {
|
||||||
return secfalse;
|
return secfalse;
|
||||||
}
|
}
|
||||||
secbool ret = set_pin(newpin);
|
secbool ret = set_pin(newpin, new_ext_salt);
|
||||||
memzero(&oldpin, sizeof(oldpin));
|
memzero(&oldpin, sizeof(oldpin));
|
||||||
memzero(&newpin, sizeof(newpin));
|
memzero(&newpin, sizeof(newpin));
|
||||||
return ret;
|
return ret;
|
||||||
@ -1268,9 +1281,9 @@ static secbool storage_upgrade(void) {
|
|||||||
ui_rem = ui_total;
|
ui_rem = ui_total;
|
||||||
ui_message = PROCESSING_MSG;
|
ui_message = PROCESSING_MSG;
|
||||||
if (sectrue == norcow_get(V0_PIN_KEY, &val, &len)) {
|
if (sectrue == norcow_get(V0_PIN_KEY, &val, &len)) {
|
||||||
set_pin(*(const uint32_t *)val);
|
set_pin(*(const uint32_t *)val, NULL);
|
||||||
} else {
|
} else {
|
||||||
set_pin(PIN_EMPTY);
|
set_pin(PIN_EMPTY, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert PIN failure counter.
|
// Convert PIN failure counter.
|
||||||
|
@ -24,6 +24,9 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "secbool.h"
|
#include "secbool.h"
|
||||||
|
|
||||||
|
// The length of the external salt in bytes.
|
||||||
|
#define EXTERNAL_SALT_SIZE 32
|
||||||
|
|
||||||
typedef secbool (*PIN_UI_WAIT_CALLBACK)(uint32_t wait, uint32_t progress,
|
typedef secbool (*PIN_UI_WAIT_CALLBACK)(uint32_t wait, uint32_t progress,
|
||||||
const char *message);
|
const char *message);
|
||||||
|
|
||||||
@ -32,11 +35,13 @@ void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt,
|
|||||||
void storage_wipe(void);
|
void storage_wipe(void);
|
||||||
secbool storage_is_unlocked(void);
|
secbool storage_is_unlocked(void);
|
||||||
void storage_lock(void);
|
void storage_lock(void);
|
||||||
secbool storage_unlock(const uint32_t pin);
|
secbool storage_unlock(const uint32_t pin, const uint8_t *ext_salt);
|
||||||
secbool storage_has_pin(void);
|
secbool storage_has_pin(void);
|
||||||
secbool storage_pin_fails_increase(void);
|
secbool storage_pin_fails_increase(void);
|
||||||
uint32_t storage_get_pin_rem(void);
|
uint32_t storage_get_pin_rem(void);
|
||||||
secbool storage_change_pin(const uint32_t oldpin, const uint32_t newpin);
|
secbool storage_change_pin(const uint32_t oldpin, const uint32_t newpin,
|
||||||
|
const uint8_t *old_ext_salt,
|
||||||
|
const uint8_t *new_ext_salt);
|
||||||
secbool storage_get(const uint16_t key, void *val, const uint16_t max_len,
|
secbool storage_get(const uint16_t key, void *val, const uint16_t max_len,
|
||||||
uint16_t *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, uint16_t len);
|
||||||
|
Binary file not shown.
@ -1,6 +1,7 @@
|
|||||||
import ctypes as c
|
import ctypes as c
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
EXTERNAL_SALT_LEN = 32
|
||||||
sectrue = -1431655766 # 0xAAAAAAAAA
|
sectrue = -1431655766 # 0xAAAAAAAAA
|
||||||
fname = os.path.join(os.path.dirname(__file__), "libtrezor-storage.so")
|
fname = os.path.join(os.path.dirname(__file__), "libtrezor-storage.so")
|
||||||
|
|
||||||
@ -20,8 +21,10 @@ class Storage:
|
|||||||
def wipe(self) -> None:
|
def wipe(self) -> None:
|
||||||
self.lib.storage_wipe()
|
self.lib.storage_wipe()
|
||||||
|
|
||||||
def unlock(self, pin: int) -> bool:
|
def unlock(self, pin: int, ext_salt: bytes = None) -> bool:
|
||||||
return sectrue == self.lib.storage_unlock(c.c_uint32(pin))
|
if ext_salt is not None and len(ext_salt) != EXTERNAL_SALT_LEN:
|
||||||
|
raise ValueError
|
||||||
|
return sectrue == self.lib.storage_unlock(c.c_uint32(pin), ext_salt)
|
||||||
|
|
||||||
def lock(self) -> None:
|
def lock(self) -> None:
|
||||||
self.lib.storage_lock()
|
self.lib.storage_lock()
|
||||||
@ -32,9 +35,19 @@ class Storage:
|
|||||||
def get_pin_rem(self) -> int:
|
def get_pin_rem(self) -> int:
|
||||||
return self.lib.storage_get_pin_rem()
|
return self.lib.storage_get_pin_rem()
|
||||||
|
|
||||||
def change_pin(self, oldpin: int, newpin: int) -> bool:
|
def change_pin(
|
||||||
|
self,
|
||||||
|
oldpin: int,
|
||||||
|
newpin: int,
|
||||||
|
old_ext_salt: bytes = None,
|
||||||
|
new_ext_salt: bytes = None,
|
||||||
|
) -> bool:
|
||||||
|
if old_ext_salt is not None and len(old_ext_salt) != EXTERNAL_SALT_LEN:
|
||||||
|
raise ValueError
|
||||||
|
if new_ext_salt is not None and len(new_ext_salt) != EXTERNAL_SALT_LEN:
|
||||||
|
raise ValueError
|
||||||
return sectrue == self.lib.storage_change_pin(
|
return sectrue == self.lib.storage_change_pin(
|
||||||
c.c_uint32(oldpin), c.c_uint32(newpin)
|
c.c_uint32(oldpin), c.c_uint32(newpin), old_ext_salt, new_ext_salt
|
||||||
)
|
)
|
||||||
|
|
||||||
def get(self, key: int) -> bytes:
|
def get(self, key: int) -> bytes:
|
||||||
|
Binary file not shown.
62
tests/device_tests/test_msg_sd_protect.py
Normal file
62
tests/device_tests/test_msg_sd_protect.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# 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 debuglink, device, messages as proto
|
||||||
|
from trezorlib.exceptions import TrezorFailure
|
||||||
|
|
||||||
|
from ..common import MNEMONIC12
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_t1
|
||||||
|
class TestMsgSdProtect:
|
||||||
|
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
||||||
|
def test_sd_protect(self, client):
|
||||||
|
|
||||||
|
# Disabling SD protection should fail
|
||||||
|
with pytest.raises(TrezorFailure):
|
||||||
|
device.sd_protect(client, proto.SdProtectOperationType.DISABLE)
|
||||||
|
|
||||||
|
# Enable SD protection
|
||||||
|
device.sd_protect(client, proto.SdProtectOperationType.ENABLE)
|
||||||
|
|
||||||
|
# Enabling SD protection should fail
|
||||||
|
with pytest.raises(TrezorFailure):
|
||||||
|
device.sd_protect(client, proto.SdProtectOperationType.ENABLE)
|
||||||
|
|
||||||
|
# Wipe
|
||||||
|
device.wipe(client)
|
||||||
|
debuglink.load_device_by_mnemonic(
|
||||||
|
client,
|
||||||
|
mnemonic=MNEMONIC12,
|
||||||
|
pin="",
|
||||||
|
passphrase_protection=False,
|
||||||
|
label="test",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enable SD protection
|
||||||
|
device.sd_protect(client, proto.SdProtectOperationType.ENABLE)
|
||||||
|
|
||||||
|
# Refresh SD protection
|
||||||
|
device.sd_protect(client, proto.SdProtectOperationType.REFRESH)
|
||||||
|
|
||||||
|
# Disable SD protection
|
||||||
|
device.sd_protect(client, proto.SdProtectOperationType.DISABLE)
|
||||||
|
|
||||||
|
# Refreshing SD protection should fail
|
||||||
|
with pytest.raises(TrezorFailure):
|
||||||
|
device.sd_protect(client, proto.SdProtectOperationType.REFRESH)
|
Loading…
Reference in New Issue
Block a user