1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-22 23:48:12 +00:00

Merge branch 'pin_fails'

This commit is contained in:
Pavol Rusnak 2017-12-07 15:29:47 +01:00
commit f88080b904
No known key found for this signature in database
GPG Key ID: 91F3B339B9A02A3D
27 changed files with 978 additions and 345 deletions

View File

@ -11,6 +11,7 @@ SOURCE_MOD = []
SOURCE_MOD += [ SOURCE_MOD += [
'embed/extmod/modtrezorconfig/modtrezorconfig.c', 'embed/extmod/modtrezorconfig/modtrezorconfig.c',
'embed/extmod/modtrezorconfig/norcow.c', 'embed/extmod/modtrezorconfig/norcow.c',
'embed/extmod/modtrezorconfig/storage.c',
] ]
# modtrezorcrypto # modtrezorcrypto

View File

@ -12,6 +12,7 @@ LIBS_MOD = []
SOURCE_MOD += [ SOURCE_MOD += [
'embed/extmod/modtrezorconfig/modtrezorconfig.c', 'embed/extmod/modtrezorconfig/modtrezorconfig.c',
'embed/extmod/modtrezorconfig/norcow.c', 'embed/extmod/modtrezorconfig/norcow.c',
'embed/extmod/modtrezorconfig/storage.c',
] ]
# modtrezorcrypto # modtrezorcrypto
@ -215,6 +216,7 @@ SOURCE_UNIX = [
'vendor/micropython/ports/unix/alloc.c', 'vendor/micropython/ports/unix/alloc.c',
'embed/unix/common.c', 'embed/unix/common.c',
'embed/unix/touch.c', 'embed/unix/touch.c',
'embed/unix/flash.c',
] ]
SOURCE_EMIT_NATIVE = ['vendor/micropython/py/emitnative.c'] SOURCE_EMIT_NATIVE = ['vendor/micropython/py/emitnative.c']

View File

@ -1,54 +1,89 @@
/* /*
* Copyright (c) Pavol Rusnak, SatoshiLabs * Copyright (c) Pavol Rusnak, Jan Pochyla, SatoshiLabs
* *
* Licensed under TREZOR License * Licensed under TREZOR License
* see LICENSE file for details * see LICENSE file for details
*/ */
#include "py/runtime.h" #include "py/runtime.h"
#include "py/mphal.h"
#include "py/objstr.h"
#if MICROPY_PY_TREZORCONFIG #if MICROPY_PY_TREZORCONFIG
#include <stdint.h>
#include <string.h>
#include "norcow.h" #include "norcow.h"
#include "storage.h"
static bool initialized = false;
/// def init() -> None: /// 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) { STATIC mp_obj_t mod_trezorconfig_init(void) {
bool r = norcow_init(); if (sectrue != storage_init()) {
if (!r) {
mp_raise_msg(&mp_type_RuntimeError, "Could not initialize config module"); mp_raise_msg(&mp_type_RuntimeError, "Could not initialize config module");
} }
initialized = true;
return mp_const_none; return mp_const_none;
} }
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorconfig_init_obj, mod_trezorconfig_init); 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: /// def get(app: int, key: int) -> bytes:
/// ''' /// '''
/// Gets a value of given key for given app (or empty bytes if not set). /// 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) { 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 a = mp_obj_get_int(app);
uint8_t k = mp_obj_get_int(key); 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; const void *val;
bool r = norcow_get(appkey, &val, &len); if (sectrue != storage_get(appkey, &val, &len) || len == 0) {
if (!r || len == 0) {
return mp_const_empty_bytes; return mp_const_empty_bytes;
} }
vstr_t vstr; return mp_obj_new_str_of_type(&mp_type_bytes, val, len);
vstr_init_len(&vstr, len);
memcpy(vstr.buf, val, len);
return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr);
} }
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorconfig_get_obj, mod_trezorconfig_get); 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. /// 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) { 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 a = mp_obj_get_int(app);
uint8_t k = mp_obj_get_int(key); uint8_t k = mp_obj_get_int(key);
uint16_t appkey = a << 8 | k; uint16_t appkey = a << 8 | k;
mp_buffer_info_t v; mp_buffer_info_t v;
mp_get_buffer_raise(value, &v, MP_BUFFER_READ); mp_get_buffer_raise(value, &v, MP_BUFFER_READ);
bool r = norcow_set(appkey, v.buf, v.len); if (sectrue != storage_set(appkey, v.buf, v.len)) {
if (!r) {
mp_raise_msg(&mp_type_RuntimeError, "Could not save value"); mp_raise_msg(&mp_type_RuntimeError, "Could not save value");
} }
return mp_const_none; 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! /// Erases the whole config. Use with caution!
/// ''' /// '''
STATIC mp_obj_t mod_trezorconfig_wipe(void) { STATIC mp_obj_t mod_trezorconfig_wipe(void) {
if (!initialized) { if (sectrue != storage_wipe()) {
mp_raise_msg(&mp_type_RuntimeError, "Config module not initialized");
}
bool r = norcow_wipe();
if (!r) {
mp_raise_msg(&mp_type_RuntimeError, "Could not wipe storage"); mp_raise_msg(&mp_type_RuntimeError, "Could not wipe storage");
} }
return mp_const_none; 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[] = { 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___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_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_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_set), MP_ROM_PTR(&mod_trezorconfig_set_obj) },
{ MP_ROM_QSTR(MP_QSTR_wipe), MP_ROM_PTR(&mod_trezorconfig_wipe_obj) }, { MP_ROM_QSTR(MP_QSTR_wipe), MP_ROM_PTR(&mod_trezorconfig_wipe_obj) },

View File

@ -1 +0,0 @@
../../../vendor/norcow/norcow.c

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

View File

@ -1 +0,0 @@
../../../vendor/norcow/norcow.h

View 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

View File

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

View 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();
}

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

View File

@ -5,13 +5,7 @@
* see LICENSE file for details * see LICENSE file for details
*/ */
#if defined TREZOR_STM32 #include "../../trezorhal/flash.h"
#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
/// class FlashOTP: /// class FlashOTP:
/// ''' /// '''

View File

@ -13,10 +13,13 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "../../unix/common.h"
#include "../../trezorhal/usb.h" #include "../../trezorhal/usb.h"
#include "../../trezorhal/touch.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_IFACE 0
#define TREZOR_UDP_PORT 21324 #define TREZOR_UDP_PORT 21324

View File

@ -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 STM32_HAL_H
#include <string.h> #include <string.h>
#include "flash.h" #include "flash.h"
// see docs/memory.md for more information // 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 [ 0] = 0x08000000, // - 0x08003FFF | 16 KiB
[ 1] = 0x08004000, // - 0x08007FFF | 16 KiB [ 1] = 0x08004000, // - 0x08007FFF | 16 KiB
[ 2] = 0x08008000, // - 0x0800BFFF | 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 [24] = 0x08200000, // last element - not a valid sector
}; };
secbool flash_init(void)
{
return sectrue;
}
secbool flash_unlock(void) secbool flash_unlock(void)
{ {
HAL_FLASH_Unlock(); HAL_FLASH_Unlock();
@ -46,6 +59,19 @@ secbool flash_lock(void)
return sectrue; 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)) secbool flash_erase_sectors(const uint8_t *sectors, int len, void (*progress)(int pos, int len))
{ {
if (sectrue != flash_unlock()) { if (sectrue != flash_unlock()) {
@ -66,7 +92,7 @@ secbool flash_erase_sectors(const uint8_t *sectors, int len, void (*progress)(in
return secfalse; return secfalse;
} }
// check whether the sector was really deleted (contains only 0xFF) // 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) { for (uint32_t addr = addr_start; addr < addr_end; addr += 4) {
if (*((const uint32_t *)addr) != 0xFFFFFFFF) { if (*((const uint32_t *)addr) != 0xFFFFFFFF) {
flash_lock(); 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)); 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 #define FLASH_OTP_LOCK_BASE 0x1FFF7A00U
secbool flash_otp_read(uint8_t block, uint8_t offset, uint8_t *data, uint8_t datalen) secbool flash_otp_read(uint8_t block, uint8_t offset, uint8_t *data, uint8_t datalen)

View File

@ -43,14 +43,19 @@
// note: FLASH_SR_RDERR is STM32F42xxx and STM32F43xxx specific (STM32F427) (reference RM0090 section 3.7.5) // 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) #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_unlock(void);
secbool flash_lock(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_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_byte(uint32_t address, uint8_t data);
secbool flash_write_word(uint32_t address, uint32_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_NUM_BLOCKS 16
#define FLASH_OTP_BLOCK_SIZE 32 #define FLASH_OTP_BLOCK_SIZE 32

View File

@ -144,7 +144,10 @@ secbool check_image_contents(const image_header * const hdr, uint32_t firstskip,
if (0 == sectors || blocks < 1) { if (0 == sectors || blocks < 1) {
return secfalse; 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; int remaining = hdr->codelen;
if (sectrue != check_single_hash(hdr->hashes, data, MIN(remaining, IMAGE_CHUNK_SIZE - firstskip))) { if (sectrue != check_single_hash(hdr->hashes, data, MIN(remaining, IMAGE_CHUNK_SIZE - firstskip))) {
return secfalse; return secfalse;
@ -155,7 +158,10 @@ secbool check_image_contents(const image_header * const hdr, uint32_t firstskip,
if (block >= blocks) { if (block >= blocks) {
return secfalse; 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))) { if (sectrue != check_single_hash(hdr->hashes + block * 32, data, MIN(remaining, IMAGE_CHUNK_SIZE))) {
return secfalse; return secfalse;
} }

View File

@ -31,3 +31,8 @@ void hal_delay(uint32_t ms)
{ {
usleep(1000 * ms); usleep(1000 * ms);
} }
void shutdown(void)
{
exit(1);
}

View File

@ -9,4 +9,6 @@ void __attribute__((noreturn)) __fatal_error(const char *expr, const char *msg,
void hal_delay(uint32_t ms); void hal_delay(uint32_t ms);
void shutdown(void);
#endif #endif

175
embed/unix/flash.c Normal file
View 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;
}

View File

@ -22,7 +22,7 @@ async def request_passphrase(ctx):
async def protect_by_passphrase(ctx): async def protect_by_passphrase(ctx):
from apps.common import storage from apps.common import storage
if storage.is_protected_by_passphrase(): if storage.has_passphrase():
return await request_passphrase(ctx) return await request_passphrase(ctx)
else: else:
return '' return ''

View File

@ -1,37 +1,24 @@
from trezor import ui, res from trezor import res
from trezor import wire from trezor import ui
from trezor.utils import unimport
if __debug__:
matrix = None
DEFAULT_CANCEL = res.load(ui.ICON_CLEAR) class PinCancelled(Exception):
DEFAULT_LOCK = res.load(ui.ICON_LOCK) pass
@ui.layout @ui.layout
async def request_pin_on_display(ctx: wire.Context, code: int=None) -> str: async def request_pin(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
from trezor.ui.confirm import ConfirmDialog, CONFIRMED from trezor.ui.confirm import ConfirmDialog, CONFIRMED
from trezor.ui.pin import PinMatrix from trezor.ui.pin import PinMatrix
if __debug__: label = _get_label(code)
global matrix
_, label = _get_code_and_label(code)
await ctx.call(ButtonRequest(code=ProtectCall),
ButtonAck)
def onchange(): def onchange():
c = dialog.cancel c = dialog.cancel
if matrix.pin: if matrix.pin:
c.content = DEFAULT_CANCEL c.content = res.load(ui.ICON_CLEAR)
else: else:
c.content = DEFAULT_LOCK c.content = res.load(ui.ICON_LOCK)
c.taint() c.taint()
c.render() c.render()
@ -44,94 +31,18 @@ async def request_pin_on_display(ctx: wire.Context, code: int=None) -> str:
matrix.onchange() matrix.onchange()
while True: while True:
res = await dialog result = await dialog
pin = matrix.pin
if res == CONFIRMED: if result == CONFIRMED:
matrix = None return matrix.pin
return pin elif result != CONFIRMED and matrix.pin:
elif res != CONFIRMED and pin:
matrix.change('') matrix.change('')
continue continue
else: else:
matrix = None raise PinCancelled()
raise wire.FailureError(PinCancelled, 'PIN cancelled')
@ui.layout def _get_label(code: int):
@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):
from trezor.messages import PinMatrixRequestType from trezor.messages import PinMatrixRequestType
if code is None: if code is None:
code = PinMatrixRequestType.Current code = PinMatrixRequestType.Current
@ -141,8 +52,4 @@ def _get_code_and_label(code: int):
label = 'Enter PIN again' label = 'Enter PIN again'
else: # PinMatrixRequestType.Current else: # PinMatrixRequestType.Current
label = 'Enter PIN' label = 'Enter PIN'
return code, label return label
def _decode_pin(pin: str, secret: list) -> str:
return ''.join([str(secret[int(d) - 1]) for d in pin])

View File

@ -21,14 +21,11 @@ async def get_seed(ctx: wire.Context) -> bytes:
async def compute_seed(ctx: wire.Context) -> bytes: async def compute_seed(ctx: wire.Context) -> bytes:
from trezor.messages.FailureType import ProcessError from trezor.messages.FailureType import ProcessError
from .request_passphrase import protect_by_passphrase from .request_passphrase import protect_by_passphrase
from .request_pin import protect_by_pin
from . import storage from . import storage
if not storage.is_initialized(): if not storage.is_initialized():
raise wire.FailureError(ProcessError, 'Device is not initialized') raise wire.FailureError(ProcessError, 'Device is not initialized')
await protect_by_pin(ctx)
passphrase = await protect_by_passphrase(ctx) passphrase = await protect_by_passphrase(ctx)
return bip39.seed(storage.get_mnemonic(), passphrase) return bip39.seed(storage.get_mnemonic(), passphrase)
@ -37,8 +34,6 @@ def get_root_without_passphrase(curve_name=_DEFAULT_CURVE):
from . import storage from . import storage
if not storage.is_initialized(): if not storage.is_initialized():
raise Exception('Device is not initialized') raise Exception('Device is not initialized')
if storage.is_locked():
raise Exception('Unlock first')
seed = bip39.seed(storage.get_mnemonic(), '') seed = bip39.seed(storage.get_mnemonic(), '')
root = bip32.from_seed(seed, curve_name) root = bip32.from_seed(seed, curve_name)
return root return root

View File

@ -1,152 +1,62 @@
from micropython import const from micropython import const
import ustruct
import utime
from trezor import config from trezor import config
from trezor import utils
_APP = const(1) _STORAGE_VERSION = b'\x01'
DEVICE_ID = const(0) # str _APP = const(0x0001) # app namespace
VERSION = const(1) # varint _DEVICE_ID = const(0x0000) # bytes
MNEMONIC = const(2) # str _VERSION = const(0x0001) # int
LANGUAGE = const(3) # str _MNEMONIC = const(0x0002) # str
LABEL = const(4) # str _LANGUAGE = const(0x0003) # str
PIN = const(5) # bytes _LABEL = const(0x0004) # str
PIN_FAILS = const(6) # varint _USE_PASSPHRASE = const(0x0005) # 0x01 or empty
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
# ===
def get_device_id() -> str: def get_device_id() -> str:
dev_id = config_get(DEVICE_ID).decode() dev_id = config.get(_APP, _DEVICE_ID).decode()
if not dev_id: if not dev_id:
dev_id = new_device_id() dev_id = new_device_id()
config_set(DEVICE_ID, dev_id.encode()) config.set(_APP, _DEVICE_ID, dev_id.encode())
return dev_id return dev_id
def is_initialized() -> bool: def is_initialized() -> bool:
return bool(config_get(VERSION)) return bool(config.get(_APP, _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()
def get_label() -> str: def get_label() -> str:
return config_get(LABEL).decode() return config.get(_APP, _LABEL).decode()
def get_language() -> str:
return config_get(LANGUAGE).decode() or _DEFAULT_LANGUAGE
def get_mnemonic() -> str: def get_mnemonic() -> str:
utils.ensure(is_initialized()) return config.get(_APP, _MNEMONIC).decode()
utils.ensure(not is_locked())
return config_get(MNEMONIC).decode()
# settings configuration def has_passphrase() -> bool:
# === return bool(config.get(_APP, _USE_PASSPHRASE))
def load_mnemonic(mnemonic: str): def load_mnemonic(mnemonic: str):
utils.ensure(not is_initialized()) config.set(_APP, _VERSION, _STORAGE_VERSION)
config.set(_APP, _MNEMONIC, mnemonic.encode())
config_set(VERSION, int_to_bytes(1))
config_set(MNEMONIC, mnemonic.encode())
_ALLOWED_LANGUAGES = ('english') def load_settings(label: str = None, use_passphrase: bool = None):
_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())
if label is not None: if label is not None:
config_set(LABEL, label.encode()) config.set(_APP, _LABEL, label.encode())
if pin is not None: if use_passphrase is True:
config_set(PIN, pin.encode()) config.set(_APP, _USE_PASSPHRASE, b'\x01')
if passphrase_protection is not None: if use_passphrase is False:
config_set(PASSPHRASE_PROTECTION, config.set(_APP, _USE_PASSPHRASE, b'')
int_to_bytes(passphrase_protection))
def change_pin(pin: str, newpin: str):
return config.change_pin(pin, newpin)
def wipe(): def wipe():
from . import cache from . import cache
lock()
config.wipe() config.wipe()
cache.clear() cache.clear()
@ -155,28 +65,3 @@ def new_device_id() -> str:
from ubinascii import hexlify from ubinascii import hexlify
from trezor.crypto import random from trezor.crypto import random
return hexlify(random.bytes(12)).decode('ascii').upper() 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

View File

@ -20,10 +20,10 @@ async def respond_Features(ctx, msg):
f.device_id = storage.get_device_id() f.device_id = storage.get_device_id()
f.label = storage.get_label() f.label = storage.get_label()
f.language = storage.get_language()
f.initialized = storage.is_initialized() f.initialized = storage.is_initialized()
f.pin_protection = storage.is_protected_by_pin() f.passphrase_protection = storage.has_passphrase()
f.passphrase_protection = storage.is_protected_by_passphrase() f.pin_protection = False
f.language = 'english'
return f return f
@ -42,10 +42,6 @@ async def respond_Pong(ctx, msg):
from trezor import ui from trezor import ui
await require_confirm(ctx, Text('Confirm', ui.ICON_RESET), ProtectCall) 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: if msg.passphrase_protection:
from apps.common.request_passphrase import protect_by_passphrase from apps.common.request_passphrase import protect_by_passphrase
await protect_by_passphrase(ctx) await protect_by_passphrase(ctx)

View File

@ -8,11 +8,8 @@ async def layout_apply_settings(ctx, msg):
from trezor.messages.FailureType import ProcessError from trezor.messages.FailureType import ProcessError
from trezor.ui.text import Text from trezor.ui.text import Text
from ..common.confirm import require_confirm from ..common.confirm import require_confirm
from ..common.request_pin import protect_by_pin
from ..common import storage from ..common import storage
await protect_by_pin(ctx)
if msg.homescreen is not None: if msg.homescreen is not None:
raise wire.FailureError( raise wire.FailureError(
ProcessError, 'ApplySettings.homescreen is not supported') ProcessError, 'ApplySettings.homescreen is not supported')
@ -42,7 +39,6 @@ async def layout_apply_settings(ctx, msg):
'encryption?')) 'encryption?'))
storage.load_settings(label=msg.label, storage.load_settings(label=msg.label,
language=msg.language, use_passphrase=msg.use_passphrase)
passphrase_protection=msg.use_passphrase)
return Success(message='Settings applied') return Success(message='Settings applied')

View File

@ -26,9 +26,9 @@ async def layout_load_device(ctx, msg):
ui.NORMAL, 'Continue only if you', 'know what you are doing!')) ui.NORMAL, 'Continue only if you', 'know what you are doing!'))
storage.load_mnemonic(msg.mnemonic) storage.load_mnemonic(msg.mnemonic)
storage.load_settings(pin=msg.pin, storage.load_settings(use_passphrase=msg.passphrase_protection,
passphrase_protection=msg.passphrase_protection,
language=msg.language,
label=msg.label) label=msg.label)
if msg.pin:
storage.change_pin('', msg.pin)
return Success(message='Device loaded') return Success(message='Device loaded')

27
src/boot.py Normal file
View 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()

View File

@ -1,12 +1,11 @@
from trezor import config import boot
from trezor import io from trezor import io
from trezor import log from trezor import log
from trezor import loop from trezor import loop
from trezor import wire from trezor import wire
from trezor import workflow from trezor import workflow
config.init()
log.level = log.DEBUG log.level = log.DEBUG
# initialize the USB stack # initialize the USB stack
@ -102,7 +101,6 @@ usb.add(usb_vcp)
usb.add(usb_u2f) usb.add(usb_u2f)
# load applications # load applications
from apps.common import storage
if __debug__: if __debug__:
from apps import debug from apps import debug
from apps import homescreen from apps import homescreen

View File

@ -86,7 +86,7 @@ def run():
task_entry = [0, 0, 0] # deadline, task, value task_entry = [0, 0, 0] # deadline, task, value
msg_entry = [0, 0] # iface | flags, 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 # compute the maximum amount of time we can wait for a message
if _queue: if _queue:
delay = utime.ticks_diff(_queue.peektime(), utime.ticks_us()) delay = utime.ticks_diff(_queue.peektime(), utime.ticks_us())

View File

@ -4,6 +4,18 @@ from trezor.crypto import random
from trezor import config 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): class TestConfig(unittest.TestCase):
def test_init(self): def test_init(self):
@ -14,23 +26,59 @@ class TestConfig(unittest.TestCase):
def test_wipe(self): def test_wipe(self):
config.init() config.init()
config.wipe() 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') config.set(1, 1, b'world')
v0 = config.get(0, 0) v0 = config.get(0, 1)
v1 = config.get(1, 1) v1 = config.get(1, 1)
self.assertEqual(v0, b'hello') self.assertEqual(v0, b'hello')
self.assertEqual(v1, b'world') self.assertEqual(v1, b'world')
config.wipe() config.wipe()
v0 = config.get(0, 0) v0 = config.get(0, 1)
v1 = config.get(1, 1) v1 = config.get(1, 1)
self.assertEqual(v0, bytes()) self.assertEqual(v0, bytes())
self.assertEqual(v1, 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): def test_set_get(self):
config.init() config.init()
config.wipe() config.wipe()
for _ in range(64): self.assertEqual(config.unlock(''), True)
appid, key = random.uniform(256), random.uniform(256) for _ in range(32):
appid, key = random_entry()
value = random.bytes(128) value = random.bytes(128)
config.set(appid, key, value) config.set(appid, key, value)
value2 = config.get(appid, key) value2 = config.get(appid, key)
@ -39,8 +87,9 @@ class TestConfig(unittest.TestCase):
def test_get_default(self): def test_get_default(self):
config.init() config.init()
config.wipe() config.wipe()
for _ in range(64): self.assertEqual(config.unlock(''), True)
appid, key = random.uniform(256), random.uniform(256) for _ in range(128):
appid, key = random_entry()
value = config.get(appid, key) value = config.get(appid, key)
self.assertEqual(value, bytes()) self.assertEqual(value, bytes())