mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-23 14:58:09 +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?
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @start
|
||||
|
@ -61,6 +61,7 @@ enum MessageType {
|
||||
MessageType_WordAck = 47 [(wire_in) = true];
|
||||
MessageType_GetFeatures = 55 [(wire_in) = true];
|
||||
MessageType_SetU2FCounter = 63 [(wire_in) = true];
|
||||
MessageType_SdProtect = 79 [(wire_in) = true];
|
||||
|
||||
// Bootloader
|
||||
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,
|
||||
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
|
||||
/// success, False on failure.
|
||||
/// Attempts to unlock the storage with the given PIN and external salt.
|
||||
/// 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);
|
||||
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_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);
|
||||
|
||||
/// 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) {
|
||||
return mod_trezorconfig_unlock(pin);
|
||||
STATIC mp_obj_t mod_trezorconfig_check_pin(mp_obj_t pin, mp_obj_t ext_salt) {
|
||||
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);
|
||||
|
||||
/// 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,
|
||||
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) {
|
||||
uint32_t pin_i = trezor_obj_get_uint(pin);
|
||||
uint32_t newpin_i = trezor_obj_get_uint(newpin);
|
||||
if (sectrue != storage_change_pin(pin_i, newpin_i)) {
|
||||
STATIC mp_obj_t mod_trezorconfig_change_pin(size_t n_args,
|
||||
const mp_obj_t *args) {
|
||||
uint32_t oldpin = trezor_obj_get_uint(args[0]);
|
||||
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_true;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorconfig_change_pin_obj,
|
||||
mod_trezorconfig_change_pin);
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorconfig_change_pin_obj, 4,
|
||||
4, mod_trezorconfig_change_pin);
|
||||
|
||||
/// def get(app: int, key: int, public: bool = False) -> Optional[bytes]:
|
||||
/// """
|
||||
|
@ -12,17 +12,18 @@ def init(
|
||||
|
||||
|
||||
# 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
|
||||
success, False on failure.
|
||||
Attempts to unlock the storage with the given PIN and external salt.
|
||||
Returns True on success, False on failure.
|
||||
"""
|
||||
|
||||
|
||||
# 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
|
||||
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.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__:
|
||||
from apps.debug import input_signal
|
||||
@ -9,6 +21,10 @@ class PinCancelled(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PinInvalid(Exception):
|
||||
pass
|
||||
|
||||
|
||||
async def request_pin(
|
||||
prompt: str = "Enter your PIN",
|
||||
attempts_remaining: int = None,
|
||||
@ -31,3 +47,68 @@ async def request_pin(
|
||||
if result is CANCELLED:
|
||||
raise PinCancelled
|
||||
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)
|
||||
|
||||
|
||||
def delete(app: int, key: int) -> None:
|
||||
config.delete(app, key)
|
||||
def delete(app: int, key: int, public: bool = False) -> None:
|
||||
config.delete(app, key, public)
|
||||
|
||||
|
||||
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 apps.common.sd_salt import SD_SALT_AUTH_KEY_LEN_BYTES
|
||||
from apps.common.storage import common
|
||||
|
||||
if False:
|
||||
@ -31,6 +32,7 @@ _MNEMONIC_TYPE = const(0x0E) # int
|
||||
_ROTATION = const(0x0F) # int
|
||||
_SLIP39_IDENTIFIER = const(0x10) # bool
|
||||
_SLIP39_ITERATION_EXPONENT = const(0x11) # int
|
||||
_SD_SALT_AUTH_KEY = const(0x12) # bytes
|
||||
# fmt: on
|
||||
|
||||
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.
|
||||
"""
|
||||
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.ChangePin, __name__, "change_pin")
|
||||
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.messages import ButtonRequestType
|
||||
from trezor.messages.ButtonAck import ButtonAck
|
||||
from trezor.messages.ButtonRequest import ButtonRequest
|
||||
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.request_pin import PinCancelled, request_pin
|
||||
from apps.common.request_pin import request_pin_and_sd_salt, request_pin_confirm
|
||||
|
||||
if False:
|
||||
from typing import Any
|
||||
from trezor.messages.ChangePin import ChangePin
|
||||
|
||||
|
||||
async def change_pin(ctx: wire.Context, msg: ChangePin) -> Success:
|
||||
|
||||
# confirm that user wants to change the pin
|
||||
await require_confirm_change_pin(ctx, msg)
|
||||
|
||||
# get current pin, return failure if invalid
|
||||
if config.has_pin():
|
||||
curpin = await request_pin_ack(ctx, "Enter old PIN", config.get_pin_rem())
|
||||
# if removing, defer check to change_pin()
|
||||
if not msg.remove:
|
||||
if not config.check_pin(pin_to_int(curpin)):
|
||||
# get old pin
|
||||
curpin, salt = await request_pin_and_sd_salt(ctx, "Enter old PIN")
|
||||
|
||||
# if changing pin, pre-check the entered pin before getting new pin
|
||||
if curpin and not msg.remove:
|
||||
if not config.check_pin(pin_to_int(curpin), salt):
|
||||
raise wire.PinInvalid("PIN invalid")
|
||||
else:
|
||||
curpin = ""
|
||||
|
||||
# get new pin
|
||||
if not msg.remove:
|
||||
@ -37,7 +29,7 @@ async def change_pin(ctx: wire.Context, msg: ChangePin) -> Success:
|
||||
newpin = ""
|
||||
|
||||
# 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")
|
||||
|
||||
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.bold("enable PIN protection?")
|
||||
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
|
||||
)
|
||||
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")
|
||||
|
@ -6,7 +6,7 @@ from trezor.ui.text import Text
|
||||
|
||||
from apps.common import storage
|
||||
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
|
||||
|
||||
if False:
|
||||
@ -24,13 +24,10 @@ async def recovery_device(ctx: wire.Context, msg: RecoveryDevice) -> Success:
|
||||
|
||||
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 config.has_pin():
|
||||
curpin = await request_pin_ack(ctx, "Enter PIN", config.get_pin_rem())
|
||||
else:
|
||||
curpin = ""
|
||||
if not config.check_pin(pin_to_int(curpin)):
|
||||
curpin, salt = await request_pin_and_sd_salt(ctx, "Enter PIN")
|
||||
if not config.check_pin(pin_to_int(curpin), salt):
|
||||
raise wire.PinInvalid("PIN invalid")
|
||||
|
||||
# set up pin if requested
|
||||
@ -38,7 +35,7 @@ async def recovery_device(ctx: wire.Context, msg: RecoveryDevice) -> Success:
|
||||
if msg.dry_run:
|
||||
raise wire.ProcessError("Can't setup PIN during dry_run recovery.")
|
||||
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:
|
||||
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.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
|
||||
|
||||
if __debug__:
|
||||
@ -71,7 +71,7 @@ async def reset_device(ctx: wire.Context, msg: ResetDevice) -> Success:
|
||||
await backup_bip39_wallet(ctx, secret)
|
||||
|
||||
# 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")
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
async def check_pin(keepalive_callback: KeepaliveCallback) -> bool:
|
||||
from apps.common.request_pin import PinCancelled, request_pin
|
||||
async def verify_user(keepalive_callback: KeepaliveCallback) -> bool:
|
||||
from apps.common.request_pin import verify_user_pin, PinCancelled, PinInvalid
|
||||
import trezor.pin
|
||||
|
||||
try:
|
||||
trezor.pin.keepalive_callback = keepalive_callback
|
||||
if config.has_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())
|
||||
await verify_user_pin()
|
||||
ret = True
|
||||
else:
|
||||
ret = config.unlock(trezor.pin.pin_to_int(""))
|
||||
except PinCancelled:
|
||||
except (PinCancelled, PinInvalid):
|
||||
ret = False
|
||||
finally:
|
||||
trezor.pin.keepalive_callback = None
|
||||
@ -695,7 +690,7 @@ class Fido2ConfirmMakeCredential(Fido2State, ConfirmInfo):
|
||||
if not await confirm(content):
|
||||
return False
|
||||
if self._user_verification:
|
||||
return await check_pin(KeepaliveCallback(self.cid, self.iface))
|
||||
return await verify_user(KeepaliveCallback(self.cid, self.iface))
|
||||
return True
|
||||
|
||||
async def on_confirm(self) -> None:
|
||||
@ -764,7 +759,7 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
|
||||
if await ConfirmPageable(self, content) is not CONFIRMED:
|
||||
return False
|
||||
if self._user_verification:
|
||||
return await check_pin(KeepaliveCallback(self.cid, self.iface))
|
||||
return await verify_user(KeepaliveCallback(self.cid, self.iface))
|
||||
return True
|
||||
|
||||
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 apps.common import storage
|
||||
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:
|
||||
ui.display.orientation(storage.device.get_rotation())
|
||||
salt_auth_key = device.get_sd_salt_auth_key()
|
||||
|
||||
while True:
|
||||
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():
|
||||
config.unlock(pin_to_int(""))
|
||||
config.unlock(pin_to_int(""), salt)
|
||||
storage.init_unlocked()
|
||||
return
|
||||
await lockscreen()
|
||||
|
||||
label = "Enter your PIN"
|
||||
while True:
|
||||
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()
|
||||
return
|
||||
else:
|
||||
@ -55,6 +72,19 @@ async def lockscreen() -> None:
|
||||
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.backlight_fade(ui.BACKLIGHT_NORMAL)
|
||||
config.init(show_pin_timeout)
|
||||
|
@ -33,6 +33,7 @@ WordRequest = 46
|
||||
WordAck = 47
|
||||
GetFeatures = 55
|
||||
SetU2FCounter = 63
|
||||
SdProtect = 79
|
||||
FirmwareErase = 6
|
||||
FirmwareUpload = 7
|
||||
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:
|
||||
from typing import Iterable
|
||||
from typing import Iterable, Optional
|
||||
|
||||
|
||||
def digit_area(i: int) -> ui.Area:
|
||||
@ -30,7 +30,7 @@ def generate_digits() -> Iterable[int]:
|
||||
|
||||
|
||||
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.subprompt = subprompt
|
||||
self.pin = pin
|
||||
@ -82,7 +82,11 @@ CANCELLED = object()
|
||||
|
||||
class PinDialog(ui.Layout):
|
||||
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:
|
||||
self.maxlength = maxlength
|
||||
self.input = PinInput(prompt, subprompt, "")
|
||||
|
@ -25,7 +25,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||
upy_pid=$!
|
||||
cd -
|
||||
sleep 1
|
||||
sleep 30
|
||||
fi
|
||||
|
||||
# run tests
|
||||
|
@ -27,7 +27,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||
upy_pid=$!
|
||||
cd -
|
||||
sleep 1
|
||||
sleep 30
|
||||
fi
|
||||
|
||||
DOCKER_ID=""
|
||||
|
@ -25,7 +25,7 @@ if [[ $RUN_TEST_EMU > 0 ]]; then
|
||||
$MICROPYTHON $ARGS "${MAIN}" &> "${TREZOR_LOGFILE}" &
|
||||
upy_pid=$!
|
||||
cd -
|
||||
sleep 1
|
||||
sleep 30
|
||||
fi
|
||||
|
||||
# run tests
|
||||
|
@ -27,7 +27,7 @@ class TestConfig(unittest.TestCase):
|
||||
def test_wipe(self):
|
||||
config.init()
|
||||
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, 2, b'world')
|
||||
v0 = config.get(1, 1)
|
||||
@ -44,7 +44,7 @@ class TestConfig(unittest.TestCase):
|
||||
for _ in range(128):
|
||||
config.init()
|
||||
config.wipe()
|
||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
||||
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||
appid, key = random_entry()
|
||||
value = random.bytes(16)
|
||||
config.set(appid, key, value)
|
||||
@ -58,7 +58,7 @@ class TestConfig(unittest.TestCase):
|
||||
def test_public(self):
|
||||
config.init()
|
||||
config.wipe()
|
||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
||||
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||
|
||||
appid, key = random_entry()
|
||||
|
||||
@ -84,25 +84,59 @@ class TestConfig(unittest.TestCase):
|
||||
def test_change_pin(self):
|
||||
config.init()
|
||||
config.wipe()
|
||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
||||
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||
with self.assertRaises(RuntimeError):
|
||||
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(''), pin_to_int('000')), True)
|
||||
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'), None, None), True)
|
||||
self.assertEqual(config.get(PINAPP, PINKEY), None)
|
||||
config.set(1, 1, b'value')
|
||||
config.init()
|
||||
self.assertEqual(config.unlock(pin_to_int('000')), True)
|
||||
config.change_pin(pin_to_int('000'), pin_to_int(''))
|
||||
self.assertEqual(config.unlock(pin_to_int('000'), None), True)
|
||||
config.change_pin(pin_to_int('000'), pin_to_int(''), None, None)
|
||||
config.init()
|
||||
self.assertEqual(config.unlock(pin_to_int('000')), False)
|
||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
||||
self.assertEqual(config.unlock(pin_to_int('000'), None), False)
|
||||
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')
|
||||
|
||||
def test_set_get(self):
|
||||
config.init()
|
||||
config.wipe()
|
||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
||||
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||
for _ in range(32):
|
||||
appid, key = random_entry()
|
||||
value = random.bytes(128)
|
||||
@ -113,7 +147,7 @@ class TestConfig(unittest.TestCase):
|
||||
def test_compact(self):
|
||||
config.init()
|
||||
config.wipe()
|
||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
||||
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||
appid, key = 1, 1
|
||||
for _ in range(259):
|
||||
value = random.bytes(259)
|
||||
@ -124,7 +158,7 @@ class TestConfig(unittest.TestCase):
|
||||
def test_get_default(self):
|
||||
config.init()
|
||||
config.wipe()
|
||||
self.assertEqual(config.unlock(pin_to_int('')), True)
|
||||
self.assertEqual(config.unlock(pin_to_int(''), None), True)
|
||||
for _ in range(128):
|
||||
appid, key = random_entry()
|
||||
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_unlock(PIN_EMPTY);
|
||||
storage_unlock(PIN_EMPTY, NULL);
|
||||
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) {
|
||||
@ -386,7 +386,7 @@ void config_init(void) {
|
||||
|
||||
// Auto-unlock storage if no PIN is set.
|
||||
if (storage_is_unlocked() == secfalse && storage_has_pin() == secfalse) {
|
||||
storage_unlock(PIN_EMPTY);
|
||||
storage_unlock(PIN_EMPTY, NULL);
|
||||
}
|
||||
|
||||
uint16_t len = 0;
|
||||
@ -759,7 +759,7 @@ bool config_containsMnemonic(const char *mnemonic) {
|
||||
*/
|
||||
bool config_unlock(const char *pin) {
|
||||
char oldTiny = usbTiny(1);
|
||||
secbool ret = storage_unlock(pin_to_int(pin));
|
||||
secbool ret = storage_unlock(pin_to_int(pin), NULL);
|
||||
usbTiny(oldTiny);
|
||||
return sectrue == ret;
|
||||
}
|
||||
@ -773,7 +773,8 @@ bool config_changePin(const char *old_pin, const char *new_pin) {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
#if DEBUG_LINK
|
||||
@ -925,7 +926,7 @@ void config_wipe(void) {
|
||||
char oldTiny = usbTiny(1);
|
||||
storage_wipe();
|
||||
if (storage_is_unlocked() != sectrue) {
|
||||
storage_unlock(PIN_EMPTY);
|
||||
storage_unlock(PIN_EMPTY, NULL);
|
||||
}
|
||||
usbTiny(oldTiny);
|
||||
random_buffer((uint8_t *)config_uuid, sizeof(config_uuid));
|
||||
|
@ -2,7 +2,7 @@ ifneq ($(V),1)
|
||||
Q := @
|
||||
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)
|
||||
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-public-key Get Cardano public key.
|
||||
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.).
|
||||
cosi-commit Ask device to commit to CoSi signing.
|
||||
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.
|
||||
ripple-get-address Get Ripple address
|
||||
ripple-sign-tx Sign Ripple transaction
|
||||
sd-protect Secure the device with SD card protection.
|
||||
self-test Perform a self-test.
|
||||
set-auto-lock-delay Set auto-lock delay (in seconds).
|
||||
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):
|
||||
"""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.pass_obj
|
||||
def 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.")
|
||||
@click.pass_obj
|
||||
def enable_passphrase(connect):
|
||||
|
@ -90,6 +90,12 @@ def change_pin(client, remove=False):
|
||||
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")
|
||||
def set_u2f_counter(client, u2f_counter):
|
||||
ret = client.call(proto.SetU2FCounter(u2f_counter=u2f_counter))
|
||||
|
@ -31,6 +31,7 @@ WordRequest = 46
|
||||
WordAck = 47
|
||||
GetFeatures = 55
|
||||
SetU2FCounter = 63
|
||||
SdProtect = 79
|
||||
FirmwareErase = 6
|
||||
FirmwareUpload = 7
|
||||
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 .RippleSignTx import RippleSignTx
|
||||
from .RippleSignedTx import RippleSignedTx
|
||||
from .SdProtect import SdProtect
|
||||
from .SelfTest import SelfTest
|
||||
from .SetU2FCounter import SetU2FCounter
|
||||
from .SignIdentity import SignIdentity
|
||||
@ -274,6 +275,7 @@ from . import PinMatrixRequestType
|
||||
from . import RecoveryDeviceType
|
||||
from . import RequestType
|
||||
from . import ResetDeviceBackupType
|
||||
from . import SdProtectOperationType
|
||||
from . import TezosBallotType
|
||||
from . import TezosContractType
|
||||
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,
|
||||
const uint8_t *ext_salt,
|
||||
uint8_t kek[SHA256_DIGEST_LENGTH],
|
||||
uint8_t keiv[SHA256_DIGEST_LENGTH]) {
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
REVERSE32(pin, pin);
|
||||
#endif
|
||||
|
||||
uint8_t salt[HARDWARE_SALT_SIZE + RANDOM_SALT_SIZE];
|
||||
memcpy(salt, hardware_salt, HARDWARE_SALT_SIZE);
|
||||
memcpy(salt + HARDWARE_SALT_SIZE, random_salt, RANDOM_SALT_SIZE);
|
||||
uint8_t salt[HARDWARE_SALT_SIZE + RANDOM_SALT_SIZE + EXTERNAL_SALT_SIZE];
|
||||
size_t salt_len = 0;
|
||||
|
||||
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;
|
||||
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_Init(&ctx, (const uint8_t *)&pin, sizeof(pin), salt,
|
||||
sizeof(salt), 1);
|
||||
salt_len, 1);
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10);
|
||||
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_Init(&ctx, (const uint8_t *)&pin, sizeof(pin), salt,
|
||||
sizeof(salt), 2);
|
||||
salt_len, 2);
|
||||
for (int i = 6; i <= 10; i++) {
|
||||
pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10);
|
||||
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));
|
||||
}
|
||||
|
||||
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 *salt = buffer;
|
||||
uint8_t *rand_salt = buffer;
|
||||
uint8_t *ekeys = buffer + RANDOM_SALT_SIZE;
|
||||
uint8_t *pvc = buffer + RANDOM_SALT_SIZE + KEYS_SIZE;
|
||||
|
||||
uint8_t kek[SHA256_DIGEST_LENGTH];
|
||||
uint8_t keiv[SHA256_DIGEST_LENGTH];
|
||||
chacha20poly1305_ctx ctx;
|
||||
random_buffer(salt, RANDOM_SALT_SIZE);
|
||||
derive_kek(pin, salt, kek, keiv);
|
||||
random_buffer(rand_salt, RANDOM_SALT_SIZE);
|
||||
derive_kek(pin, rand_salt, ext_salt, kek, keiv);
|
||||
rfc7539_init(&ctx, kek, keiv);
|
||||
memzero(kek, sizeof(kek));
|
||||
memzero(keiv, sizeof(keiv));
|
||||
@ -515,7 +526,7 @@ static void init_wiped_storage(void) {
|
||||
ui_total = DERIVE_SECS;
|
||||
ui_rem = ui_total;
|
||||
ui_message = PROCESSING_MSG;
|
||||
ensure(set_pin(PIN_EMPTY), "init_pin failed");
|
||||
ensure(set_pin(PIN_EMPTY, NULL), "init_pin failed");
|
||||
if (unlocked != sectrue) {
|
||||
memzero(cached_keys, sizeof(cached_keys));
|
||||
}
|
||||
@ -784,7 +795,7 @@ static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) {
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
static secbool unlock(uint32_t pin) {
|
||||
static secbool unlock(uint32_t pin, const uint8_t *ext_salt) {
|
||||
if (sectrue != initialized) {
|
||||
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
|
||||
// KEIV from the PIN.
|
||||
const void *salt = NULL;
|
||||
const void *rand_salt = NULL;
|
||||
uint16_t len = 0;
|
||||
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) {
|
||||
memzero(&pin, sizeof(pin));
|
||||
handle_fault("no EDEK");
|
||||
@ -838,7 +849,7 @@ static secbool unlock(uint32_t pin) {
|
||||
}
|
||||
uint8_t kek[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));
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
secbool storage_unlock(uint32_t pin) {
|
||||
secbool storage_unlock(uint32_t pin, const uint8_t *ext_salt) {
|
||||
ui_total = DERIVE_SECS;
|
||||
ui_rem = ui_total;
|
||||
if (pin == PIN_EMPTY) {
|
||||
@ -887,7 +898,7 @@ secbool storage_unlock(uint32_t pin) {
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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
|
||||
: PROCESSING_MSG;
|
||||
|
||||
if (sectrue != unlock(oldpin)) {
|
||||
if (sectrue != unlock(oldpin, old_ext_salt)) {
|
||||
return secfalse;
|
||||
}
|
||||
secbool ret = set_pin(newpin);
|
||||
secbool ret = set_pin(newpin, new_ext_salt);
|
||||
memzero(&oldpin, sizeof(oldpin));
|
||||
memzero(&newpin, sizeof(newpin));
|
||||
return ret;
|
||||
@ -1268,9 +1281,9 @@ static secbool storage_upgrade(void) {
|
||||
ui_rem = ui_total;
|
||||
ui_message = PROCESSING_MSG;
|
||||
if (sectrue == norcow_get(V0_PIN_KEY, &val, &len)) {
|
||||
set_pin(*(const uint32_t *)val);
|
||||
set_pin(*(const uint32_t *)val, NULL);
|
||||
} else {
|
||||
set_pin(PIN_EMPTY);
|
||||
set_pin(PIN_EMPTY, NULL);
|
||||
}
|
||||
|
||||
// Convert PIN failure counter.
|
||||
|
@ -24,6 +24,9 @@
|
||||
#include <stdint.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,
|
||||
const char *message);
|
||||
|
||||
@ -32,11 +35,13 @@ 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);
|
||||
secbool storage_unlock(const 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(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,
|
||||
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 os
|
||||
|
||||
EXTERNAL_SALT_LEN = 32
|
||||
sectrue = -1431655766 # 0xAAAAAAAAA
|
||||
fname = os.path.join(os.path.dirname(__file__), "libtrezor-storage.so")
|
||||
|
||||
@ -20,8 +21,10 @@ class Storage:
|
||||
def wipe(self) -> None:
|
||||
self.lib.storage_wipe()
|
||||
|
||||
def unlock(self, pin: int) -> bool:
|
||||
return sectrue == self.lib.storage_unlock(c.c_uint32(pin))
|
||||
def unlock(self, pin: int, ext_salt: bytes = None) -> bool:
|
||||
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:
|
||||
self.lib.storage_lock()
|
||||
@ -32,9 +35,19 @@ class Storage:
|
||||
def get_pin_rem(self) -> int:
|
||||
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(
|
||||
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:
|
||||
|
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