diff --git a/SConscript.firmware b/SConscript.firmware index 1bcfbfe72..b742700ee 100644 --- a/SConscript.firmware +++ b/SConscript.firmware @@ -11,6 +11,7 @@ SOURCE_MOD = [] SOURCE_MOD += [ 'embed/extmod/modtrezorconfig/modtrezorconfig.c', 'embed/extmod/modtrezorconfig/norcow.c', + 'embed/extmod/modtrezorconfig/storage.c', ] # modtrezorcrypto diff --git a/SConscript.unix b/SConscript.unix index 8b215430e..f64f42e78 100644 --- a/SConscript.unix +++ b/SConscript.unix @@ -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'] diff --git a/embed/extmod/modtrezorconfig/modtrezorconfig.c b/embed/extmod/modtrezorconfig/modtrezorconfig.c index 9244918ab..5103ff99d 100644 --- a/embed/extmod/modtrezorconfig/modtrezorconfig.c +++ b/embed/extmod/modtrezorconfig/modtrezorconfig.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 -#include #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) }, diff --git a/embed/extmod/modtrezorconfig/norcow.c b/embed/extmod/modtrezorconfig/norcow.c deleted file mode 120000 index 0c15ca2f9..000000000 --- a/embed/extmod/modtrezorconfig/norcow.c +++ /dev/null @@ -1 +0,0 @@ -../../../vendor/norcow/norcow.c \ No newline at end of file diff --git a/embed/extmod/modtrezorconfig/norcow.c b/embed/extmod/modtrezorconfig/norcow.c new file mode 100644 index 000000000..b8917b308 --- /dev/null +++ b/embed/extmod/modtrezorconfig/norcow.c @@ -0,0 +1,262 @@ +#include + +#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; +} diff --git a/embed/extmod/modtrezorconfig/norcow.h b/embed/extmod/modtrezorconfig/norcow.h deleted file mode 120000 index f33ecb157..000000000 --- a/embed/extmod/modtrezorconfig/norcow.h +++ /dev/null @@ -1 +0,0 @@ -../../../vendor/norcow/norcow.h \ No newline at end of file diff --git a/embed/extmod/modtrezorconfig/norcow.h b/embed/extmod/modtrezorconfig/norcow.h new file mode 100644 index 000000000..a96816001 --- /dev/null +++ b/embed/extmod/modtrezorconfig/norcow.h @@ -0,0 +1,34 @@ +#ifndef __NORCOW_H__ +#define __NORCOW_H__ + +#include +#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 diff --git a/embed/extmod/modtrezorconfig/norcow_config.h b/embed/extmod/modtrezorconfig/norcow_config.h deleted file mode 100644 index dfa38e0dc..000000000 --- a/embed/extmod/modtrezorconfig/norcow_config.h +++ /dev/null @@ -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 diff --git a/embed/extmod/modtrezorconfig/storage.c b/embed/extmod/modtrezorconfig/storage.c new file mode 100644 index 000000000..39b954899 --- /dev/null +++ b/embed/extmod/modtrezorconfig/storage.c @@ -0,0 +1,215 @@ +/* + * Copyright (c) Pavol Rusnak, Jan Pochyla, SatoshiLabs + * + * Licensed under TREZOR License + * see LICENSE file for details + */ + +#include + +#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(); +} diff --git a/embed/extmod/modtrezorconfig/storage.h b/embed/extmod/modtrezorconfig/storage.h new file mode 100644 index 000000000..403447bff --- /dev/null +++ b/embed/extmod/modtrezorconfig/storage.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) Pavol Rusnak, Jan Pochyla, SatoshiLabs + * + * Licensed under TREZOR License + * see LICENSE file for details + */ + +#include +#include +#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); diff --git a/embed/extmod/modtrezorio/modtrezorio-flash.h b/embed/extmod/modtrezorio/modtrezorio-flash.h index 2003f450e..31c544957 100644 --- a/embed/extmod/modtrezorio/modtrezorio-flash.h +++ b/embed/extmod/modtrezorio/modtrezorio-flash.h @@ -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: /// ''' diff --git a/embed/extmod/modtrezorio/unix-msg-mock.h b/embed/extmod/modtrezorio/unix-msg-mock.h index 31adc5b66..5bf7a38fe 100644 --- a/embed/extmod/modtrezorio/unix-msg-mock.h +++ b/embed/extmod/modtrezorio/unix-msg-mock.h @@ -13,10 +13,13 @@ #include #include -#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 diff --git a/embed/trezorhal/flash.c b/embed/trezorhal/flash.c index b425c60a0..d13ea3ae3 100644 --- a/embed/trezorhal/flash.c +++ b/embed/trezorhal/flash.c @@ -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 + #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) diff --git a/embed/trezorhal/flash.h b/embed/trezorhal/flash.h index f4a484d1e..e4b1b766c 100644 --- a/embed/trezorhal/flash.h +++ b/embed/trezorhal/flash.h @@ -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 diff --git a/embed/trezorhal/image.c b/embed/trezorhal/image.c index d964fb2dd..39832b17d 100644 --- a/embed/trezorhal/image.c +++ b/embed/trezorhal/image.c @@ -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; } diff --git a/embed/unix/common.c b/embed/unix/common.c index d40858102..1d848b2da 100644 --- a/embed/unix/common.c +++ b/embed/unix/common.c @@ -31,3 +31,8 @@ void hal_delay(uint32_t ms) { usleep(1000 * ms); } + +void shutdown(void) +{ + exit(1); +} diff --git a/embed/unix/common.h b/embed/unix/common.h index fde45ec82..d1daf8a1d 100644 --- a/embed/unix/common.h +++ b/embed/unix/common.h @@ -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 diff --git a/embed/unix/flash.c b/embed/unix/flash.c new file mode 100644 index 000000000..1a9a74917 --- /dev/null +++ b/embed/unix/flash.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) Jan Pochyla, SatoshiLabs + * + * Licensed under TREZOR License + * see LICENSE file for details + */ + +#include +#include + +#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; +} diff --git a/src/apps/common/request_passphrase.py b/src/apps/common/request_passphrase.py index 6aacf7abb..888f89d06 100644 --- a/src/apps/common/request_passphrase.py +++ b/src/apps/common/request_passphrase.py @@ -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 '' diff --git a/src/apps/common/request_pin.py b/src/apps/common/request_pin.py index 801cbb664..52672138b 100644 --- a/src/apps/common/request_pin.py +++ b/src/apps/common/request_pin.py @@ -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') - - -@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 + raise PinCancelled() - 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 diff --git a/src/apps/common/seed.py b/src/apps/common/seed.py index af8a15ddd..8acf953e1 100644 --- a/src/apps/common/seed.py +++ b/src/apps/common/seed.py @@ -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 diff --git a/src/apps/common/storage.py b/src/apps/common/storage.py index cc911306e..2e0836b5a 100644 --- a/src/apps/common/storage.py +++ b/src/apps/common/storage.py @@ -1,152 +1,62 @@ from micropython import const -import ustruct -import utime - from trezor import config -from trezor import utils - -_APP = const(1) - -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)) +_STORAGE_VERSION = b'\x01' - 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(_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') -_DEFAULT_LANGUAGE = 'english' +def load_settings(label: str = None, use_passphrase: bool = None): + if label is not None: + 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 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: - 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)) +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 diff --git a/src/apps/homescreen/__init__.py b/src/apps/homescreen/__init__.py index 98a8247b6..29bf281a8 100644 --- a/src/apps/homescreen/__init__.py +++ b/src/apps/homescreen/__init__.py @@ -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) diff --git a/src/apps/management/apply_settings.py b/src/apps/management/apply_settings.py index 815f10592..8718eeaf0 100644 --- a/src/apps/management/apply_settings.py +++ b/src/apps/management/apply_settings.py @@ -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') diff --git a/src/apps/management/load_device.py b/src/apps/management/load_device.py index 89c850cca..e851d7be4 100644 --- a/src/apps/management/load_device.py +++ b/src/apps/management/load_device.py @@ -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') diff --git a/src/boot.py b/src/boot.py new file mode 100644 index 000000000..d3afefc17 --- /dev/null +++ b/src/boot.py @@ -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() diff --git a/src/main.py b/src/main.py index c9dcc8549..b3ebac8aa 100644 --- a/src/main.py +++ b/src/main.py @@ -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 diff --git a/src/trezor/loop.py b/src/trezor/loop.py index 714e018aa..83e339364 100644 --- a/src/trezor/loop.py +++ b/src/trezor/loop.py @@ -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()) diff --git a/tests/test_trezor.config.py b/tests/test_trezor.config.py index 57639e634..b47523d4d 100644 --- a/tests/test_trezor.config.py +++ b/tests/test_trezor.config.py @@ -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())