mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-13 19:18:56 +00:00
Merge branch 'pin_fails'
This commit is contained in:
commit
f88080b904
@ -11,6 +11,7 @@ SOURCE_MOD = []
|
||||
SOURCE_MOD += [
|
||||
'embed/extmod/modtrezorconfig/modtrezorconfig.c',
|
||||
'embed/extmod/modtrezorconfig/norcow.c',
|
||||
'embed/extmod/modtrezorconfig/storage.c',
|
||||
]
|
||||
|
||||
# modtrezorcrypto
|
||||
|
@ -12,6 +12,7 @@ LIBS_MOD = []
|
||||
SOURCE_MOD += [
|
||||
'embed/extmod/modtrezorconfig/modtrezorconfig.c',
|
||||
'embed/extmod/modtrezorconfig/norcow.c',
|
||||
'embed/extmod/modtrezorconfig/storage.c',
|
||||
]
|
||||
|
||||
# modtrezorcrypto
|
||||
@ -215,6 +216,7 @@ SOURCE_UNIX = [
|
||||
'vendor/micropython/ports/unix/alloc.c',
|
||||
'embed/unix/common.c',
|
||||
'embed/unix/touch.c',
|
||||
'embed/unix/flash.c',
|
||||
]
|
||||
|
||||
SOURCE_EMIT_NATIVE = ['vendor/micropython/py/emitnative.c']
|
||||
|
@ -1,54 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) Pavol Rusnak, SatoshiLabs
|
||||
* Copyright (c) Pavol Rusnak, Jan Pochyla, SatoshiLabs
|
||||
*
|
||||
* Licensed under TREZOR License
|
||||
* see LICENSE file for details
|
||||
*/
|
||||
|
||||
#include "py/runtime.h"
|
||||
#include "py/mphal.h"
|
||||
#include "py/objstr.h"
|
||||
|
||||
#if MICROPY_PY_TREZORCONFIG
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "norcow.h"
|
||||
|
||||
static bool initialized = false;
|
||||
#include "storage.h"
|
||||
|
||||
/// def init() -> None:
|
||||
/// '''
|
||||
/// Initializes the storage. Must be called before any other method is called from this module!
|
||||
/// Initializes the storage. Must be called before any other method is
|
||||
/// called from this module!
|
||||
/// '''
|
||||
STATIC mp_obj_t mod_trezorconfig_init(void) {
|
||||
bool r = norcow_init();
|
||||
if (!r) {
|
||||
if (sectrue != storage_init()) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, "Could not initialize config module");
|
||||
}
|
||||
initialized = true;
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorconfig_init_obj, mod_trezorconfig_init);
|
||||
|
||||
/// def unlock(pin: str) -> bool:
|
||||
/// '''
|
||||
/// Attempts to unlock the storage with given PIN. Returns True on
|
||||
/// success, False on failure.
|
||||
/// '''
|
||||
STATIC mp_obj_t mod_trezorconfig_unlock(mp_obj_t pin) {
|
||||
mp_buffer_info_t buf;
|
||||
mp_get_buffer_raise(pin, &buf, MP_BUFFER_READ);
|
||||
if (sectrue != storage_unlock(buf.buf, buf.len)) {
|
||||
return mp_const_false;
|
||||
}
|
||||
return mp_const_true;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorconfig_unlock_obj, mod_trezorconfig_unlock);
|
||||
|
||||
/// def has_pin() -> bool:
|
||||
/// '''
|
||||
/// Returns True if storage has a configured PIN, False otherwise.
|
||||
/// '''
|
||||
STATIC mp_obj_t mod_trezorconfig_has_pin(void) {
|
||||
if (sectrue != storage_has_pin()) {
|
||||
return mp_const_false;
|
||||
}
|
||||
return mp_const_true;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorconfig_has_pin_obj, mod_trezorconfig_has_pin);
|
||||
|
||||
/// def change_pin(pin: str, newpin: str) -> bool:
|
||||
/// '''
|
||||
/// Change PIN. Returns True on success, False on failure.
|
||||
/// '''
|
||||
STATIC mp_obj_t mod_trezorconfig_change_pin(mp_obj_t pin, mp_obj_t newpin) {
|
||||
mp_buffer_info_t pinbuf;
|
||||
mp_get_buffer_raise(pin, &pinbuf, MP_BUFFER_READ);
|
||||
mp_buffer_info_t newbuf;
|
||||
mp_get_buffer_raise(newpin, &newbuf, MP_BUFFER_READ);
|
||||
if (sectrue != storage_change_pin(pinbuf.buf, pinbuf.len, newbuf.buf, newbuf.len)) {
|
||||
return mp_const_false;
|
||||
}
|
||||
return mp_const_true;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorconfig_change_pin_obj, mod_trezorconfig_change_pin);
|
||||
|
||||
/// def get(app: int, key: int) -> bytes:
|
||||
/// '''
|
||||
/// Gets a value of given key for given app (or empty bytes if not set).
|
||||
/// '''
|
||||
STATIC mp_obj_t mod_trezorconfig_get(mp_obj_t app, mp_obj_t key) {
|
||||
if (!initialized) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, "Config module not initialized");
|
||||
}
|
||||
uint8_t a = mp_obj_get_int(app);
|
||||
uint8_t k = mp_obj_get_int(key);
|
||||
uint16_t appkey = a << 8 | k, len;
|
||||
uint16_t appkey = a << 8 | k;
|
||||
uint16_t len = 0;
|
||||
const void *val;
|
||||
bool r = norcow_get(appkey, &val, &len);
|
||||
if (!r || len == 0) {
|
||||
if (sectrue != storage_get(appkey, &val, &len) || len == 0) {
|
||||
return mp_const_empty_bytes;
|
||||
}
|
||||
vstr_t vstr;
|
||||
vstr_init_len(&vstr, len);
|
||||
memcpy(vstr.buf, val, len);
|
||||
return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr);
|
||||
return mp_obj_new_str_of_type(&mp_type_bytes, val, len);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorconfig_get_obj, mod_trezorconfig_get);
|
||||
|
||||
@ -57,16 +92,12 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorconfig_get_obj, mod_trezorconfig_get)
|
||||
/// Sets a value of given key for given app.
|
||||
/// '''
|
||||
STATIC mp_obj_t mod_trezorconfig_set(mp_obj_t app, mp_obj_t key, mp_obj_t value) {
|
||||
if (!initialized) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, "Config module not initialized");
|
||||
}
|
||||
uint8_t a = mp_obj_get_int(app);
|
||||
uint8_t k = mp_obj_get_int(key);
|
||||
uint16_t appkey = a << 8 | k;
|
||||
mp_buffer_info_t v;
|
||||
mp_get_buffer_raise(value, &v, MP_BUFFER_READ);
|
||||
bool r = norcow_set(appkey, v.buf, v.len);
|
||||
if (!r) {
|
||||
if (sectrue != storage_set(appkey, v.buf, v.len)) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, "Could not save value");
|
||||
}
|
||||
return mp_const_none;
|
||||
@ -78,11 +109,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_trezorconfig_set_obj, mod_trezorconfig_set)
|
||||
/// Erases the whole config. Use with caution!
|
||||
/// '''
|
||||
STATIC mp_obj_t mod_trezorconfig_wipe(void) {
|
||||
if (!initialized) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, "Config module not initialized");
|
||||
}
|
||||
bool r = norcow_wipe();
|
||||
if (!r) {
|
||||
if (sectrue != storage_wipe()) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, "Could not wipe storage");
|
||||
}
|
||||
return mp_const_none;
|
||||
@ -92,6 +119,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorconfig_wipe_obj, mod_trezorconfig_wip
|
||||
STATIC const mp_rom_map_elem_t mp_module_trezorconfig_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorconfig) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&mod_trezorconfig_init_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_unlock), MP_ROM_PTR(&mod_trezorconfig_unlock_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_has_pin), MP_ROM_PTR(&mod_trezorconfig_has_pin_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_change_pin), MP_ROM_PTR(&mod_trezorconfig_change_pin_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&mod_trezorconfig_get_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&mod_trezorconfig_set_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_wipe), MP_ROM_PTR(&mod_trezorconfig_wipe_obj) },
|
||||
|
@ -1 +0,0 @@
|
||||
../../../vendor/norcow/norcow.c
|
262
embed/extmod/modtrezorconfig/norcow.c
Normal file
262
embed/extmod/modtrezorconfig/norcow.c
Normal file
@ -0,0 +1,262 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "norcow.h"
|
||||
|
||||
#include "../../trezorhal/flash.h"
|
||||
|
||||
#ifndef NORCOW_SECTORS
|
||||
#define NORCOW_SECTORS {4, 16}
|
||||
#endif
|
||||
|
||||
static uint8_t norcow_sectors[NORCOW_SECTOR_COUNT] = NORCOW_SECTORS;
|
||||
static uint8_t norcow_active_sector = 0;
|
||||
static uint32_t norcow_active_offset = 0;
|
||||
|
||||
/*
|
||||
* Erases sector
|
||||
*/
|
||||
static secbool norcow_erase(uint8_t sector)
|
||||
{
|
||||
if (sector >= NORCOW_SECTOR_COUNT) {
|
||||
return secfalse;
|
||||
}
|
||||
return flash_erase_sectors(&norcow_sectors[sector], 1, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns pointer to sector, starting with offset
|
||||
* Fails when there is not enough space for data of given size
|
||||
*/
|
||||
static const void *norcow_ptr(uint8_t sector, uint32_t offset, uint32_t size)
|
||||
{
|
||||
if (sector >= NORCOW_SECTOR_COUNT) {
|
||||
return NULL;
|
||||
}
|
||||
return flash_get_address(norcow_sectors[sector], offset, size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Writes data to given sector, starting from offset
|
||||
*/
|
||||
static secbool norcow_write(uint8_t sector, uint32_t offset, uint32_t prefix, const uint8_t *data, uint16_t len)
|
||||
{
|
||||
if (sector >= NORCOW_SECTOR_COUNT) {
|
||||
return secfalse;
|
||||
}
|
||||
if (sectrue != flash_unlock()) {
|
||||
return secfalse;
|
||||
}
|
||||
// write prefix
|
||||
if (sectrue != flash_write_word_rel(norcow_sectors[sector], offset, prefix)) {
|
||||
flash_lock();
|
||||
return secfalse;
|
||||
}
|
||||
offset += sizeof(uint32_t);
|
||||
// write data
|
||||
for (uint16_t i = 0; i < len; i++, offset++) {
|
||||
if (sectrue != flash_write_byte_rel(norcow_sectors[sector], offset, data[i])) {
|
||||
flash_lock();
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
// pad with zeroes
|
||||
for (; offset % 4; offset++) {
|
||||
if (sectrue != flash_write_byte_rel(norcow_sectors[sector], offset, 0x00)) {
|
||||
flash_lock();
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
flash_lock();
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
#define ALIGN4(X) (X) = ((X) + 3) & ~3
|
||||
|
||||
/*
|
||||
* Reads one item starting from offset
|
||||
*/
|
||||
static secbool read_item(uint8_t sector, uint32_t offset, uint16_t *key, const void **val, uint16_t *len, uint32_t *pos)
|
||||
{
|
||||
*pos = offset;
|
||||
|
||||
const void *k = norcow_ptr(sector, *pos, 2);
|
||||
if (k == NULL) return secfalse;
|
||||
*pos += 2;
|
||||
memcpy(key, k, sizeof(uint16_t));
|
||||
if (*key == 0xFFFF) {
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
const void *l = norcow_ptr(sector, *pos, 2);
|
||||
if (l == NULL) return secfalse;
|
||||
*pos += 2;
|
||||
memcpy(len, l, sizeof(uint16_t));
|
||||
|
||||
*val = norcow_ptr(sector, *pos, *len);
|
||||
if (*val == NULL) return secfalse;
|
||||
*pos += *len;
|
||||
ALIGN4(*pos);
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Writes one item starting from offset
|
||||
*/
|
||||
static secbool write_item(uint8_t sector, uint32_t offset, uint16_t key, const void *val, uint16_t len, uint32_t *pos)
|
||||
{
|
||||
uint32_t prefix = (len << 16) | key;
|
||||
*pos = offset + sizeof(uint32_t) + len;
|
||||
ALIGN4(*pos);
|
||||
return norcow_write(sector, offset, prefix, val, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Finds item in given sector
|
||||
*/
|
||||
static secbool find_item(uint8_t sector, uint16_t key, const void **val, uint16_t *len)
|
||||
{
|
||||
*val = 0;
|
||||
*len = 0;
|
||||
uint32_t offset = 0;
|
||||
for (;;) {
|
||||
uint16_t k, l;
|
||||
const void *v;
|
||||
uint32_t pos;
|
||||
if (sectrue != read_item(sector, offset, &k, &v, &l, &pos)) {
|
||||
break;
|
||||
}
|
||||
if (key == k) {
|
||||
*val = v;
|
||||
*len = l;
|
||||
}
|
||||
offset = pos;
|
||||
}
|
||||
return sectrue * (*val != NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Finds first unused offset in given sector
|
||||
*/
|
||||
static uint32_t find_free_offset(uint8_t sector)
|
||||
{
|
||||
uint32_t offset = 0;
|
||||
for (;;) {
|
||||
uint16_t key, len;
|
||||
const void *val;
|
||||
uint32_t pos;
|
||||
if (sectrue != read_item(sector, offset, &key, &val, &len, &pos)) {
|
||||
break;
|
||||
}
|
||||
offset = pos;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compacts active sector and sets new active sector
|
||||
*/
|
||||
static void compact()
|
||||
{
|
||||
uint8_t norcow_next_sector = (norcow_active_sector + 1) % NORCOW_SECTOR_COUNT;
|
||||
|
||||
uint32_t offset = 0, offsetw = 0;
|
||||
|
||||
for (;;) {
|
||||
// read item
|
||||
uint16_t k, l;
|
||||
const void *v;
|
||||
uint32_t pos;
|
||||
secbool r = read_item(norcow_active_sector, offset, &k, &v, &l, &pos);
|
||||
if (sectrue != r) break;
|
||||
offset = pos;
|
||||
|
||||
// check if not already saved
|
||||
const void *v2;
|
||||
uint16_t l2;
|
||||
r = find_item(norcow_next_sector, k, &v2, &l2);
|
||||
if (sectrue == r) continue;
|
||||
|
||||
// scan for latest instance
|
||||
uint32_t offsetr = offset;
|
||||
for (;;) {
|
||||
uint16_t k2;
|
||||
uint32_t posr;
|
||||
r = read_item(norcow_active_sector, offsetr, &k2, &v2, &l2, &posr);
|
||||
if (sectrue != r) break;
|
||||
if (k == k2) {
|
||||
v = v2;
|
||||
l = l2;
|
||||
}
|
||||
offsetr = posr;
|
||||
}
|
||||
|
||||
// copy the last item
|
||||
uint32_t posw;
|
||||
r = write_item(norcow_next_sector, offsetw, k, v, l, &posw);
|
||||
if (sectrue != r) { } // TODO: error
|
||||
offsetw = posw;
|
||||
}
|
||||
|
||||
norcow_erase(norcow_active_sector);
|
||||
norcow_active_sector = norcow_next_sector;
|
||||
norcow_active_offset = find_free_offset(norcow_active_sector);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initializes storage
|
||||
*/
|
||||
secbool norcow_init(void)
|
||||
{
|
||||
// detect active sector (inactive sectors are empty = start with 0xFF)
|
||||
for (uint8_t i = 0; i < NORCOW_SECTOR_COUNT; i++) {
|
||||
const uint8_t *b = norcow_ptr(i, 0, 1);
|
||||
if (b != NULL && *b != 0xFF) {
|
||||
norcow_active_sector = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
norcow_active_offset = find_free_offset(norcow_active_sector);
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wipe the storage
|
||||
*/
|
||||
secbool norcow_wipe(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < NORCOW_SECTOR_COUNT; i++) {
|
||||
if (sectrue != norcow_erase(i)) {
|
||||
return secfalse;
|
||||
}
|
||||
}
|
||||
norcow_active_sector = 0;
|
||||
norcow_active_offset = 0;
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Looks for the given key, returns status of the operation
|
||||
*/
|
||||
secbool norcow_get(uint16_t key, const void **val, uint16_t *len)
|
||||
{
|
||||
return find_item(norcow_active_sector, key, val, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the given key, returns status of the operation
|
||||
*/
|
||||
secbool norcow_set(uint16_t key, const void *val, uint16_t len)
|
||||
{
|
||||
// check whether there is enough free space
|
||||
// and compact if full
|
||||
if (norcow_active_offset + sizeof(uint32_t) + len > NORCOW_SECTOR_SIZE) {
|
||||
compact();
|
||||
}
|
||||
// write item
|
||||
uint32_t pos;
|
||||
secbool r = write_item(norcow_active_sector, norcow_active_offset, key, val, len, &pos);
|
||||
if (sectrue == r) {
|
||||
norcow_active_offset = pos;
|
||||
}
|
||||
return r;
|
||||
}
|
@ -1 +0,0 @@
|
||||
../../../vendor/norcow/norcow.h
|
34
embed/extmod/modtrezorconfig/norcow.h
Normal file
34
embed/extmod/modtrezorconfig/norcow.h
Normal file
@ -0,0 +1,34 @@
|
||||
#ifndef __NORCOW_H__
|
||||
#define __NORCOW_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../../trezorhal/secbool.h"
|
||||
|
||||
/*
|
||||
* Storage parameters:
|
||||
*/
|
||||
|
||||
#define NORCOW_SECTOR_COUNT 2
|
||||
#define NORCOW_SECTOR_SIZE (64*1024)
|
||||
|
||||
/*
|
||||
* Initialize storage
|
||||
*/
|
||||
secbool norcow_init(void);
|
||||
|
||||
/*
|
||||
* Wipe the storage
|
||||
*/
|
||||
secbool norcow_wipe(void);
|
||||
|
||||
/*
|
||||
* Looks for the given key, returns status of the operation
|
||||
*/
|
||||
secbool norcow_get(uint16_t key, const void **val, uint16_t *len);
|
||||
|
||||
/*
|
||||
* Sets the given key, returns status of the operation
|
||||
*/
|
||||
secbool norcow_set(uint16_t key, const void *val, uint16_t len);
|
||||
|
||||
#endif
|
@ -1,18 +0,0 @@
|
||||
#if defined TREZOR_STM32
|
||||
|
||||
#define NORCOW_STM32 1
|
||||
|
||||
#define NORCOW_SECTORS {4, 16}
|
||||
#define NORCOW_ADDRESSES {0x08010000, 0x08110000}
|
||||
|
||||
#elif defined TREZOR_UNIX
|
||||
|
||||
#define NORCOW_UNIX 1
|
||||
|
||||
#define NORCOW_FILE "/var/tmp/trezor.config"
|
||||
|
||||
#else
|
||||
|
||||
#error Unsupported TREZOR port. Only STM32 and UNIX ports are supported.
|
||||
|
||||
#endif
|
215
embed/extmod/modtrezorconfig/storage.c
Normal file
215
embed/extmod/modtrezorconfig/storage.c
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (c) Pavol Rusnak, Jan Pochyla, SatoshiLabs
|
||||
*
|
||||
* Licensed under TREZOR License
|
||||
* see LICENSE file for details
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "norcow.h"
|
||||
#include "../../trezorhal/flash.h"
|
||||
|
||||
// Byte-length of flash sector containing fail counters.
|
||||
#define PIN_SECTOR_SIZE 0x4000
|
||||
|
||||
// Maximum number of failed unlock attempts.
|
||||
#define PIN_MAX_TRIES 15
|
||||
|
||||
// Norcow storage key of configured PIN.
|
||||
#define PIN_KEY 0x0000
|
||||
|
||||
// Maximum PIN length.
|
||||
#define PIN_MAXLEN 32
|
||||
|
||||
static secbool initialized = secfalse;
|
||||
static secbool unlocked = secfalse;
|
||||
|
||||
secbool storage_init(void)
|
||||
{
|
||||
initialized = secfalse;
|
||||
unlocked = secfalse;
|
||||
if (sectrue != flash_init()) {
|
||||
return secfalse;
|
||||
}
|
||||
if (sectrue != norcow_init()) {
|
||||
return secfalse;
|
||||
}
|
||||
initialized = sectrue;
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
static void pin_fails_reset(uint32_t ofs)
|
||||
{
|
||||
if (ofs + sizeof(uint32_t) >= PIN_SECTOR_SIZE) {
|
||||
// ofs points to the last word of the PIN fails area. Because there is
|
||||
// no space left, we recycle the sector (set all words to 0xffffffff).
|
||||
// On next unlock attempt, we start counting from the the first word.
|
||||
flash_erase_sectors((uint8_t[]) { FLASH_SECTOR_PIN_AREA }, 1, NULL);
|
||||
} else {
|
||||
// Mark this counter as exhausted. On next unlock attempt, pinfails_get
|
||||
// seeks to the next word.
|
||||
flash_unlock();
|
||||
flash_write_word_rel(FLASH_SECTOR_PIN_AREA, ofs, 0);
|
||||
flash_lock();
|
||||
}
|
||||
}
|
||||
|
||||
static secbool pin_fails_increase(uint32_t ofs)
|
||||
{
|
||||
uint32_t ctr = ~PIN_MAX_TRIES;
|
||||
if (sectrue != flash_read_word_rel(FLASH_SECTOR_PIN_AREA, ofs, &ctr)) {
|
||||
return secfalse;
|
||||
}
|
||||
ctr = ctr << 1;
|
||||
|
||||
flash_unlock();
|
||||
if (sectrue != flash_write_word_rel(FLASH_SECTOR_PIN_AREA, ofs, ctr)) {
|
||||
flash_lock();
|
||||
return secfalse;
|
||||
}
|
||||
flash_lock();
|
||||
|
||||
uint32_t check = 0;
|
||||
if (sectrue != flash_read_word_rel(FLASH_SECTOR_PIN_AREA, ofs, &check)) {
|
||||
return secfalse;
|
||||
}
|
||||
if (ctr != check) {
|
||||
return secfalse;
|
||||
}
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
static void pin_fails_check_max(uint32_t ctr)
|
||||
{
|
||||
if (~ctr >= 1 << PIN_MAX_TRIES) {
|
||||
for (;;) {
|
||||
if (norcow_wipe()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
static secbool pin_fails_read(uint32_t *ofs, uint32_t *ctr)
|
||||
{
|
||||
if (NULL == ofs || NULL == ctr) {
|
||||
return secfalse;
|
||||
}
|
||||
for (uint32_t o = 0; o < PIN_SECTOR_SIZE; o += sizeof(uint32_t)) {
|
||||
uint32_t c = 0;
|
||||
if (!flash_read_word_rel(FLASH_SECTOR_PIN_AREA, o, &c)) {
|
||||
return secfalse;
|
||||
}
|
||||
if (c != 0) {
|
||||
*ofs = o;
|
||||
*ctr = c;
|
||||
return sectrue;
|
||||
}
|
||||
}
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
static secbool const_cmp(const uint8_t *pub, size_t publen, const uint8_t *sec, size_t seclen)
|
||||
{
|
||||
size_t diff = seclen ^ publen;
|
||||
for (size_t i = 0; i < publen; i++) {
|
||||
diff |= pub[i] ^ sec[i];
|
||||
}
|
||||
return sectrue * (0 == diff);
|
||||
}
|
||||
|
||||
static secbool pin_cmp(const uint8_t *pin, size_t pinlen)
|
||||
{
|
||||
const void *spin = NULL;
|
||||
uint16_t spinlen = 0;
|
||||
norcow_get(PIN_KEY, &spin, &spinlen);
|
||||
if (NULL != spin) {
|
||||
return const_cmp(pin, pinlen, spin, spinlen);
|
||||
} else {
|
||||
return sectrue * (0 == pinlen);
|
||||
}
|
||||
}
|
||||
|
||||
static secbool pin_check(const uint8_t *pin, size_t len)
|
||||
{
|
||||
uint32_t ofs;
|
||||
uint32_t ctr;
|
||||
if (sectrue != pin_fails_read(&ofs, &ctr)) {
|
||||
return secfalse;
|
||||
}
|
||||
pin_fails_check_max(ctr);
|
||||
|
||||
// Sleep for ~ctr seconds before checking the PIN.
|
||||
for (uint32_t wait = ~ctr; wait > 0; wait--) {
|
||||
hal_delay(1000);
|
||||
}
|
||||
|
||||
// First, we increase PIN fail counter in storage, even before checking the
|
||||
// PIN. If the PIN is correct, we reset the counter afterwards. If not, we
|
||||
// check if this is the last allowed attempt.
|
||||
if (sectrue != pin_fails_increase(ofs)) {
|
||||
return secfalse;
|
||||
}
|
||||
if (sectrue != pin_cmp(pin, len)) {
|
||||
pin_fails_check_max(ctr << 1);
|
||||
return secfalse;
|
||||
}
|
||||
pin_fails_reset(ofs);
|
||||
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
secbool storage_unlock(const uint8_t *pin, size_t len)
|
||||
{
|
||||
unlocked = secfalse;
|
||||
if (sectrue == initialized && sectrue == pin_check(pin, len)) {
|
||||
unlocked = sectrue;
|
||||
}
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
secbool storage_get(uint16_t key, const void **val, uint16_t *len)
|
||||
{
|
||||
if (sectrue != initialized || sectrue != unlocked || PIN_KEY == key) {
|
||||
return secfalse;
|
||||
}
|
||||
return norcow_get(key, val, len);
|
||||
}
|
||||
|
||||
secbool storage_set(uint16_t key, const void *val, uint16_t len)
|
||||
{
|
||||
if (sectrue != initialized || sectrue != unlocked || PIN_KEY == key) {
|
||||
return secfalse;
|
||||
}
|
||||
return norcow_set(key, val, len);
|
||||
}
|
||||
|
||||
secbool storage_has_pin(void)
|
||||
{
|
||||
if (sectrue != initialized) {
|
||||
return secfalse;
|
||||
}
|
||||
const void *spin = NULL;
|
||||
uint16_t spinlen = 0;
|
||||
norcow_get(PIN_KEY, &spin, &spinlen);
|
||||
return sectrue * (0 != spinlen);
|
||||
}
|
||||
|
||||
secbool storage_change_pin(const uint8_t *pin, size_t len, const uint8_t *newpin, size_t newlen)
|
||||
{
|
||||
if (sectrue != initialized || sectrue != unlocked || newlen > PIN_MAXLEN) {
|
||||
return secfalse;
|
||||
}
|
||||
if (sectrue != pin_check(pin, len)) {
|
||||
return secfalse;
|
||||
}
|
||||
return norcow_set(PIN_KEY, newpin, newlen);
|
||||
}
|
||||
|
||||
secbool storage_wipe(void)
|
||||
{
|
||||
return norcow_wipe();
|
||||
}
|
18
embed/extmod/modtrezorconfig/storage.h
Normal file
18
embed/extmod/modtrezorconfig/storage.h
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) Pavol Rusnak, Jan Pochyla, SatoshiLabs
|
||||
*
|
||||
* Licensed under TREZOR License
|
||||
* see LICENSE file for details
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "../../trezorhal/secbool.h"
|
||||
|
||||
secbool storage_init(void);
|
||||
secbool storage_wipe(void);
|
||||
secbool storage_unlock(const uint8_t *pin, size_t len);
|
||||
secbool storage_has_pin(void);
|
||||
secbool storage_change_pin(const uint8_t *pin, size_t len, const uint8_t *newpin, size_t newlen);
|
||||
secbool storage_get(uint16_t key, const void **val, uint16_t *len);
|
||||
secbool storage_set(uint16_t key, const void *val, uint16_t len);
|
@ -5,13 +5,7 @@
|
||||
* see LICENSE file for details
|
||||
*/
|
||||
|
||||
#if defined TREZOR_STM32
|
||||
#include "flash.h"
|
||||
#elif defined TREZOR_UNIX
|
||||
#include "unix-flash-mock.h"
|
||||
#else
|
||||
#error Unsupported TREZOR port. Only STM32 and UNIX ports are supported.
|
||||
#endif
|
||||
#include "../../trezorhal/flash.h"
|
||||
|
||||
/// class FlashOTP:
|
||||
/// '''
|
||||
|
@ -13,10 +13,13 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../../unix/common.h"
|
||||
#include "../../trezorhal/usb.h"
|
||||
#include "../../trezorhal/touch.h"
|
||||
|
||||
void __attribute__((noreturn)) __fatal_error(const char *expr, const char *msg, const char *file, int line, const char *func);
|
||||
|
||||
#define ensure(expr, msg) (((expr) == sectrue) ? (void)0 : __fatal_error(#expr, msg, __FILE__, __LINE__, __func__))
|
||||
|
||||
#define TREZOR_UDP_IFACE 0
|
||||
#define TREZOR_UDP_PORT 21324
|
||||
|
||||
|
@ -1,11 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) Pavol Rusnak, Jan Pochyla, SatoshiLabs
|
||||
*
|
||||
* Licensed under TREZOR License
|
||||
* see LICENSE file for details
|
||||
*/
|
||||
|
||||
#include STM32_HAL_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "flash.h"
|
||||
|
||||
// see docs/memory.md for more information
|
||||
|
||||
const uint32_t FLASH_SECTOR_TABLE[FLASH_SECTOR_COUNT + 1] = {
|
||||
static const uint32_t FLASH_SECTOR_TABLE[FLASH_SECTOR_COUNT + 1] = {
|
||||
[ 0] = 0x08000000, // - 0x08003FFF | 16 KiB
|
||||
[ 1] = 0x08004000, // - 0x08007FFF | 16 KiB
|
||||
[ 2] = 0x08008000, // - 0x0800BFFF | 16 KiB
|
||||
@ -33,6 +41,11 @@ const uint32_t FLASH_SECTOR_TABLE[FLASH_SECTOR_COUNT + 1] = {
|
||||
[24] = 0x08200000, // last element - not a valid sector
|
||||
};
|
||||
|
||||
secbool flash_init(void)
|
||||
{
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
secbool flash_unlock(void)
|
||||
{
|
||||
HAL_FLASH_Unlock();
|
||||
@ -46,6 +59,19 @@ secbool flash_lock(void)
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
const void *flash_get_address(uint8_t sector, uint32_t offset, uint32_t size)
|
||||
{
|
||||
if (sector >= FLASH_SECTOR_COUNT) {
|
||||
return NULL;
|
||||
}
|
||||
uint32_t addr = FLASH_SECTOR_TABLE[sector];
|
||||
uint32_t next = FLASH_SECTOR_TABLE[sector + 1];
|
||||
if (addr + offset + size > next) {
|
||||
return NULL;
|
||||
}
|
||||
return (const uint8_t *)addr + offset;
|
||||
}
|
||||
|
||||
secbool flash_erase_sectors(const uint8_t *sectors, int len, void (*progress)(int pos, int len))
|
||||
{
|
||||
if (sectrue != flash_unlock()) {
|
||||
@ -66,7 +92,7 @@ secbool flash_erase_sectors(const uint8_t *sectors, int len, void (*progress)(in
|
||||
return secfalse;
|
||||
}
|
||||
// check whether the sector was really deleted (contains only 0xFF)
|
||||
uint32_t addr_start = FLASH_SECTOR_TABLE[sectors[i]], addr_end = FLASH_SECTOR_TABLE[sectors[i] + 1];
|
||||
const uint32_t addr_start = FLASH_SECTOR_TABLE[sectors[i]], addr_end = FLASH_SECTOR_TABLE[sectors[i] + 1];
|
||||
for (uint32_t addr = addr_start; addr < addr_end; addr += 4) {
|
||||
if (*((const uint32_t *)addr) != 0xFFFFFFFF) {
|
||||
flash_lock();
|
||||
@ -91,6 +117,28 @@ secbool flash_write_word(uint32_t address, uint32_t data)
|
||||
return sectrue * (HAL_OK == HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data));
|
||||
}
|
||||
|
||||
secbool flash_write_byte_rel(uint8_t sector, uint32_t offset, uint8_t data)
|
||||
{
|
||||
return sectrue * (HAL_OK == HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, FLASH_SECTOR_TABLE[sector] + offset, data));
|
||||
}
|
||||
|
||||
secbool flash_write_word_rel(uint8_t sector, uint32_t offset, uint32_t data)
|
||||
{
|
||||
if (offset % 4 != 0) {
|
||||
return secfalse;
|
||||
}
|
||||
return sectrue * (HAL_OK == HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_SECTOR_TABLE[sector] + offset, data));
|
||||
}
|
||||
|
||||
secbool flash_read_word_rel(uint8_t sector, uint32_t offset, uint32_t *data)
|
||||
{
|
||||
if (offset % 4 != 0) {
|
||||
return secfalse;
|
||||
}
|
||||
*data = *((const uint32_t *)FLASH_SECTOR_TABLE[sector] + offset);
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
#define FLASH_OTP_LOCK_BASE 0x1FFF7A00U
|
||||
|
||||
secbool flash_otp_read(uint8_t block, uint8_t offset, uint8_t *data, uint8_t datalen)
|
||||
|
@ -43,14 +43,19 @@
|
||||
// note: FLASH_SR_RDERR is STM32F42xxx and STM32F43xxx specific (STM32F427) (reference RM0090 section 3.7.5)
|
||||
#define FLASH_STATUS_ALL_FLAGS (FLASH_SR_RDERR | FLASH_SR_PGSERR | FLASH_SR_PGPERR | FLASH_SR_PGAERR | FLASH_SR_WRPERR | FLASH_SR_SOP | FLASH_SR_EOP)
|
||||
|
||||
extern const uint32_t FLASH_SECTOR_TABLE[FLASH_SECTOR_COUNT + 1];
|
||||
secbool flash_init(void);
|
||||
|
||||
secbool flash_unlock(void);
|
||||
secbool flash_lock(void);
|
||||
|
||||
const void *flash_get_address(uint8_t sector, uint32_t offset, uint32_t size);
|
||||
|
||||
secbool flash_erase_sectors(const uint8_t *sectors, int len, void (*progress)(int pos, int len));
|
||||
secbool flash_write_byte(uint32_t address, uint8_t data);
|
||||
secbool flash_write_word(uint32_t address, uint32_t data);
|
||||
secbool flash_write_byte_rel(uint8_t sector, uint32_t offset, uint8_t data);
|
||||
secbool flash_write_word_rel(uint8_t sector, uint32_t offset, uint32_t data);
|
||||
secbool flash_read_word_rel(uint8_t sector, uint32_t offset, uint32_t *data);
|
||||
|
||||
#define FLASH_OTP_NUM_BLOCKS 16
|
||||
#define FLASH_OTP_BLOCK_SIZE 32
|
||||
|
@ -144,7 +144,10 @@ secbool check_image_contents(const image_header * const hdr, uint32_t firstskip,
|
||||
if (0 == sectors || blocks < 1) {
|
||||
return secfalse;
|
||||
}
|
||||
const void *data = (const void *)(FLASH_SECTOR_TABLE[sectors[0]] + firstskip);
|
||||
const void *data = flash_get_address(sectors[0], firstskip, IMAGE_CHUNK_SIZE - firstskip);
|
||||
if (!data) {
|
||||
return secfalse;
|
||||
}
|
||||
int remaining = hdr->codelen;
|
||||
if (sectrue != check_single_hash(hdr->hashes, data, MIN(remaining, IMAGE_CHUNK_SIZE - firstskip))) {
|
||||
return secfalse;
|
||||
@ -155,7 +158,10 @@ secbool check_image_contents(const image_header * const hdr, uint32_t firstskip,
|
||||
if (block >= blocks) {
|
||||
return secfalse;
|
||||
}
|
||||
data = (const void *)FLASH_SECTOR_TABLE[sectors[block]];
|
||||
data = flash_get_address(sectors[block], 0, IMAGE_CHUNK_SIZE);
|
||||
if (!data) {
|
||||
return secfalse;
|
||||
}
|
||||
if (sectrue != check_single_hash(hdr->hashes + block * 32, data, MIN(remaining, IMAGE_CHUNK_SIZE))) {
|
||||
return secfalse;
|
||||
}
|
||||
|
@ -31,3 +31,8 @@ void hal_delay(uint32_t ms)
|
||||
{
|
||||
usleep(1000 * ms);
|
||||
}
|
||||
|
||||
void shutdown(void)
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
|
@ -9,4 +9,6 @@ void __attribute__((noreturn)) __fatal_error(const char *expr, const char *msg,
|
||||
|
||||
void hal_delay(uint32_t ms);
|
||||
|
||||
void shutdown(void);
|
||||
|
||||
#endif
|
||||
|
175
embed/unix/flash.c
Normal file
175
embed/unix/flash.c
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (c) Jan Pochyla, SatoshiLabs
|
||||
*
|
||||
* Licensed under TREZOR License
|
||||
* see LICENSE file for details
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "../trezorhal/flash.h"
|
||||
|
||||
#ifndef FLASH_FILE
|
||||
#define FLASH_FILE "/var/tmp/trezor.config"
|
||||
#endif
|
||||
|
||||
#define SECTOR_COUNT 24
|
||||
|
||||
static const uint32_t sector_table[SECTOR_COUNT + 1] = {
|
||||
[ 0] = 0x08000000, // - 0x08003FFF | 16 KiB
|
||||
[ 1] = 0x08004000, // - 0x08007FFF | 16 KiB
|
||||
[ 2] = 0x08008000, // - 0x0800BFFF | 16 KiB
|
||||
[ 3] = 0x0800C000, // - 0x0800FFFF | 16 KiB
|
||||
[ 4] = 0x08010000, // - 0x0801FFFF | 64 KiB
|
||||
[ 5] = 0x08020000, // - 0x0803FFFF | 128 KiB
|
||||
[ 6] = 0x08040000, // - 0x0805FFFF | 128 KiB
|
||||
[ 7] = 0x08060000, // - 0x0807FFFF | 128 KiB
|
||||
[ 8] = 0x08080000, // - 0x0809FFFF | 128 KiB
|
||||
[ 9] = 0x080A0000, // - 0x080BFFFF | 128 KiB
|
||||
[10] = 0x080C0000, // - 0x080DFFFF | 128 KiB
|
||||
[11] = 0x080E0000, // - 0x080FFFFF | 128 KiB
|
||||
[12] = 0x08100000, // - 0x08103FFF | 16 KiB
|
||||
[13] = 0x08104000, // - 0x08107FFF | 16 KiB
|
||||
[14] = 0x08108000, // - 0x0810BFFF | 16 KiB
|
||||
[15] = 0x0810C000, // - 0x0810FFFF | 16 KiB
|
||||
[16] = 0x08110000, // - 0x0811FFFF | 64 KiB
|
||||
[17] = 0x08120000, // - 0x0813FFFF | 128 KiB
|
||||
[18] = 0x08140000, // - 0x0815FFFF | 128 KiB
|
||||
[19] = 0x08160000, // - 0x0817FFFF | 128 KiB
|
||||
[20] = 0x08180000, // - 0x0819FFFF | 128 KiB
|
||||
[21] = 0x081A0000, // - 0x081BFFFF | 128 KiB
|
||||
[22] = 0x081C0000, // - 0x081DFFFF | 128 KiB
|
||||
[23] = 0x081E0000, // - 0x081FFFFF | 128 KiB
|
||||
[24] = 0x08200000, // last element - not a valid sector
|
||||
};
|
||||
|
||||
static uint8_t flash_buffer[0x200000];
|
||||
|
||||
static void flash_sync(void)
|
||||
{
|
||||
FILE *f = fopen(FLASH_FILE, "wb");
|
||||
if (f) {
|
||||
fwrite(flash_buffer, sizeof(flash_buffer), 1, f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
secbool flash_init(void)
|
||||
{
|
||||
FILE *f = fopen(FLASH_FILE, "rb");
|
||||
size_t r = 0;
|
||||
if (f) {
|
||||
r = fread(flash_buffer, sizeof(flash_buffer), 1, f);
|
||||
fclose(f);
|
||||
}
|
||||
if (r != 1) {
|
||||
memset(flash_buffer, 0xFF, sizeof(flash_buffer));
|
||||
}
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
secbool flash_unlock(void)
|
||||
{
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
secbool flash_lock(void)
|
||||
{
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
const void *flash_get_address(uint8_t sector, uint32_t offset, uint32_t size)
|
||||
{
|
||||
if (sector >= SECTOR_COUNT) {
|
||||
return NULL;
|
||||
}
|
||||
const uint32_t sector_size = sector_table[sector + 1] - sector_table[sector];
|
||||
if (offset + size > sector_size) {
|
||||
return NULL;
|
||||
}
|
||||
const uint32_t sector_offset = sector_table[sector] - sector_table[0];
|
||||
return flash_buffer + sector_offset + offset;
|
||||
}
|
||||
|
||||
secbool flash_erase_sectors(const uint8_t *sectors, int len, void (*progress)(int pos, int len))
|
||||
{
|
||||
if (progress) {
|
||||
progress(0, len);
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
const uint8_t sector = sectors[i];
|
||||
const uint32_t offset = sector_table[sector] - sector_table[0];
|
||||
const uint32_t size = sector_table[sector + 1] - sector_table[sector];
|
||||
memset(flash_buffer + offset, 0xFF, size);
|
||||
if (progress) {
|
||||
progress(i + 1, len);
|
||||
}
|
||||
flash_sync();
|
||||
}
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
secbool flash_write_byte_rel(uint8_t sector, uint32_t offset, uint8_t data)
|
||||
{
|
||||
uint8_t *flash = (uint8_t *)flash_get_address(sector, offset, sizeof(data));
|
||||
if (!flash) {
|
||||
return secfalse;
|
||||
}
|
||||
if ((flash[0] & data) != data) {
|
||||
return secfalse; // we cannot change zeroes to ones
|
||||
}
|
||||
flash[0] = data;
|
||||
flash_sync();
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
secbool flash_write_word_rel(uint8_t sector, uint32_t offset, uint32_t data)
|
||||
{
|
||||
if (offset % 4) { // we write only at 4-byte boundary
|
||||
return secfalse;
|
||||
}
|
||||
uint32_t *flash = (uint32_t *)flash_get_address(sector, offset, sizeof(data));
|
||||
if (!flash) {
|
||||
return secfalse;
|
||||
}
|
||||
if ((flash[0] & data) != data) {
|
||||
return secfalse; // we cannot change zeroes to ones
|
||||
}
|
||||
flash[0] = data;
|
||||
flash_sync();
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
secbool flash_read_word_rel(uint8_t sector, uint32_t offset, uint32_t *data)
|
||||
{
|
||||
if (offset % 4) { // we read only at 4-byte boundary
|
||||
return secfalse;
|
||||
}
|
||||
const uint32_t *flash = (const uint32_t *)flash_get_address(sector, offset, sizeof(data));
|
||||
if (!flash) {
|
||||
return secfalse;
|
||||
}
|
||||
data[0] = flash[0];
|
||||
return sectrue;
|
||||
}
|
||||
|
||||
secbool flash_otp_read(uint8_t block, uint8_t offset, uint8_t *data, uint8_t datalen)
|
||||
{
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
secbool flash_otp_write(uint8_t block, uint8_t offset, const uint8_t *data, uint8_t datalen)
|
||||
{
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
secbool flash_otp_lock(uint8_t block)
|
||||
{
|
||||
return secfalse;
|
||||
}
|
||||
|
||||
secbool flash_otp_is_locked(uint8_t block)
|
||||
{
|
||||
return secfalse;
|
||||
}
|
@ -22,7 +22,7 @@ async def request_passphrase(ctx):
|
||||
async def protect_by_passphrase(ctx):
|
||||
from apps.common import storage
|
||||
|
||||
if storage.is_protected_by_passphrase():
|
||||
if storage.has_passphrase():
|
||||
return await request_passphrase(ctx)
|
||||
else:
|
||||
return ''
|
||||
|
@ -1,37 +1,24 @@
|
||||
from trezor import ui, res
|
||||
from trezor import wire
|
||||
from trezor.utils import unimport
|
||||
from trezor import res
|
||||
from trezor import ui
|
||||
|
||||
if __debug__:
|
||||
matrix = None
|
||||
|
||||
DEFAULT_CANCEL = res.load(ui.ICON_CLEAR)
|
||||
DEFAULT_LOCK = res.load(ui.ICON_LOCK)
|
||||
class PinCancelled(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@ui.layout
|
||||
async def request_pin_on_display(ctx: wire.Context, code: int=None) -> str:
|
||||
from trezor.messages.ButtonRequest import ButtonRequest
|
||||
from trezor.messages.ButtonRequestType import ProtectCall
|
||||
from trezor.messages.FailureType import PinCancelled
|
||||
from trezor.messages.wire_types import ButtonAck
|
||||
async def request_pin(code: int = None) -> str:
|
||||
from trezor.ui.confirm import ConfirmDialog, CONFIRMED
|
||||
from trezor.ui.pin import PinMatrix
|
||||
|
||||
if __debug__:
|
||||
global matrix
|
||||
|
||||
_, label = _get_code_and_label(code)
|
||||
|
||||
await ctx.call(ButtonRequest(code=ProtectCall),
|
||||
ButtonAck)
|
||||
label = _get_label(code)
|
||||
|
||||
def onchange():
|
||||
c = dialog.cancel
|
||||
if matrix.pin:
|
||||
c.content = DEFAULT_CANCEL
|
||||
c.content = res.load(ui.ICON_CLEAR)
|
||||
else:
|
||||
c.content = DEFAULT_LOCK
|
||||
c.content = res.load(ui.ICON_LOCK)
|
||||
c.taint()
|
||||
c.render()
|
||||
|
||||
@ -44,94 +31,18 @@ async def request_pin_on_display(ctx: wire.Context, code: int=None) -> str:
|
||||
matrix.onchange()
|
||||
|
||||
while True:
|
||||
res = await dialog
|
||||
pin = matrix.pin
|
||||
result = await dialog
|
||||
|
||||
if res == CONFIRMED:
|
||||
matrix = None
|
||||
return pin
|
||||
elif res != CONFIRMED and pin:
|
||||
if result == CONFIRMED:
|
||||
return matrix.pin
|
||||
elif result != CONFIRMED and matrix.pin:
|
||||
matrix.change('')
|
||||
continue
|
||||
else:
|
||||
matrix = None
|
||||
raise wire.FailureError(PinCancelled, 'PIN cancelled')
|
||||
raise PinCancelled()
|
||||
|
||||
|
||||
@ui.layout
|
||||
@unimport
|
||||
async def request_pin_on_client(ctx: wire.Context, code: int=None) -> str:
|
||||
from trezor.messages.FailureType import PinCancelled
|
||||
from trezor.messages.PinMatrixRequest import PinMatrixRequest
|
||||
from trezor.messages.wire_types import PinMatrixAck, Cancel
|
||||
from trezor.ui.pin import PinMatrix
|
||||
|
||||
if __debug__:
|
||||
global matrix
|
||||
|
||||
code, label = _get_code_and_label(code)
|
||||
|
||||
ui.display.clear()
|
||||
matrix = PinMatrix(label)
|
||||
matrix.render()
|
||||
|
||||
ack = await ctx.call(PinMatrixRequest(type=code),
|
||||
PinMatrixAck, Cancel)
|
||||
digits = matrix.digits
|
||||
matrix = None
|
||||
|
||||
if ack.MESSAGE_WIRE_TYPE == Cancel:
|
||||
raise wire.FailureError(PinCancelled, 'PIN cancelled')
|
||||
return _decode_pin(ack.pin, digits)
|
||||
|
||||
|
||||
request_pin = request_pin_on_client
|
||||
|
||||
|
||||
@unimport
|
||||
async def request_pin_twice(ctx: wire.Context) -> str:
|
||||
from trezor.messages.FailureType import ActionCancelled
|
||||
from trezor.messages import PinMatrixRequestType
|
||||
|
||||
pin_first = await request_pin(ctx, PinMatrixRequestType.NewFirst)
|
||||
pin_again = await request_pin(ctx, PinMatrixRequestType.NewSecond)
|
||||
if pin_first != pin_again:
|
||||
# changed message due to consistency with T1 msgs
|
||||
raise wire.FailureError(ActionCancelled, 'PIN change failed')
|
||||
|
||||
return pin_first
|
||||
|
||||
|
||||
async def protect_by_pin_repeatedly(ctx: wire.Context, at_least_once: bool=False):
|
||||
from . import storage
|
||||
|
||||
locked = storage.is_locked() or at_least_once
|
||||
while locked:
|
||||
pin = await request_pin(ctx)
|
||||
locked = not storage.unlock(pin, _render_pin_failure)
|
||||
|
||||
|
||||
async def protect_by_pin_or_fail(ctx: wire.Context, at_least_once: bool=False):
|
||||
from trezor.messages.FailureType import PinInvalid
|
||||
from . import storage
|
||||
|
||||
locked = storage.is_locked() or at_least_once
|
||||
if locked:
|
||||
pin = await request_pin(ctx)
|
||||
if not storage.unlock(pin, _render_pin_failure):
|
||||
raise wire.FailureError(PinInvalid, 'PIN invalid')
|
||||
|
||||
|
||||
protect_by_pin = protect_by_pin_or_fail
|
||||
|
||||
|
||||
def _render_pin_failure(sleep_ms: int):
|
||||
ui.display.clear()
|
||||
ui.display.text_center(240, 240, 'Sleeping for %d seconds' % (sleep_ms / 1000),
|
||||
ui.BOLD, ui.RED, ui.BG)
|
||||
|
||||
|
||||
def _get_code_and_label(code: int):
|
||||
def _get_label(code: int):
|
||||
from trezor.messages import PinMatrixRequestType
|
||||
if code is None:
|
||||
code = PinMatrixRequestType.Current
|
||||
@ -141,8 +52,4 @@ def _get_code_and_label(code: int):
|
||||
label = 'Enter PIN again'
|
||||
else: # PinMatrixRequestType.Current
|
||||
label = 'Enter PIN'
|
||||
return code, label
|
||||
|
||||
|
||||
def _decode_pin(pin: str, secret: list) -> str:
|
||||
return ''.join([str(secret[int(d) - 1]) for d in pin])
|
||||
return label
|
||||
|
@ -21,14 +21,11 @@ async def get_seed(ctx: wire.Context) -> bytes:
|
||||
async def compute_seed(ctx: wire.Context) -> bytes:
|
||||
from trezor.messages.FailureType import ProcessError
|
||||
from .request_passphrase import protect_by_passphrase
|
||||
from .request_pin import protect_by_pin
|
||||
from . import storage
|
||||
|
||||
if not storage.is_initialized():
|
||||
raise wire.FailureError(ProcessError, 'Device is not initialized')
|
||||
|
||||
await protect_by_pin(ctx)
|
||||
|
||||
passphrase = await protect_by_passphrase(ctx)
|
||||
return bip39.seed(storage.get_mnemonic(), passphrase)
|
||||
|
||||
@ -37,8 +34,6 @@ def get_root_without_passphrase(curve_name=_DEFAULT_CURVE):
|
||||
from . import storage
|
||||
if not storage.is_initialized():
|
||||
raise Exception('Device is not initialized')
|
||||
if storage.is_locked():
|
||||
raise Exception('Unlock first')
|
||||
seed = bip39.seed(storage.get_mnemonic(), '')
|
||||
root = bip32.from_seed(seed, curve_name)
|
||||
return root
|
||||
|
@ -1,152 +1,62 @@
|
||||
from micropython import const
|
||||
|
||||
import ustruct
|
||||
import utime
|
||||
|
||||
from trezor import config
|
||||
from trezor import utils
|
||||
|
||||
_APP = const(1)
|
||||
_STORAGE_VERSION = b'\x01'
|
||||
|
||||
DEVICE_ID = const(0) # str
|
||||
VERSION = const(1) # varint
|
||||
MNEMONIC = const(2) # str
|
||||
LANGUAGE = const(3) # str
|
||||
LABEL = const(4) # str
|
||||
PIN = const(5) # bytes
|
||||
PIN_FAILS = const(6) # varint
|
||||
PASSPHRASE_PROTECTION = const(7) # varint
|
||||
|
||||
|
||||
# pin lock
|
||||
# ===
|
||||
|
||||
_locked = True
|
||||
|
||||
|
||||
def is_locked() -> bool:
|
||||
return is_protected_by_pin() and _locked
|
||||
|
||||
|
||||
def unlock(user_pin: str, failure_callback=None) -> bool:
|
||||
global _locked
|
||||
|
||||
if not is_protected_by_pin():
|
||||
return True
|
||||
|
||||
# increment the pin fail counter before checking the pin
|
||||
fails = bytes_to_int(config_get(PIN_FAILS)) + 1
|
||||
config_set_checked(PIN_FAILS, int_to_bytes(fails))
|
||||
|
||||
if const_equal(config_get(PIN), user_pin.encode()):
|
||||
# unlock and reset the counter
|
||||
_locked = False
|
||||
config_set(PIN_FAILS, int_to_bytes(0))
|
||||
return True
|
||||
|
||||
else:
|
||||
# lock, run the callback (ie for ui) and sleep for a quadratic delay
|
||||
_locked = True
|
||||
delay_ms = fails * fails * 1000
|
||||
try:
|
||||
if failure_callback:
|
||||
failure_callback(delay_ms)
|
||||
finally:
|
||||
utime.sleep_ms(delay_ms)
|
||||
return False
|
||||
|
||||
|
||||
def lock():
|
||||
global _locked
|
||||
_locked = True
|
||||
|
||||
|
||||
def const_equal(a: bytes, b: bytes) -> bool:
|
||||
return a == b # TODO: proper const equal
|
||||
|
||||
|
||||
# settings
|
||||
# ===
|
||||
_APP = const(0x0001) # app namespace
|
||||
_DEVICE_ID = const(0x0000) # bytes
|
||||
_VERSION = const(0x0001) # int
|
||||
_MNEMONIC = const(0x0002) # str
|
||||
_LANGUAGE = const(0x0003) # str
|
||||
_LABEL = const(0x0004) # str
|
||||
_USE_PASSPHRASE = const(0x0005) # 0x01 or empty
|
||||
|
||||
|
||||
def get_device_id() -> str:
|
||||
dev_id = config_get(DEVICE_ID).decode()
|
||||
dev_id = config.get(_APP, _DEVICE_ID).decode()
|
||||
if not dev_id:
|
||||
dev_id = new_device_id()
|
||||
config_set(DEVICE_ID, dev_id.encode())
|
||||
config.set(_APP, _DEVICE_ID, dev_id.encode())
|
||||
return dev_id
|
||||
|
||||
|
||||
def is_initialized() -> bool:
|
||||
return bool(config_get(VERSION))
|
||||
|
||||
|
||||
def is_protected_by_pin() -> bool:
|
||||
return bool(config_get(PIN))
|
||||
|
||||
|
||||
def is_protected_by_passphrase() -> bool:
|
||||
return bool(bytes_to_int(config_get(PASSPHRASE_PROTECTION)))
|
||||
|
||||
|
||||
def get_pin() -> str:
|
||||
return config_get(PIN).decode()
|
||||
return bool(config.get(_APP, _VERSION))
|
||||
|
||||
|
||||
def get_label() -> str:
|
||||
return config_get(LABEL).decode()
|
||||
|
||||
|
||||
def get_language() -> str:
|
||||
return config_get(LANGUAGE).decode() or _DEFAULT_LANGUAGE
|
||||
return config.get(_APP, _LABEL).decode()
|
||||
|
||||
|
||||
def get_mnemonic() -> str:
|
||||
utils.ensure(is_initialized())
|
||||
utils.ensure(not is_locked())
|
||||
|
||||
return config_get(MNEMONIC).decode()
|
||||
return config.get(_APP, _MNEMONIC).decode()
|
||||
|
||||
|
||||
# settings configuration
|
||||
# ===
|
||||
def has_passphrase() -> bool:
|
||||
return bool(config.get(_APP, _USE_PASSPHRASE))
|
||||
|
||||
|
||||
def load_mnemonic(mnemonic: str):
|
||||
utils.ensure(not is_initialized())
|
||||
|
||||
config_set(VERSION, int_to_bytes(1))
|
||||
config_set(MNEMONIC, mnemonic.encode())
|
||||
config.set(_APP, _VERSION, _STORAGE_VERSION)
|
||||
config.set(_APP, _MNEMONIC, mnemonic.encode())
|
||||
|
||||
|
||||
_ALLOWED_LANGUAGES = ('english')
|
||||
_DEFAULT_LANGUAGE = 'english'
|
||||
|
||||
|
||||
def load_settings(language: str=None,
|
||||
label: str=None,
|
||||
pin: str=None,
|
||||
passphrase_protection: bool=None):
|
||||
utils.ensure(is_initialized())
|
||||
utils.ensure(not is_locked())
|
||||
|
||||
if language is not None and language in _ALLOWED_LANGUAGES:
|
||||
if language is _DEFAULT_LANGUAGE:
|
||||
config_set(LANGUAGE, b'')
|
||||
else:
|
||||
config_set(LANGUAGE, language.encode())
|
||||
def load_settings(label: str = None, use_passphrase: bool = None):
|
||||
if label is not None:
|
||||
config_set(LABEL, label.encode())
|
||||
if pin is not None:
|
||||
config_set(PIN, pin.encode())
|
||||
if passphrase_protection is not None:
|
||||
config_set(PASSPHRASE_PROTECTION,
|
||||
int_to_bytes(passphrase_protection))
|
||||
config.set(_APP, _LABEL, label.encode())
|
||||
if use_passphrase is True:
|
||||
config.set(_APP, _USE_PASSPHRASE, b'\x01')
|
||||
if use_passphrase is False:
|
||||
config.set(_APP, _USE_PASSPHRASE, b'')
|
||||
|
||||
|
||||
def change_pin(pin: str, newpin: str):
|
||||
return config.change_pin(pin, newpin)
|
||||
|
||||
|
||||
def wipe():
|
||||
from . import cache
|
||||
lock()
|
||||
config.wipe()
|
||||
cache.clear()
|
||||
|
||||
@ -155,28 +65,3 @@ def new_device_id() -> str:
|
||||
from ubinascii import hexlify
|
||||
from trezor.crypto import random
|
||||
return hexlify(random.bytes(12)).decode('ascii').upper()
|
||||
|
||||
|
||||
def config_get(key: int) -> bytes:
|
||||
return config.get(_APP, key)
|
||||
|
||||
|
||||
def config_set(key: int, value: bytes):
|
||||
config.set(_APP, key, value)
|
||||
|
||||
|
||||
def config_set_checked(key, value: bytes):
|
||||
config_set(key, value)
|
||||
check = config_get(key)
|
||||
if check != value:
|
||||
utils.halt('config.set failed')
|
||||
|
||||
|
||||
# TODO: store ints as varints
|
||||
|
||||
def int_to_bytes(i: int) -> bytes:
|
||||
return ustruct.pack('>L', i) if i else bytes()
|
||||
|
||||
|
||||
def bytes_to_int(b: bytes) -> int:
|
||||
return ustruct.unpack('>L', b)[0] if b else 0
|
||||
|
@ -20,10 +20,10 @@ async def respond_Features(ctx, msg):
|
||||
|
||||
f.device_id = storage.get_device_id()
|
||||
f.label = storage.get_label()
|
||||
f.language = storage.get_language()
|
||||
f.initialized = storage.is_initialized()
|
||||
f.pin_protection = storage.is_protected_by_pin()
|
||||
f.passphrase_protection = storage.is_protected_by_passphrase()
|
||||
f.passphrase_protection = storage.has_passphrase()
|
||||
f.pin_protection = False
|
||||
f.language = 'english'
|
||||
|
||||
return f
|
||||
|
||||
@ -42,10 +42,6 @@ async def respond_Pong(ctx, msg):
|
||||
from trezor import ui
|
||||
await require_confirm(ctx, Text('Confirm', ui.ICON_RESET), ProtectCall)
|
||||
|
||||
if msg.pin_protection:
|
||||
from apps.common.request_pin import protect_by_pin
|
||||
await protect_by_pin(ctx)
|
||||
|
||||
if msg.passphrase_protection:
|
||||
from apps.common.request_passphrase import protect_by_passphrase
|
||||
await protect_by_passphrase(ctx)
|
||||
|
@ -8,11 +8,8 @@ async def layout_apply_settings(ctx, msg):
|
||||
from trezor.messages.FailureType import ProcessError
|
||||
from trezor.ui.text import Text
|
||||
from ..common.confirm import require_confirm
|
||||
from ..common.request_pin import protect_by_pin
|
||||
from ..common import storage
|
||||
|
||||
await protect_by_pin(ctx)
|
||||
|
||||
if msg.homescreen is not None:
|
||||
raise wire.FailureError(
|
||||
ProcessError, 'ApplySettings.homescreen is not supported')
|
||||
@ -42,7 +39,6 @@ async def layout_apply_settings(ctx, msg):
|
||||
'encryption?'))
|
||||
|
||||
storage.load_settings(label=msg.label,
|
||||
language=msg.language,
|
||||
passphrase_protection=msg.use_passphrase)
|
||||
use_passphrase=msg.use_passphrase)
|
||||
|
||||
return Success(message='Settings applied')
|
||||
|
@ -26,9 +26,9 @@ async def layout_load_device(ctx, msg):
|
||||
ui.NORMAL, 'Continue only if you', 'know what you are doing!'))
|
||||
|
||||
storage.load_mnemonic(msg.mnemonic)
|
||||
storage.load_settings(pin=msg.pin,
|
||||
passphrase_protection=msg.passphrase_protection,
|
||||
language=msg.language,
|
||||
storage.load_settings(use_passphrase=msg.passphrase_protection,
|
||||
label=msg.label)
|
||||
if msg.pin:
|
||||
storage.change_pin('', msg.pin)
|
||||
|
||||
return Success(message='Device loaded')
|
||||
|
27
src/boot.py
Normal file
27
src/boot.py
Normal file
@ -0,0 +1,27 @@
|
||||
from trezor import config
|
||||
from trezor import loop
|
||||
from trezor import ui
|
||||
|
||||
from apps.common.request_pin import request_pin
|
||||
|
||||
|
||||
async def unlock_layout():
|
||||
while True:
|
||||
if config.has_pin():
|
||||
pin = await request_pin()
|
||||
else:
|
||||
pin = ''
|
||||
if config.unlock(pin):
|
||||
return
|
||||
else:
|
||||
await unlock_failed()
|
||||
|
||||
|
||||
async def unlock_failed():
|
||||
pass
|
||||
|
||||
|
||||
config.init()
|
||||
ui.display.backlight(ui.BACKLIGHT_DIM)
|
||||
loop.schedule(unlock_layout())
|
||||
loop.run()
|
@ -1,12 +1,11 @@
|
||||
from trezor import config
|
||||
import boot
|
||||
|
||||
from trezor import io
|
||||
from trezor import log
|
||||
from trezor import loop
|
||||
from trezor import wire
|
||||
from trezor import workflow
|
||||
|
||||
config.init()
|
||||
|
||||
log.level = log.DEBUG
|
||||
|
||||
# initialize the USB stack
|
||||
@ -102,7 +101,6 @@ usb.add(usb_vcp)
|
||||
usb.add(usb_u2f)
|
||||
|
||||
# load applications
|
||||
from apps.common import storage
|
||||
if __debug__:
|
||||
from apps import debug
|
||||
from apps import homescreen
|
||||
|
@ -86,7 +86,7 @@ def run():
|
||||
|
||||
task_entry = [0, 0, 0] # deadline, task, value
|
||||
msg_entry = [0, 0] # iface | flags, value
|
||||
while True:
|
||||
while _queue or _paused:
|
||||
# compute the maximum amount of time we can wait for a message
|
||||
if _queue:
|
||||
delay = utime.ticks_diff(_queue.peektime(), utime.ticks_us())
|
||||
|
@ -4,6 +4,18 @@ from trezor.crypto import random
|
||||
|
||||
from trezor import config
|
||||
|
||||
PINAPP = 0x00
|
||||
PINKEY = 0x00
|
||||
|
||||
|
||||
def random_entry():
|
||||
while True:
|
||||
appid, key = random.uniform(256), random.uniform(256)
|
||||
if appid != PINAPP or key != PINKEY:
|
||||
break
|
||||
return appid, key
|
||||
|
||||
|
||||
class TestConfig(unittest.TestCase):
|
||||
|
||||
def test_init(self):
|
||||
@ -14,23 +26,59 @@ class TestConfig(unittest.TestCase):
|
||||
def test_wipe(self):
|
||||
config.init()
|
||||
config.wipe()
|
||||
config.set(0, 0, b'hello')
|
||||
self.assertEqual(config.unlock(''), True)
|
||||
config.set(0, 1, b'hello')
|
||||
config.set(1, 1, b'world')
|
||||
v0 = config.get(0, 0)
|
||||
v0 = config.get(0, 1)
|
||||
v1 = config.get(1, 1)
|
||||
self.assertEqual(v0, b'hello')
|
||||
self.assertEqual(v1, b'world')
|
||||
config.wipe()
|
||||
v0 = config.get(0, 0)
|
||||
v0 = config.get(0, 1)
|
||||
v1 = config.get(1, 1)
|
||||
self.assertEqual(v0, bytes())
|
||||
self.assertEqual(v1, bytes())
|
||||
|
||||
def test_lock(self):
|
||||
for _ in range(128):
|
||||
config.init()
|
||||
config.wipe()
|
||||
self.assertEqual(config.unlock(''), True)
|
||||
appid, key = random_entry()
|
||||
value = random.bytes(16)
|
||||
config.set(appid, key, value)
|
||||
config.init()
|
||||
self.assertEqual(config.get(appid, key), bytes())
|
||||
with self.assertRaises(RuntimeError):
|
||||
config.set(appid, key, bytes())
|
||||
config.init()
|
||||
config.wipe()
|
||||
self.assertEqual(config.change_pin('', 'xxx'), False)
|
||||
|
||||
def test_change_pin(self):
|
||||
config.init()
|
||||
config.wipe()
|
||||
self.assertEqual(config.unlock(''), True)
|
||||
with self.assertRaises(RuntimeError):
|
||||
config.set(PINAPP, PINKEY, 'xxx')
|
||||
self.assertEqual(config.change_pin('xxx', 'yyy'), False)
|
||||
self.assertEqual(config.change_pin('', 'xxx'), True)
|
||||
self.assertEqual(config.get(PINAPP, PINKEY), bytes())
|
||||
config.set(1, 1, b'value')
|
||||
config.init()
|
||||
self.assertEqual(config.unlock('xxx'), True)
|
||||
config.change_pin('xxx', '')
|
||||
config.init()
|
||||
self.assertEqual(config.unlock('xxx'), False)
|
||||
self.assertEqual(config.unlock(''), True)
|
||||
self.assertEqual(config.get(1, 1), b'value')
|
||||
|
||||
def test_set_get(self):
|
||||
config.init()
|
||||
config.wipe()
|
||||
for _ in range(64):
|
||||
appid, key = random.uniform(256), random.uniform(256)
|
||||
self.assertEqual(config.unlock(''), True)
|
||||
for _ in range(32):
|
||||
appid, key = random_entry()
|
||||
value = random.bytes(128)
|
||||
config.set(appid, key, value)
|
||||
value2 = config.get(appid, key)
|
||||
@ -39,8 +87,9 @@ class TestConfig(unittest.TestCase):
|
||||
def test_get_default(self):
|
||||
config.init()
|
||||
config.wipe()
|
||||
for _ in range(64):
|
||||
appid, key = random.uniform(256), random.uniform(256)
|
||||
self.assertEqual(config.unlock(''), True)
|
||||
for _ in range(128):
|
||||
appid, key = random_entry()
|
||||
value = config.get(appid, key)
|
||||
self.assertEqual(value, bytes())
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user