diff --git a/firmware/storage.c b/firmware/storage.c index 8abff13b1f..248e19bb10 100644 --- a/firmware/storage.c +++ b/firmware/storage.c @@ -41,6 +41,7 @@ #include "layout2.h" #include "usb.h" #include "gettext.h" +#include "u2f.h" /* magic constant to check validity of storage block */ static const uint32_t storage_magic = 0x726f7473; // 'stor' as uint32_t @@ -108,7 +109,7 @@ static bool sessionPinCached; static bool sessionPassphraseCached; static char CONFIDENTIAL sessionPassphrase[51]; -#define STORAGE_VERSION 8 +#define STORAGE_VERSION 9 void storage_show_error(void) { @@ -126,6 +127,7 @@ void storage_check_flash_errors(void) bool storage_from_flash(void) { + storage_clear_update(); if (memcmp((void *)FLASH_STORAGE_START, &storage_magic, sizeof(storage_magic)) != 0) { // wrong magic return false; @@ -140,6 +142,7 @@ bool storage_from_flash(void) // version 6: since 1.3.6 // version 7: since 1.5.1 // version 8: since 1.5.2 + // version 9: since 1.6.1 if (version > STORAGE_VERSION) { // downgrade -> clear storage return false; @@ -156,13 +159,20 @@ bool storage_from_flash(void) old_storage_size = 460; } else if (version == 3 || version == 4 || version == 5) { + // added homescreen old_storage_size = 1488; } else if (version == 6 || version == 7) { + // added u2fcounter old_storage_size = 1496; } else if (version == 8) { + // added flags and needsBackup old_storage_size = 1504; + } else + if (version == 9) { + // added u2froot + old_storage_size = 1704; } // erase newly added fields @@ -204,8 +214,15 @@ bool storage_from_flash(void) storage_u2f_offset++; u2fword >>= 1; } - // note: we don't update storage version on flash at this point, - // but it is already upgraded when it comes to content + // force recomputing u2f root for storage version < 9. + if (version < 9) { + storageUpdate.has_mnemonic = storageRom->has_mnemonic; + strlcpy(storageUpdate.mnemonic, storageRom->mnemonic, sizeof(storageUpdate.mnemonic)); + } + // update storage version on flash + if (version != STORAGE_VERSION) { + storage_update(); + } return true; } @@ -242,6 +259,29 @@ static uint32_t storage_flash_words(uint32_t addr, const uint32_t *src, int nwor return addr; } +static void get_u2froot_callback(uint32_t iter, uint32_t total) +{ + layoutProgress(_("Updating"), 1000 * iter / total); +} + +static void storage_compute_u2froot(const char* mnemonic, HDNodeType *u2froot) { + static CONFIDENTIAL HDNode node; + char oldTiny = usbTiny(1); + mnemonic_to_seed(mnemonic, "", sessionSeed, get_u2froot_callback); // BIP-0039 + usbTiny(oldTiny); + hdnode_from_seed(sessionSeed, 64, NIST256P1_NAME, &node); + hdnode_private_ckd(&node, U2F_KEY_PATH); + u2froot->depth = node.depth; + u2froot->child_num = U2F_KEY_PATH; + u2froot->chain_code.size = sizeof(node.chain_code); + memcpy(u2froot->chain_code.bytes, node.chain_code, sizeof(node.chain_code)); + u2froot->has_private_key = true; + u2froot->private_key.size = sizeof(node.private_key); + memcpy(u2froot->private_key.bytes, node.private_key, sizeof(node.private_key)); + memset(&node, 0, sizeof(node)); + session_clear(false); // invalidate seed cache +} + // if storage is filled in - update fields that has has_field set to true // if storage is NULL - do not backup original content - essentialy a wipe static void storage_commit_locked(bool update) @@ -261,6 +301,11 @@ static void storage_commit_locked(bool update) memcpy(&storageUpdate.node, &storageRom->node, sizeof(HDNodeType)); storageUpdate.has_mnemonic = storageRom->has_mnemonic; strlcpy(storageUpdate.mnemonic, storageRom->mnemonic, sizeof(storageUpdate.mnemonic)); + storageUpdate.has_u2froot = storageRom->has_u2froot; + memcpy(&storageUpdate.u2froot, &storageRom->u2froot, sizeof(HDNodeType)); + } else if (storageUpdate.has_mnemonic) { + storageUpdate.has_u2froot = true; + storage_compute_u2froot(storageUpdate.mnemonic, &storageUpdate.u2froot); } if (!storageUpdate.has_passphrase_protection) { storageUpdate.has_passphrase_protection = storageRom->has_passphrase_protection; @@ -467,6 +512,12 @@ const uint8_t *storage_getSeed(bool usePassphrase) return NULL; } +bool storage_getU2FRoot(HDNode *node) +{ + return storageRom->has_u2froot + && hdnode_from_xprv(storageRom->u2froot.depth, storageRom->u2froot.child_num, storageRom->u2froot.chain_code.bytes, storageRom->u2froot.private_key.bytes, NIST256P1_NAME, node); +} + bool storage_getRootNode(HDNode *node, const char *curve, bool usePassphrase) { // if storage has node, decrypt and use it diff --git a/firmware/storage.h b/firmware/storage.h index 7c0eb01ebd..9d7935258f 100644 --- a/firmware/storage.h +++ b/firmware/storage.h @@ -37,6 +37,7 @@ void storage_loadDevice(LoadDevice *msg); const uint8_t *storage_getSeed(bool usePassphrase); +bool storage_getU2FRoot(HDNode *node); bool storage_getRootNode(HDNode *node, const char *curve, bool usePassphrase); const char *storage_getLabel(void); diff --git a/firmware/u2f.c b/firmware/u2f.c index 2c084da40b..5e6a06af53 100644 --- a/firmware/u2f.c +++ b/firmware/u2f.c @@ -59,7 +59,7 @@ static uint8_t u2f_out_packets[U2F_OUT_PKT_BUFFER_LEN][HID_RPT_SIZE]; #define KEY_HANDLE_LEN (KEY_PATH_LEN + SHA256_DIGEST_LENGTH) // Derivation path is m/U2F'/r'/r'/r'/r'/r'/r'/r'/r' -#define KEY_PATH_ENTRIES (1 + KEY_PATH_LEN / sizeof(uint32_t)) +#define KEY_PATH_ENTRIES (KEY_PATH_LEN / sizeof(uint32_t)) // Defined as UsbSignHandler.BOGUS_APP_ID_HASH // in https://github.com/google/u2f-ref-code/blob/master/u2f-chrome-extension/usbsignhandler.js#L118 @@ -450,7 +450,7 @@ static void getReadableAppId(const uint8_t appid[U2F_APPID_SIZE], const char **a static const HDNode *getDerivedNode(uint32_t *address_n, size_t address_n_count) { static CONFIDENTIAL HDNode node; - if (!storage_getRootNode(&node, NIST256P1_NAME, false)) { + if (!storage_getU2FRoot(&node)) { layoutHome(); debugLog(0, "", "ERR: Device not init"); return 0; @@ -472,14 +472,13 @@ static const HDNode *generateKeyHandle(const uint8_t app_id[], uint8_t key_handl // Derivation path is m/U2F'/r'/r'/r'/r'/r'/r'/r'/r' uint32_t key_path[KEY_PATH_ENTRIES]; - key_path[0] = U2F_KEY_PATH; - for (uint32_t i = 1; i < KEY_PATH_ENTRIES; i++) { + for (uint32_t i = 0; i < KEY_PATH_ENTRIES; i++) { // high bit for hardened keys key_path[i]= 0x80000000 | random32(); } // First half of keyhandle is key_path - memcpy(key_handle, &key_path[1], KEY_PATH_LEN); + memcpy(key_handle, key_path, KEY_PATH_LEN); // prepare keypair from /random data const HDNode *node = getDerivedNode(key_path, KEY_PATH_ENTRIES); @@ -501,9 +500,8 @@ static const HDNode *generateKeyHandle(const uint8_t app_id[], uint8_t key_handl static const HDNode *validateKeyHandle(const uint8_t app_id[], const uint8_t key_handle[]) { uint32_t key_path[KEY_PATH_ENTRIES]; - key_path[0] = U2F_KEY_PATH; - memcpy(&key_path[1], key_handle, KEY_PATH_LEN); - for (unsigned int i = 1; i < KEY_PATH_ENTRIES; i++) { + memcpy(key_path, key_handle, KEY_PATH_LEN); + for (unsigned int i = 0; i < KEY_PATH_ENTRIES; i++) { // check high bit for hardened keys if (! (key_path[i] & 0x80000000)) { return NULL; @@ -557,8 +555,6 @@ void u2f_register(const APDU *a) // First Time request, return not present and display request dialog if (last_req_state == INIT) { - // wake up crypto system to be ready for signing - getDerivedNode(NULL, 0); // error: testof-user-presence is required buttonUpdate(); // Clear button state if (0 == memcmp(req->appId, BOGUS_APPID, U2F_APPID_SIZE)) {