diff --git a/storage/tests/Makefile b/storage/tests/Makefile
index f5cea5ff5..c724ca922 100644
--- a/storage/tests/Makefile
+++ b/storage/tests/Makefile
@@ -6,10 +6,12 @@ build:
$(MAKE) -C c
$(MAKE) -C c libtrezor-storage-qw.so
$(MAKE) -C c0
+ $(MAKE) -C c3
clean:
$(MAKE) -C c clean
$(MAKE) -C c0 clean
+ $(MAKE) -C c3 clean
## tests commands:
tests:
diff --git a/storage/tests/c3/Makefile b/storage/tests/c3/Makefile
new file mode 100644
index 000000000..706a08b48
--- /dev/null
+++ b/storage/tests/c3/Makefile
@@ -0,0 +1,46 @@
+CC = cc
+
+CFLAGS = -Wall -Wshadow -Wextra -Wpedantic -Werror -Wno-missing-braces
+CFLAGS += -fPIC
+CFALGS += -fsanitize=address,undefined
+CFLAGS += -DTREZOR_MODEL_T
+CFLAGS += -DUSE_INSECURE_PRNG
+
+LIBS =
+INC = -I ../../../crypto -I ../.. -I .
+BASE = ../../../
+
+SRC = storage/tests/c3/flash.c
+SRC += storage/tests/c3/common.c
+SRC += storage/tests/c3/random_delays.c
+SRC += storage/tests/c3/test_layout.c
+SRC += storage/tests/c3/flash_common.c
+SRC += storage/tests/c3/storage.c
+SRC += storage/tests/c3/norcow.c
+SRC += crypto/pbkdf2.c
+SRC += crypto/rand.c
+SRC += crypto/chacha20poly1305/rfc7539.c
+SRC += crypto/chacha20poly1305/chacha20poly1305.c
+SRC += crypto/chacha20poly1305/poly1305-donna.c
+SRC += crypto/chacha20poly1305/chacha_merged.c
+SRC += crypto/hmac.c
+SRC += crypto/sha2.c
+SRC += crypto/memzero.c
+
+OBJ = $(SRC:%.c=build/%.o)
+
+OUT = libtrezor-storage3.so
+
+$(OUT): $(OBJ)
+ $(CC) $(CFLAGS) $(LIBS) $(OBJ) -shared -o $(OUT)
+
+build/crypto/chacha20poly1305/chacha_merged.o: $(BASE)crypto/chacha20poly1305/chacha_merged.c
+ mkdir -p $(@D)
+ $(CC) $(CFLAGS) $(INC) -c $< -o $@
+
+build/%.o: $(BASE)%.c $(BASE)%.h
+ mkdir -p $(@D)
+ $(CC) $(CFLAGS) $(INC) -c $< -o $@
+
+clean:
+ rm -f $(OUT) $(OBJ)
diff --git a/storage/tests/c3/common.c b/storage/tests/c3/common.c
new file mode 100644
index 000000000..82234756b
--- /dev/null
+++ b/storage/tests/c3/common.c
@@ -0,0 +1,50 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+
+#include "common.h"
+
+void __shutdown(void) {
+ printf("SHUTDOWN\n");
+ exit(3);
+}
+
+void __fatal_error(const char *expr, const char *msg, const char *file,
+ int line, const char *func) {
+ printf("\nFATAL ERROR:\n");
+ if (expr) {
+ printf("expr: %s\n", expr);
+ }
+ if (msg) {
+ printf("msg : %s\n", msg);
+ }
+ if (file) {
+ printf("file: %s:%d\n", file, line);
+ }
+ if (func) {
+ printf("func: %s\n", func);
+ }
+ __shutdown();
+}
+
+void show_wipe_code_screen(void) {}
+void show_pin_too_many_screen(void) {}
diff --git a/storage/tests/c3/common.h b/storage/tests/c3/common.h
new file mode 100644
index 000000000..845dbf0c0
--- /dev/null
+++ b/storage/tests/c3/common.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef __TREZORHAL_COMMON_H__
+#define __TREZORHAL_COMMON_H__
+
+#include "secbool.h"
+
+void __fatal_error(const char *expr, const char *msg, const char *file,
+ int line, const char *func);
+
+void show_wipe_code_screen(void);
+void show_pin_too_many_screen(void);
+
+#define ensure(expr, msg) \
+ (((expr) == sectrue) \
+ ? (void)0 \
+ : __fatal_error(#expr, msg, __FILE__, __LINE__, __func__))
+
+#define hal_delay(ms) (void)ms;
+
+#endif
diff --git a/storage/tests/c3/flash.c b/storage/tests/c3/flash.c
new file mode 100644
index 000000000..7ca193f8d
--- /dev/null
+++ b/storage/tests/c3/flash.c
@@ -0,0 +1,148 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+
+#include "common.h"
+#include "flash.h"
+#include "norcow_config.h"
+
+#define FLASH_SECTOR_COUNT 24
+
+static const uint32_t FLASH_START = 0x08000000;
+static const uint32_t FLASH_END = 0x08200000;
+static const uint32_t FLASH_SECTOR_TABLE[FLASH_SECTOR_COUNT + 1] = {
+ [0] = FLASH_START, // - 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] = FLASH_END, // last element - not a valid sector
+};
+const uint32_t FLASH_SIZE = FLASH_END - FLASH_START;
+uint8_t *FLASH_BUFFER = NULL;
+
+secbool flash_unlock_write(void) { return sectrue; }
+
+secbool flash_lock_write(void) { return sectrue; }
+
+uint32_t flash_sector_size(uint16_t sector) {
+ if (sector >= FLASH_SECTOR_COUNT) {
+ return 0;
+ }
+ return FLASH_SECTOR_TABLE[sector + 1] - FLASH_SECTOR_TABLE[sector];
+}
+
+const void *flash_get_address(uint16_t sector, uint32_t offset, uint32_t size) {
+ if (sector >= FLASH_SECTOR_COUNT) {
+ return NULL;
+ }
+ const uint32_t addr = FLASH_SECTOR_TABLE[sector] + offset;
+ const uint32_t next = FLASH_SECTOR_TABLE[sector + 1];
+ if (addr + size > next) {
+ return NULL;
+ }
+ return FLASH_BUFFER + addr - FLASH_SECTOR_TABLE[0];
+}
+
+secbool flash_area_erase_bulk(const flash_area_t *area, int count,
+ void (*progress)(int pos, int len)) {
+ ensure(flash_unlock_write(), NULL);
+
+ int total_sectors = 0;
+ int done_sectors = 0;
+ for (int a = 0; a < count; a++) {
+ for (int i = 0; i < area[a].num_subareas; i++) {
+ total_sectors += area[a].subarea[i].num_sectors;
+ }
+ }
+
+ if (progress) {
+ progress(0, total_sectors);
+ }
+
+ for (int a = 0; a < count; a++) {
+ for (int s = 0; s < area[a].num_subareas; s++) {
+ for (int i = 0; i < area[a].subarea[s].num_sectors; i++) {
+ int sector = area[a].subarea[s].first_sector + i;
+
+ const uint32_t offset =
+ FLASH_SECTOR_TABLE[sector] - FLASH_SECTOR_TABLE[0];
+ const uint32_t size =
+ FLASH_SECTOR_TABLE[sector + 1] - FLASH_SECTOR_TABLE[sector];
+ memset(FLASH_BUFFER + offset, 0xFF, size);
+
+ done_sectors++;
+ if (progress) {
+ progress(done_sectors, total_sectors);
+ }
+ }
+ }
+ }
+ ensure(flash_lock_write(), NULL);
+ return sectrue;
+}
+
+secbool flash_write_byte(uint16_t sector, uint32_t offset, uint8_t data) {
+ uint8_t *flash = (uint8_t *)flash_get_address(sector, offset, 1);
+ if (!flash) {
+ return secfalse;
+ }
+ if ((flash[0] & data) != data) {
+ return secfalse; // we cannot change zeroes to ones
+ }
+ flash[0] = data;
+ return sectrue;
+}
+
+secbool flash_write_word(uint16_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;
+ return sectrue;
+}
diff --git a/storage/tests/c3/flash.h b/storage/tests/c3/flash.h
new file mode 100644
index 000000000..674f3dba5
--- /dev/null
+++ b/storage/tests/c3/flash.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef FLASH_H
+#define FLASH_H
+
+#include
+#include
+#include "secbool.h"
+
+#define FLASH_BYTE_ACCESS 1
+
+#include "flash_common.h"
+#include "test_layout.h"
+
+uint32_t flash_sector_size(uint16_t sector);
+
+secbool flash_write_byte(uint16_t sector, uint32_t offset, uint8_t data);
+
+secbool flash_write_word(uint16_t sector, uint32_t offset, uint32_t data);
+
+#endif
diff --git a/storage/tests/c3/flash_common.c b/storage/tests/c3/flash_common.c
new file mode 100644
index 000000000..025851488
--- /dev/null
+++ b/storage/tests/c3/flash_common.c
@@ -0,0 +1,133 @@
+#include "flash.h"
+
+secbool flash_write_byte(uint16_t sector, uint32_t offset, uint8_t data);
+
+secbool flash_write_word(uint16_t sector, uint32_t offset, uint32_t data);
+
+const void *flash_get_address(uint16_t sector, uint32_t offset, uint32_t size);
+
+static uint32_t flash_subarea_get_size(const flash_subarea_t *subarea) {
+ uint32_t size = 0;
+ for (int s = 0; s < subarea->num_sectors; s++) {
+ size += flash_sector_size(subarea->first_sector + s);
+ }
+ return size;
+}
+
+static secbool subarea_get_sector_and_offset(const flash_subarea_t *subarea,
+ uint32_t offset,
+ uint16_t *sector_out,
+ uint32_t *offset_out) {
+ uint32_t tmp_offset = offset;
+ uint16_t sector = subarea->first_sector;
+
+ // in correct subarea
+ for (int s = 0; s < subarea->num_sectors; s++) {
+ const uint32_t sector_size = flash_sector_size(sector);
+ if (tmp_offset < sector_size) {
+ *sector_out = sector;
+ *offset_out = tmp_offset;
+ return sectrue;
+ }
+ tmp_offset -= sector_size;
+ sector++;
+ }
+ return secfalse;
+}
+
+uint32_t flash_area_get_size(const flash_area_t *area) {
+ uint32_t size = 0;
+ for (int i = 0; i < area->num_subareas; i++) {
+ size += flash_subarea_get_size(&area->subarea[i]);
+ }
+ return size;
+}
+
+uint16_t flash_total_sectors(const flash_area_t *area) {
+ uint16_t total = 0;
+ for (int i = 0; i < area->num_subareas; i++) {
+ total += area->subarea[i].num_sectors;
+ }
+ return total;
+}
+
+int32_t flash_get_sector_num(const flash_area_t *area,
+ uint32_t sector_inner_num) {
+ uint16_t sector = 0;
+ uint16_t remaining = sector_inner_num;
+ for (int i = 0; i < area->num_subareas; i++) {
+ if (remaining < area->subarea[i].num_sectors) {
+ sector = area->subarea[i].first_sector + remaining;
+ return sector;
+ } else {
+ remaining -= area->subarea[i].num_sectors;
+ }
+ }
+
+ return -1;
+}
+
+static secbool get_sector_and_offset(const flash_area_t *area, uint32_t offset,
+ uint16_t *sector_out,
+ uint32_t *offset_out) {
+ uint32_t tmp_offset = offset;
+ for (int i = 0; i < area->num_subareas; i++) {
+ uint32_t sub_size = flash_subarea_get_size(&area->subarea[i]);
+ if (tmp_offset >= sub_size) {
+ tmp_offset -= sub_size;
+ continue;
+ }
+
+ return subarea_get_sector_and_offset(&area->subarea[i], tmp_offset,
+ sector_out, offset_out);
+ }
+ return secfalse;
+}
+
+const void *flash_area_get_address(const flash_area_t *area, uint32_t offset,
+ uint32_t size) {
+ uint16_t sector;
+ uint32_t sector_offset;
+
+ if (!get_sector_and_offset(area, offset, §or, §or_offset)) {
+ return NULL;
+ }
+
+ return flash_get_address(sector, sector_offset, size);
+}
+
+secbool flash_area_erase(const flash_area_t *area,
+ void (*progress)(int pos, int len)) {
+ return flash_area_erase_bulk(area, 1, progress);
+}
+
+secbool flash_area_write_byte(const flash_area_t *area, uint32_t offset,
+ uint8_t data) {
+ uint16_t sector;
+ uint32_t sector_offset;
+ if (get_sector_and_offset(area, offset, §or, §or_offset) != sectrue) {
+ return secfalse;
+ }
+ return flash_write_byte(sector, sector_offset, data);
+}
+
+secbool flash_area_write_word(const flash_area_t *area, uint32_t offset,
+ uint32_t data) {
+ uint16_t sector;
+ uint32_t sector_offset;
+ if (get_sector_and_offset(area, offset, §or, §or_offset) != sectrue) {
+ return secfalse;
+ }
+ return flash_write_word(sector, sector_offset, data);
+}
+
+secbool flash_area_write_quadword(const flash_area_t *area, uint32_t offset,
+ const uint32_t *data) {
+ for (int i = 0; i < 4; i++) {
+ if (sectrue !=
+ flash_area_write_word(area, offset + i * sizeof(uint32_t), data[i])) {
+ return secfalse;
+ }
+ }
+ return sectrue;
+}
diff --git a/storage/tests/c3/flash_common.h b/storage/tests/c3/flash_common.h
new file mode 100644
index 000000000..b8cac2b55
--- /dev/null
+++ b/storage/tests/c3/flash_common.h
@@ -0,0 +1,45 @@
+#ifndef FLASH_COMMON_H
+#define FLASH_COMMON_H
+
+#include
+#include "secbool.h"
+
+typedef struct {
+ uint16_t first_sector;
+ uint16_t num_sectors;
+} flash_subarea_t;
+
+typedef struct {
+ flash_subarea_t subarea[4];
+ uint8_t num_subareas;
+} flash_area_t;
+
+void flash_init(void);
+
+secbool __wur flash_unlock_write(void);
+secbool __wur flash_lock_write(void);
+
+uint32_t flash_sector_size(uint16_t sector);
+uint16_t flash_total_sectors(const flash_area_t *area);
+int32_t flash_get_sector_num(const flash_area_t *area,
+ uint32_t sector_inner_num);
+
+const void *flash_area_get_address(const flash_area_t *area, uint32_t offset,
+ uint32_t size);
+uint32_t flash_area_get_size(const flash_area_t *area);
+
+secbool __wur flash_area_erase(const flash_area_t *area,
+ void (*progress)(int pos, int len));
+secbool __wur flash_area_erase_bulk(const flash_area_t *area, int count,
+ void (*progress)(int pos, int len));
+
+#if defined FLASH_BYTE_ACCESS
+secbool __wur flash_area_write_byte(const flash_area_t *area, uint32_t offset,
+ uint8_t data);
+secbool __wur flash_area_write_word(const flash_area_t *area, uint32_t offset,
+ uint32_t data);
+#endif
+secbool __wur flash_area_write_quadword(const flash_area_t *area,
+ uint32_t offset, const uint32_t *data);
+
+#endif
diff --git a/storage/tests/c3/norcow.c b/storage/tests/c3/norcow.c
new file mode 100644
index 000000000..324d20c4b
--- /dev/null
+++ b/storage/tests/c3/norcow.c
@@ -0,0 +1,585 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+
+#include "common.h"
+#include "flash.h"
+#include "norcow.h"
+
+// NRC2 = 4e524332
+#define NORCOW_MAGIC ((uint32_t)0x3243524e)
+// NRCW = 4e524357
+#define NORCOW_MAGIC_V0 ((uint32_t)0x5743524e)
+
+#define NORCOW_WORD_SIZE (sizeof(uint32_t))
+#define NORCOW_PREFIX_LEN NORCOW_WORD_SIZE
+#define NORCOW_MAGIC_LEN NORCOW_WORD_SIZE
+#define NORCOW_VERSION_LEN NORCOW_WORD_SIZE
+
+// The key value which is used to indicate that the entry is not set.
+#define NORCOW_KEY_FREE (0xFFFF)
+
+// The key value which is used to indicate that the entry has been deleted.
+#define NORCOW_KEY_DELETED (0x0000)
+
+// The offset from the beginning of the sector where stored items start.
+#define NORCOW_STORAGE_START \
+ (NORCOW_HEADER_LEN + NORCOW_MAGIC_LEN + NORCOW_VERSION_LEN)
+
+// The index of the active reading sector and writing sector. These should be
+// equal except when storage version upgrade or compaction is in progress.
+static uint8_t norcow_active_sector = 0;
+static uint8_t norcow_write_sector = 0;
+
+// The norcow version of the reading sector.
+static uint32_t norcow_active_version = 0;
+
+// The offset of the first free item in the writing sector.
+static uint32_t norcow_free_offset = 0;
+
+/*
+ * 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) {
+ ensure(sectrue * (sector <= NORCOW_SECTOR_COUNT), "invalid sector");
+ return flash_area_get_address(&STORAGE_AREAS[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 (offset + NORCOW_PREFIX_LEN + len > NORCOW_SECTOR_SIZE) {
+ return secfalse;
+ }
+
+ ensure(flash_unlock_write(), NULL);
+
+ // write prefix
+ ensure(flash_area_write_word(&STORAGE_AREAS[sector], offset, prefix), NULL);
+ offset += NORCOW_PREFIX_LEN;
+
+ if (data != NULL) {
+ // write data
+ for (uint16_t i = 0; i < len; i++, offset++) {
+ ensure(flash_area_write_byte(&STORAGE_AREAS[sector], offset, data[i]),
+ NULL);
+ }
+ } else {
+ offset += len;
+ }
+
+ // pad with zeroes
+ for (; offset % NORCOW_WORD_SIZE; offset++) {
+ ensure(flash_area_write_byte(&STORAGE_AREAS[sector], offset, 0x00), NULL);
+ }
+
+ ensure(flash_lock_write(), NULL);
+ return sectrue;
+}
+
+/*
+ * Erases sector (and sets a magic)
+ */
+static void erase_sector(uint8_t sector, secbool set_magic) {
+#if NORCOW_HEADER_LEN > 0
+ // Backup the sector header.
+ uint32_t header_backup[NORCOW_HEADER_LEN / sizeof(uint32_t)] = {0};
+ const void *sector_start = norcow_ptr(sector, 0, NORCOW_HEADER_LEN);
+ memcpy(header_backup, sector_start, sizeof(header_backup));
+#endif
+
+ ensure(flash_area_erase(&STORAGE_AREAS[sector], NULL), "erase failed");
+
+#if NORCOW_HEADER_LEN > 0
+ // Copy the sector header back.
+ ensure(flash_unlock_write(), NULL);
+ for (uint32_t i = 0; i < NORCOW_HEADER_LEN / sizeof(uint32_t); ++i) {
+ ensure(flash_write_word(norcow_sectors[sector], i * sizeof(uint32_t),
+ header_backup[i]),
+ NULL);
+ }
+ ensure(flash_lock_write(), NULL);
+#endif
+
+ if (sectrue == set_magic) {
+ ensure(norcow_write(sector, NORCOW_HEADER_LEN, NORCOW_MAGIC, NULL, 0),
+ "set magic failed");
+ ensure(norcow_write(sector, NORCOW_HEADER_LEN + NORCOW_MAGIC_LEN,
+ ~NORCOW_VERSION, NULL, 0),
+ "set version failed");
+ }
+}
+
+#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 == NORCOW_KEY_FREE) {
+ 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 = ((uint32_t)len << 16) | key;
+ *pos = offset + NORCOW_PREFIX_LEN + len;
+ ALIGN4(*pos);
+ return norcow_write(sector, offset, prefix, val, len);
+}
+
+/*
+ * Finds the offset from the beginning of the sector where stored items start.
+ */
+static secbool find_start_offset(uint8_t sector, uint32_t *offset,
+ uint32_t *version) {
+ const uint32_t *magic = norcow_ptr(sector, NORCOW_HEADER_LEN,
+ NORCOW_MAGIC_LEN + NORCOW_VERSION_LEN);
+ if (magic == NULL) {
+ return secfalse;
+ }
+
+ if (*magic == NORCOW_MAGIC) {
+ *offset = NORCOW_STORAGE_START;
+ *version = ~(magic[1]);
+ } else if (*magic == NORCOW_MAGIC_V0) {
+ *offset = NORCOW_HEADER_LEN + NORCOW_MAGIC_LEN;
+ *version = 0;
+ } else {
+ return secfalse;
+ }
+
+ return sectrue;
+}
+
+/*
+ * Finds item in given sector
+ */
+static secbool find_item(uint8_t sector, uint16_t key, const void **val,
+ uint16_t *len) {
+ *val = NULL;
+ *len = 0;
+
+ uint32_t offset = 0;
+ uint32_t version = 0;
+ if (sectrue != find_start_offset(sector, &offset, &version)) {
+ return secfalse;
+ }
+
+ for (;;) {
+ uint16_t k = 0, l = 0;
+ const void *v = NULL;
+ uint32_t pos = 0;
+ 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;
+ uint32_t version = 0;
+ if (sectrue != find_start_offset(sector, &offset, &version)) {
+ return secfalse;
+ }
+
+ for (;;) {
+ uint16_t key = 0, len = 0;
+ const void *val = NULL;
+ uint32_t pos = 0;
+ 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(void) {
+ uint32_t offsetr = 0;
+ uint32_t version = 0;
+ if (sectrue != find_start_offset(norcow_active_sector, &offsetr, &version)) {
+ return;
+ }
+
+ norcow_write_sector = (norcow_active_sector + 1) % NORCOW_SECTOR_COUNT;
+ erase_sector(norcow_write_sector, sectrue);
+ uint32_t offsetw = NORCOW_STORAGE_START;
+
+ for (;;) {
+ // read item
+ uint16_t k = 0, l = 0;
+ const void *v = NULL;
+ uint32_t posr = 0;
+ secbool r = read_item(norcow_active_sector, offsetr, &k, &v, &l, &posr);
+ if (sectrue != r) {
+ break;
+ }
+ offsetr = posr;
+
+ // skip deleted items
+ if (k == NORCOW_KEY_DELETED) {
+ continue;
+ }
+
+ // copy the item
+ uint32_t posw = 0;
+ ensure(write_item(norcow_write_sector, offsetw, k, v, l, &posw),
+ "compaction write failed");
+ offsetw = posw;
+ }
+
+ erase_sector(norcow_active_sector, secfalse);
+ norcow_active_sector = norcow_write_sector;
+ norcow_active_version = NORCOW_VERSION;
+ norcow_free_offset = find_free_offset(norcow_write_sector);
+}
+
+/*
+ * Initializes storage
+ */
+void norcow_init(uint32_t *norcow_version) {
+ secbool found = secfalse;
+ *norcow_version = 0;
+ norcow_active_sector = 0;
+ // detect active sector - starts with magic and has highest version
+ for (uint8_t i = 0; i < NORCOW_SECTOR_COUNT; i++) {
+ uint32_t offset = 0;
+ if (sectrue == find_start_offset(i, &offset, &norcow_active_version) &&
+ norcow_active_version >= *norcow_version) {
+ found = sectrue;
+ norcow_active_sector = i;
+ *norcow_version = norcow_active_version;
+ }
+ }
+
+ // If no active sectors found or version downgrade, then erase.
+ if (sectrue != found || *norcow_version > NORCOW_VERSION) {
+ norcow_wipe();
+ *norcow_version = NORCOW_VERSION;
+ } else if (*norcow_version < NORCOW_VERSION) {
+ // Prepare write sector for storage upgrade.
+ norcow_write_sector = (norcow_active_sector + 1) % NORCOW_SECTOR_COUNT;
+ erase_sector(norcow_write_sector, sectrue);
+ norcow_free_offset = find_free_offset(norcow_write_sector);
+ } else {
+ norcow_write_sector = norcow_active_sector;
+ norcow_free_offset = find_free_offset(norcow_write_sector);
+ }
+}
+
+/*
+ * Wipe the storage
+ */
+void norcow_wipe(void) {
+ // Erase the active sector first, because it contains sensitive data.
+ erase_sector(norcow_active_sector, sectrue);
+
+ for (uint8_t i = 0; i < NORCOW_SECTOR_COUNT; i++) {
+ if (i != norcow_active_sector) {
+ erase_sector(i, secfalse);
+ }
+ }
+ norcow_active_version = NORCOW_VERSION;
+ norcow_write_sector = norcow_active_sector;
+ norcow_free_offset = NORCOW_STORAGE_START;
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Reads the next entry in the storage starting at offset. Returns secfalse if
+ * there is none.
+ */
+secbool norcow_get_next(uint32_t *offset, uint16_t *key, const void **val,
+ uint16_t *len) {
+ if (*offset == 0) {
+ uint32_t version = 0;
+ if (sectrue != find_start_offset(norcow_active_sector, offset, &version)) {
+ return secfalse;
+ }
+ }
+
+ for (;;) {
+ uint32_t pos = 0;
+ secbool ret = read_item(norcow_active_sector, *offset, key, val, len, &pos);
+ if (sectrue != ret) {
+ break;
+ }
+ *offset = pos;
+
+ // Skip deleted items.
+ if (*key == NORCOW_KEY_DELETED) {
+ continue;
+ }
+
+ if (norcow_active_version == 0) {
+ // Check whether the item is the latest instance.
+ uint32_t offsetr = *offset;
+ for (;;) {
+ uint16_t k = 0;
+ uint16_t l = 0;
+ const void *v = NULL;
+ ret = read_item(norcow_active_sector, offsetr, &k, &v, &l, &offsetr);
+ if (sectrue != ret) {
+ // There is no newer instance of the item.
+ return sectrue;
+ }
+ if (*key == k) {
+ // There exists a newer instance of the item.
+ break;
+ }
+ }
+ } else {
+ return sectrue;
+ }
+ }
+ return secfalse;
+}
+
+/*
+ * Sets the given key, returns status of the operation. If NULL is passed
+ * as val, then norcow_set allocates a new key of size len. The value should
+ * then be written using norcow_update_bytes().
+ */
+secbool norcow_set(uint16_t key, const void *val, uint16_t len) {
+ secbool found = secfalse;
+ return norcow_set_ex(key, val, len, &found);
+}
+
+secbool norcow_set_ex(uint16_t key, const void *val, uint16_t len,
+ secbool *found) {
+ // Key 0xffff is used as a marker to indicate that the entry is not set.
+ if (key == NORCOW_KEY_FREE) {
+ return secfalse;
+ }
+
+ secbool ret = secfalse;
+ const void *ptr = NULL;
+ uint16_t len_old = 0;
+ *found = find_item(norcow_write_sector, key, &ptr, &len_old);
+
+ // Try to update the entry if it already exists.
+ uint32_t offset = 0;
+ if (sectrue == *found) {
+ offset =
+ (const uint8_t *)ptr -
+ (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE);
+ if (val != NULL && len_old == len) {
+ ret = sectrue;
+ ensure(flash_unlock_write(), NULL);
+ for (uint16_t i = 0; i < len; i++) {
+ if (sectrue !=
+ flash_area_write_byte(&STORAGE_AREAS[norcow_write_sector],
+ offset + i, ((const uint8_t *)val)[i])) {
+ ret = secfalse;
+ break;
+ }
+ }
+ ensure(flash_lock_write(), NULL);
+ }
+ }
+
+ // If the update was not possible then write the entry as a new item.
+ if (secfalse == ret) {
+ // Delete the old item.
+ if (sectrue == *found) {
+ ensure(flash_unlock_write(), NULL);
+
+ // Update the prefix to indicate that the old item has been deleted.
+ uint32_t prefix = (uint32_t)len_old << 16;
+ ensure(flash_area_write_word(&STORAGE_AREAS[norcow_write_sector],
+ offset - NORCOW_PREFIX_LEN, prefix),
+ NULL);
+
+ // Delete the old item data.
+ uint32_t end = offset + len_old;
+ while (offset < end) {
+ ensure(flash_area_write_word(&STORAGE_AREAS[norcow_write_sector],
+ offset, 0x00000000),
+ NULL);
+ offset += NORCOW_WORD_SIZE;
+ }
+
+ ensure(flash_lock_write(), NULL);
+ }
+ // Check whether there is enough free space and compact if full.
+ if (norcow_free_offset + NORCOW_PREFIX_LEN + len > NORCOW_SECTOR_SIZE) {
+ compact();
+ }
+ // Write new item.
+ uint32_t pos = 0;
+ ret = write_item(norcow_write_sector, norcow_free_offset, key, val, len,
+ &pos);
+ if (sectrue == ret) {
+ norcow_free_offset = pos;
+ }
+ }
+ return ret;
+}
+
+/*
+ * Deletes the given key, returns status of the operation.
+ */
+secbool norcow_delete(uint16_t key) {
+ // Key 0xffff is used as a marker to indicate that the entry is not set.
+ if (key == NORCOW_KEY_FREE) {
+ return secfalse;
+ }
+
+ const void *ptr = NULL;
+ uint16_t len = 0;
+ if (sectrue != find_item(norcow_write_sector, key, &ptr, &len)) {
+ return secfalse;
+ }
+
+ uint32_t offset =
+ (const uint8_t *)ptr -
+ (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE);
+
+ ensure(flash_unlock_write(), NULL);
+
+ // Update the prefix to indicate that the item has been deleted.
+ uint32_t prefix = (uint32_t)len << 16;
+ ensure(flash_area_write_word(&STORAGE_AREAS[norcow_write_sector],
+ offset - NORCOW_PREFIX_LEN, prefix),
+ NULL);
+
+ // Delete the item data.
+ uint32_t end = offset + len;
+ while (offset < end) {
+ ensure(flash_area_write_word(&STORAGE_AREAS[norcow_write_sector], offset,
+ 0x00000000),
+ NULL);
+ offset += NORCOW_WORD_SIZE;
+ }
+
+ ensure(flash_lock_write(), NULL);
+
+ return sectrue;
+}
+
+/*
+ * Update a word in flash at the given pointer. The pointer must point
+ * into the NORCOW area.
+ */
+secbool norcow_update_word(uint16_t key, uint16_t offset, uint32_t value) {
+ const void *ptr = NULL;
+ uint16_t len = 0;
+ if (sectrue != find_item(norcow_write_sector, key, &ptr, &len)) {
+ return secfalse;
+ }
+ if ((offset & 3) != 0 || offset >= len) {
+ return secfalse;
+ }
+ uint32_t sector_offset =
+ (const uint8_t *)ptr -
+ (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE) +
+ offset;
+ ensure(flash_unlock_write(), NULL);
+ ensure(flash_area_write_word(&STORAGE_AREAS[norcow_write_sector],
+ sector_offset, value),
+ NULL);
+ ensure(flash_lock_write(), NULL);
+ return sectrue;
+}
+
+/*
+ * Update the value of the given key starting at the given offset.
+ */
+secbool norcow_update_bytes(const uint16_t key, const uint16_t offset,
+ const uint8_t *data, const uint16_t len) {
+ const void *ptr = NULL;
+ uint16_t allocated_len = 0;
+ if (sectrue != find_item(norcow_write_sector, key, &ptr, &allocated_len)) {
+ return secfalse;
+ }
+ if (offset + len > allocated_len) {
+ return secfalse;
+ }
+ uint32_t sector_offset =
+ (const uint8_t *)ptr -
+ (const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE) +
+ offset;
+ ensure(flash_unlock_write(), NULL);
+ for (uint16_t i = 0; i < len; i++, sector_offset++) {
+ ensure(flash_area_write_byte(&STORAGE_AREAS[norcow_write_sector],
+ sector_offset, data[i]),
+ NULL);
+ }
+ ensure(flash_lock_write(), NULL);
+ return sectrue;
+}
+
+/*
+ * Complete storage version upgrade
+ */
+secbool norcow_upgrade_finish(void) {
+ erase_sector(norcow_active_sector, secfalse);
+ norcow_active_sector = norcow_write_sector;
+ norcow_active_version = NORCOW_VERSION;
+ return sectrue;
+}
diff --git a/storage/tests/c3/norcow.h b/storage/tests/c3/norcow.h
new file mode 100644
index 000000000..ac622678e
--- /dev/null
+++ b/storage/tests/c3/norcow.h
@@ -0,0 +1,86 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef __NORCOW_H__
+#define __NORCOW_H__
+
+#include
+#include "secbool.h"
+
+/*
+ * Storage parameters
+ */
+
+#include "norcow_config.h"
+
+/*
+ * Initialize storage
+ */
+void norcow_init(uint32_t *norcow_version);
+
+/*
+ * Wipe the storage
+ */
+void 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);
+
+/*
+ * Reads the next entry in the storage starting at offset. Returns secfalse if
+ * there is none.
+ */
+secbool norcow_get_next(uint32_t *offset, uint16_t *key, const void **val,
+ uint16_t *len);
+
+/*
+ * Sets the given key, returns status of the operation. If NULL is passed
+ * as val, then norcow_set allocates a new key of size len. The value should
+ * then be written using norcow_update_bytes().
+ */
+secbool norcow_set(uint16_t key, const void *val, uint16_t len);
+secbool norcow_set_ex(uint16_t key, const void *val, uint16_t len,
+ secbool *found);
+
+/*
+ * Deletes the given key, returns status of the operation.
+ */
+secbool norcow_delete(uint16_t key);
+
+/*
+ * Update a word in flash in the given key at the given offset.
+ * Note that you can only change bits from 1 to 0.
+ */
+secbool norcow_update_word(uint16_t key, uint16_t offset, uint32_t value);
+
+/*
+ * Update the value of the given key starting at the given offset.
+ * Note that you can only change bits from 1 to 0.
+ */
+secbool norcow_update_bytes(const uint16_t key, const uint16_t offset,
+ const uint8_t *data, const uint16_t len);
+
+/*
+ * Complete storage version upgrade
+ */
+secbool norcow_upgrade_finish(void);
+
+#endif
diff --git a/storage/tests/c3/norcow_config.h b/storage/tests/c3/norcow_config.h
new file mode 100644
index 000000000..ea72a1115
--- /dev/null
+++ b/storage/tests/c3/norcow_config.h
@@ -0,0 +1,45 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef __NORCOW_CONFIG_H__
+#define __NORCOW_CONFIG_H__
+
+#include "flash.h"
+
+#define NORCOW_SECTOR_COUNT 2
+#define NORCOW_SECTOR_SIZE (64 * 1024)
+
+/*
+ * The length of the sector header in bytes. The header is preserved between
+ * sector erasures.
+ */
+#if defined TREZOR_MODEL_T
+#define NORCOW_HEADER_LEN 0
+#elif defined TREZOR_MODEL_1
+#define NORCOW_HEADER_LEN (0x100)
+#else
+#error Unknown Trezor model
+#endif
+
+/*
+ * Current storage version.
+ */
+#define NORCOW_VERSION ((uint32_t)0x00000003)
+
+#endif
diff --git a/storage/tests/c3/random_delays.c b/storage/tests/c3/random_delays.c
new file mode 100644
index 000000000..eafc3dbbd
--- /dev/null
+++ b/storage/tests/c3/random_delays.c
@@ -0,0 +1,22 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "random_delays.h"
+
+void wait_random(void) {}
diff --git a/storage/tests/c3/random_delays.h b/storage/tests/c3/random_delays.h
new file mode 100644
index 000000000..adb99049d
--- /dev/null
+++ b/storage/tests/c3/random_delays.h
@@ -0,0 +1,25 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef __TREZORHAL_RANDOM_DELAYS_H__
+#define __TREZORHAL_RANDOM_DELAYS_H__
+
+void wait_random(void);
+
+#endif
diff --git a/storage/tests/c3/secbool.h b/storage/tests/c3/secbool.h
new file mode 100644
index 000000000..356c71593
--- /dev/null
+++ b/storage/tests/c3/secbool.h
@@ -0,0 +1,33 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TREZORHAL_SECBOOL_H
+#define TREZORHAL_SECBOOL_H
+
+#include
+
+typedef uint32_t secbool;
+#define sectrue 0xAAAAAAAAU
+#define secfalse 0x00000000U
+
+#ifndef __wur
+#define __wur __attribute__((warn_unused_result))
+#endif
+
+#endif
diff --git a/storage/tests/c3/storage.c b/storage/tests/c3/storage.c
new file mode 100644
index 000000000..203237432
--- /dev/null
+++ b/storage/tests/c3/storage.c
@@ -0,0 +1,1897 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+
+#include "chacha20poly1305/rfc7539.h"
+#include "common.h"
+#include "hmac.h"
+#include "memzero.h"
+#include "norcow.h"
+#include "pbkdf2.h"
+#include "rand.h"
+#include "random_delays.h"
+#include "sha2.h"
+#include "storage.h"
+
+#if USE_OPTIGA
+#include "optiga.h"
+#endif
+
+#define LOW_MASK 0x55555555
+
+// The APP namespace which is reserved for storage related values.
+#define APP_STORAGE 0x00
+
+// Norcow storage keys.
+// PIN entry log and PIN success log.
+#define PIN_LOGS_KEY ((APP_STORAGE << 8) | 0x01)
+
+// Combined salt, EDEK, ESAK and PIN verification code entry.
+#define EDEK_PVC_KEY ((APP_STORAGE << 8) | 0x02)
+
+// PIN set flag.
+#define PIN_NOT_SET_KEY ((APP_STORAGE << 8) | 0x03)
+
+// Authenticated storage version.
+// NOTE: This should equal the norcow version unless an upgrade is in progress.
+#define VERSION_KEY ((APP_STORAGE << 8) | 0x04)
+
+// Storage authentication tag.
+#define STORAGE_TAG_KEY ((APP_STORAGE << 8) | 0x05)
+
+// Wipe code data. Introduced in storage version 2.
+#define WIPE_CODE_DATA_KEY ((APP_STORAGE << 8) | 0x06)
+
+// Storage upgrade flag. Introduced in storage version 2.
+#define STORAGE_UPGRADED_KEY ((APP_STORAGE << 8) | 0x07)
+
+// Unauthenticated storage version. Introduced in storage version 3.
+// NOTE: This should always equal the value in VERSION_KEY.
+#define UNAUTH_VERSION_KEY ((APP_STORAGE << 8) | 0x08)
+
+// The PIN value corresponding to an empty PIN.
+const uint8_t *PIN_EMPTY = (const uint8_t *)"";
+
+// The uint32 representation of an empty PIN, used prior to storage version 3.
+const uint32_t V0_PIN_EMPTY = 1;
+
+// Maximum number of PIN digits allowed prior to storage version 3.
+#define V0_MAX_PIN_LEN 9
+
+// Maximum length of the wipe code.
+// Some limit should be imposed on the length, because the wipe code takes up
+// storage space proportional to the length, as opposed to the PIN, which takes
+// up constant storage space.
+#define MAX_WIPE_CODE_LEN 50
+
+// The total number of iterations to use in PBKDF2.
+#define PIN_ITER_COUNT 20000
+
+// The number of milliseconds required to execute PBKDF2.
+#define PIN_PBKDF2_MS 1280
+
+// The number of milliseconds required to derive the KEK and KEIV.
+#if USE_OPTIGA
+#define PIN_DERIVE_MS (PIN_PBKDF2_MS + OPTIGA_PIN_DERIVE_MS)
+#else
+#define PIN_DERIVE_MS PIN_PBKDF2_MS
+#endif
+
+// The length of the guard key in words.
+#define GUARD_KEY_WORDS 1
+
+// The length of the PIN entry log or the PIN success log in words.
+#define PIN_LOG_WORDS 16
+
+// The length of a word in bytes.
+#define WORD_SIZE (sizeof(uint32_t))
+
+// The length of the hashed hardware salt in bytes.
+#define HARDWARE_SALT_SIZE SHA256_DIGEST_LENGTH
+
+// The length of the data encryption key in bytes.
+#define DEK_SIZE 32
+
+// The length of the storage authentication key in bytes.
+#define SAK_SIZE 16
+
+// The combined length of the data encryption key and the storage authentication
+// key in bytes.
+#define KEYS_SIZE (DEK_SIZE + SAK_SIZE)
+
+// The length of the PIN verification code in bytes.
+#define PVC_SIZE 8
+
+// The length of the storage authentication tag in bytes.
+#define STORAGE_TAG_SIZE 16
+
+// The length of the Poly1305 authentication tag in bytes.
+#define POLY1305_TAG_SIZE 16
+
+// The length of the ChaCha20 IV (aka nonce) in bytes as per RFC 7539.
+#define CHACHA20_IV_SIZE 12
+
+// The length of the ChaCha20 block in bytes.
+#define CHACHA20_BLOCK_SIZE 64
+
+// The byte length of the salt used in checking the wipe code.
+#define WIPE_CODE_SALT_SIZE 8
+
+// The byte length of the tag used in checking the wipe code.
+#define WIPE_CODE_TAG_SIZE 8
+
+// The value corresponding to an unconfigured wipe code.
+// NOTE: This is intentionally different from an empty PIN so that we don't need
+// special handling when both the PIN and wipe code are not set.
+const uint8_t WIPE_CODE_EMPTY[] = {0, 0, 0, 0};
+#define WIPE_CODE_EMPTY_LEN 4
+
+// The uint32 representation of an empty wipe code used in storage version 2.
+#define V2_WIPE_CODE_EMPTY 0
+
+// The length of the counter tail in words.
+#define COUNTER_TAIL_WORDS 2
+
+// Values used in the guard key integrity check.
+#define GUARD_KEY_MODULUS 6311
+#define GUARD_KEY_REMAINDER 15
+
+const char *const VERIFYING_PIN_MSG = "Verifying PIN";
+const char *const PROCESSING_MSG = "Processing";
+const char *const STARTING_MSG = "Starting up";
+const char *const WRONG_PIN_MSG = "Wrong PIN";
+
+static secbool initialized = secfalse;
+static secbool unlocked = secfalse;
+static PIN_UI_WAIT_CALLBACK ui_callback = NULL;
+static uint32_t ui_total = 0;
+static uint32_t ui_rem = 0;
+static const char *ui_message = NULL;
+static uint8_t cached_keys[KEYS_SIZE] = {0};
+static uint8_t *const cached_dek = cached_keys;
+static uint8_t *const cached_sak = cached_keys + DEK_SIZE;
+static uint8_t authentication_sum[SHA256_DIGEST_LENGTH] = {0};
+static uint8_t hardware_salt[HARDWARE_SALT_SIZE] = {0};
+static uint32_t norcow_active_version = 0;
+static const uint8_t TRUE_BYTE = 0x01;
+static const uint8_t FALSE_BYTE = 0x00;
+static const uint32_t TRUE_WORD = 0xC35A69A5;
+static const uint32_t FALSE_WORD = 0x3CA5965A;
+
+static void __handle_fault(const char *msg, const char *file, int line,
+ const char *func);
+#define handle_fault(msg) (__handle_fault(msg, __FILE__, __LINE__, __func__))
+
+static uint32_t pin_to_int(const uint8_t *pin, size_t pin_len);
+static secbool storage_upgrade(void);
+static secbool storage_upgrade_unlocked(const uint8_t *pin, size_t pin_len,
+ const uint8_t *ext_salt);
+static secbool storage_set_encrypted(const uint16_t key, const void *val,
+ const uint16_t len);
+static secbool storage_get_encrypted(const uint16_t key, void *val_dest,
+ const uint16_t max_len, uint16_t *len);
+
+static secbool secequal(const void *ptr1, const void *ptr2, size_t n) {
+ const uint8_t *p1 = ptr1;
+ const uint8_t *p2 = ptr2;
+ uint8_t diff = 0;
+ size_t i = 0;
+ for (i = 0; i < n; ++i) {
+ diff |= *p1 ^ *p2;
+ ++p1;
+ ++p2;
+ }
+
+ // Check loop completion in case of a fault injection attack.
+ if (i != n) {
+ handle_fault("loop completion check");
+ }
+
+ return diff ? secfalse : sectrue;
+}
+
+static secbool secequal32(const void *ptr1, const void *ptr2, size_t n) {
+ assert(n % sizeof(uint32_t) == 0);
+ assert((uintptr_t)ptr1 % sizeof(uint32_t) == 0);
+ assert((uintptr_t)ptr2 % sizeof(uint32_t) == 0);
+
+ size_t wn = n / sizeof(uint32_t);
+ const uint32_t *p1 = (const uint32_t *)ptr1;
+ const uint32_t *p2 = (const uint32_t *)ptr2;
+ uint32_t diff = 0;
+ size_t i = 0;
+ for (i = 0; i < wn; ++i) {
+ uint32_t mask = random32();
+ diff |= (*p1 + mask - *p2) ^ mask;
+ ++p1;
+ ++p2;
+ }
+
+ // Check loop completion in case of a fault injection attack.
+ if (i != wn) {
+ handle_fault("loop completion check");
+ }
+
+ return diff ? secfalse : sectrue;
+}
+
+static secbool is_protected(uint16_t key) {
+ const uint8_t app = key >> 8;
+ return ((app & FLAG_PUBLIC) == 0 && app != APP_STORAGE) ? sectrue : secfalse;
+}
+
+/*
+ * Initialize the storage authentication tag for freshly wiped storage.
+ */
+static secbool auth_init(void) {
+ uint8_t tag[SHA256_DIGEST_LENGTH] = {0};
+ memzero(authentication_sum, sizeof(authentication_sum));
+ hmac_sha256(cached_sak, SAK_SIZE, authentication_sum,
+ sizeof(authentication_sum), tag);
+ return norcow_set(STORAGE_TAG_KEY, tag, STORAGE_TAG_SIZE);
+}
+
+/*
+ * Update the storage authentication tag with the given key.
+ */
+static secbool auth_update(uint16_t key) {
+ if (sectrue != is_protected(key)) {
+ return sectrue;
+ }
+
+ uint8_t tag[SHA256_DIGEST_LENGTH] = {0};
+ hmac_sha256(cached_sak, SAK_SIZE, (uint8_t *)&key, sizeof(key), tag);
+ for (uint32_t i = 0; i < SHA256_DIGEST_LENGTH; i++) {
+ authentication_sum[i] ^= tag[i];
+ }
+ hmac_sha256(cached_sak, SAK_SIZE, authentication_sum,
+ sizeof(authentication_sum), tag);
+ return norcow_set(STORAGE_TAG_KEY, tag, STORAGE_TAG_SIZE);
+}
+
+/*
+ * A secure version of norcow_set(), which updates the storage authentication
+ * tag.
+ */
+static secbool auth_set(uint16_t key, const void *val, uint16_t len) {
+ secbool found = secfalse;
+ secbool ret = norcow_set_ex(key, val, len, &found);
+ if (sectrue == ret && secfalse == found) {
+ ret = auth_update(key);
+ if (sectrue != ret) {
+ norcow_delete(key);
+ }
+ }
+ return ret;
+}
+
+/*
+ * A secure version of norcow_get(), which checks the storage authentication
+ * tag.
+ */
+static secbool auth_get(uint16_t key, const void **val, uint16_t *len) {
+ *val = NULL;
+ *len = 0;
+ uint32_t sum[SHA256_DIGEST_LENGTH / sizeof(uint32_t)] = {0};
+
+ // Prepare inner and outer digest.
+ uint32_t odig[SHA256_DIGEST_LENGTH / sizeof(uint32_t)] = {0};
+ uint32_t idig[SHA256_DIGEST_LENGTH / sizeof(uint32_t)] = {0};
+ hmac_sha256_prepare(cached_sak, SAK_SIZE, odig, idig);
+
+ // Prepare SHA-256 message padding.
+ uint32_t g[SHA256_BLOCK_LENGTH / sizeof(uint32_t)] = {0};
+ uint32_t h[SHA256_BLOCK_LENGTH / sizeof(uint32_t)] = {0};
+ g[15] = (SHA256_BLOCK_LENGTH + 2) * 8;
+ h[15] = (SHA256_BLOCK_LENGTH + SHA256_DIGEST_LENGTH) * 8;
+ h[8] = 0x80000000;
+
+ uint32_t offset = 0;
+ uint16_t k = 0;
+ uint16_t l = 0;
+ uint16_t tag_len = 0;
+ uint16_t entry_count = 0; // Mitigation against fault injection.
+ uint16_t other_count = 0; // Mitigation against fault injection.
+ const void *v = NULL;
+ const void *tag_val = NULL;
+ while (sectrue == norcow_get_next(&offset, &k, &v, &l)) {
+ ++entry_count;
+ if (k == key) {
+ *val = v;
+ *len = l;
+ } else {
+ ++other_count;
+ }
+ if (sectrue != is_protected(k)) {
+ if (k == STORAGE_TAG_KEY) {
+ tag_val = v;
+ tag_len = l;
+ }
+ continue;
+ }
+ g[0] = (((uint32_t)k & 0xff) << 24) | (((uint32_t)k & 0xff00) << 8) |
+ 0x8000; // Add SHA message padding.
+ sha256_Transform(idig, g, h);
+ sha256_Transform(odig, h, h);
+ for (uint32_t i = 0; i < SHA256_DIGEST_LENGTH / sizeof(uint32_t); i++) {
+ sum[i] ^= h[i];
+ }
+ }
+ memcpy(h, sum, sizeof(sum));
+
+ sha256_Transform(idig, h, h);
+ sha256_Transform(odig, h, h);
+
+ memzero(odig, sizeof(odig));
+ memzero(idig, sizeof(idig));
+
+ // Cache the authentication sum.
+ for (size_t i = 0; i < SHA256_DIGEST_LENGTH / sizeof(uint32_t); i++) {
+#if BYTE_ORDER == LITTLE_ENDIAN
+ REVERSE32(sum[i], ((uint32_t *)authentication_sum)[i]);
+#else
+ ((uint32_t *)authentication_sum)[i] = sum[i];
+#endif
+ }
+
+ // Check loop completion in case of a fault injection attack.
+ if (secfalse != norcow_get_next(&offset, &k, &v, &l)) {
+ handle_fault("loop completion check");
+ }
+
+ // Check storage authentication tag.
+#if BYTE_ORDER == LITTLE_ENDIAN
+ for (size_t i = 0; i < SHA256_DIGEST_LENGTH / sizeof(uint32_t); i++) {
+ REVERSE32(h[i], h[i]);
+ }
+#endif
+ if (tag_val == NULL || tag_len != STORAGE_TAG_SIZE ||
+ sectrue != secequal(h, tag_val, STORAGE_TAG_SIZE)) {
+ handle_fault("storage tag check");
+ }
+
+ if (*val == NULL) {
+ // Check for fault injection.
+ if (other_count != entry_count) {
+ handle_fault("sanity check");
+ }
+ return secfalse;
+ }
+ return sectrue;
+}
+
+static secbool set_wipe_code(const uint8_t *wipe_code, size_t wipe_code_len) {
+ if (wipe_code_len > MAX_WIPE_CODE_LEN ||
+ wipe_code_len > UINT16_MAX - WIPE_CODE_SALT_SIZE - WIPE_CODE_TAG_SIZE) {
+ return secfalse;
+ }
+
+ if (wipe_code_len == 0) {
+ // This is to avoid having to check pin != PIN_EMPTY when checking the wipe
+ // code.
+ wipe_code = WIPE_CODE_EMPTY;
+ wipe_code_len = WIPE_CODE_EMPTY_LEN;
+ }
+
+ // The format of the WIPE_CODE_DATA_KEY entry is:
+ // wipe code (variable), random salt (16 bytes), authentication tag (16 bytes)
+ // NOTE: We allocate extra space for the HMAC result.
+ uint8_t salt_and_tag[WIPE_CODE_SALT_SIZE + SHA256_DIGEST_LENGTH] = {0};
+ uint8_t *salt = salt_and_tag;
+ uint8_t *tag = salt_and_tag + WIPE_CODE_SALT_SIZE;
+
+ random_buffer(salt, WIPE_CODE_SALT_SIZE);
+ hmac_sha256(salt, WIPE_CODE_SALT_SIZE, wipe_code, wipe_code_len, tag);
+
+ // Preallocate the entry in the flash storage.
+ if (sectrue !=
+ norcow_set(WIPE_CODE_DATA_KEY, NULL,
+ wipe_code_len + WIPE_CODE_SALT_SIZE + WIPE_CODE_TAG_SIZE)) {
+ return secfalse;
+ }
+
+ // Write wipe code into the preallocated entry.
+ if (sectrue !=
+ norcow_update_bytes(WIPE_CODE_DATA_KEY, 0, wipe_code, wipe_code_len)) {
+ return secfalse;
+ }
+
+ // Write salt and tag into the preallocated entry.
+ if (sectrue !=
+ norcow_update_bytes(WIPE_CODE_DATA_KEY, wipe_code_len, salt_and_tag,
+ WIPE_CODE_SALT_SIZE + WIPE_CODE_TAG_SIZE)) {
+ return secfalse;
+ }
+
+ return sectrue;
+}
+
+static secbool is_not_wipe_code(const uint8_t *pin, size_t pin_len) {
+ uint8_t salt[WIPE_CODE_SALT_SIZE] = {0};
+ uint8_t stored_tag[WIPE_CODE_TAG_SIZE] = {0};
+ uint8_t computed_tag1[SHA256_DIGEST_LENGTH] = {0};
+ uint8_t computed_tag2[SHA256_DIGEST_LENGTH] = {0};
+
+ // Read the wipe code data from the storage.
+ const void *wipe_code_data = NULL;
+ uint16_t len = 0;
+ if (sectrue != norcow_get(WIPE_CODE_DATA_KEY, &wipe_code_data, &len) ||
+ len <= WIPE_CODE_SALT_SIZE + WIPE_CODE_TAG_SIZE) {
+ handle_fault("no wipe code");
+ return secfalse;
+ }
+ const uint8_t *wipe_code = (const uint8_t *)wipe_code_data;
+ size_t wipe_code_len = len - WIPE_CODE_SALT_SIZE - WIPE_CODE_TAG_SIZE;
+ memcpy(salt, (uint8_t *)wipe_code_data + wipe_code_len, sizeof(salt));
+ memcpy(stored_tag,
+ (uint8_t *)wipe_code_data + wipe_code_len + WIPE_CODE_SALT_SIZE,
+ sizeof(stored_tag));
+
+ // Check integrity in case of flash read manipulation attack.
+ hmac_sha256(salt, WIPE_CODE_SALT_SIZE, wipe_code, wipe_code_len,
+ computed_tag1);
+ if (sectrue != secequal(stored_tag, computed_tag1, sizeof(stored_tag))) {
+ handle_fault("wipe code tag");
+ return secfalse;
+ }
+
+ // Prepare the authentication tag of the entered PIN.
+ wait_random();
+ hmac_sha256(salt, WIPE_CODE_SALT_SIZE, pin, pin_len, computed_tag1);
+
+ // Recompute to check for fault injection attack.
+ wait_random();
+ hmac_sha256(salt, WIPE_CODE_SALT_SIZE, pin, pin_len, computed_tag2);
+ memzero(salt, sizeof(salt));
+ if (sectrue !=
+ secequal(computed_tag1, computed_tag2, sizeof(computed_tag1))) {
+ handle_fault("wipe code fault");
+ return secfalse;
+ }
+
+ // Compare wipe code with the entered PIN via the authentication tag.
+ wait_random();
+ if (secfalse != secequal(stored_tag, computed_tag1, sizeof(stored_tag))) {
+ return secfalse;
+ }
+ memzero(stored_tag, sizeof(stored_tag));
+ return sectrue;
+}
+
+static secbool ui_progress(uint32_t elapsed_ms) {
+ ui_rem -= elapsed_ms;
+ if (ui_callback && ui_message) {
+ uint32_t progress = 0;
+ if (ui_total < 1000000) {
+ progress = 1000 * (ui_total - ui_rem) / ui_total;
+ } else {
+ // Avoid overflow. Precise enough.
+ progress = (ui_total - ui_rem) / (ui_total / 1000);
+ }
+ // Round the remaining time to the nearest second.
+ return ui_callback((ui_rem + 500) / 1000, progress, ui_message);
+ } else {
+ return secfalse;
+ }
+}
+
+#if !USE_OPTIGA
+static void derive_kek(const uint8_t *pin, size_t pin_len,
+ const uint8_t *storage_salt, const uint8_t *ext_salt,
+ uint8_t kek[SHA256_DIGEST_LENGTH],
+ uint8_t keiv[SHA256_DIGEST_LENGTH]) {
+ uint8_t salt[HARDWARE_SALT_SIZE + STORAGE_SALT_SIZE + EXTERNAL_SALT_SIZE] = {
+ 0};
+ size_t salt_len = 0;
+
+ memcpy(salt + salt_len, hardware_salt, HARDWARE_SALT_SIZE);
+ salt_len += HARDWARE_SALT_SIZE;
+
+ memcpy(salt + salt_len, storage_salt, STORAGE_SALT_SIZE);
+ salt_len += STORAGE_SALT_SIZE;
+
+ if (ext_salt != NULL) {
+ memcpy(salt + salt_len, ext_salt, EXTERNAL_SALT_SIZE);
+ salt_len += EXTERNAL_SALT_SIZE;
+ }
+
+ PBKDF2_HMAC_SHA256_CTX ctx = {0};
+ pbkdf2_hmac_sha256_Init(&ctx, pin, pin_len, salt, salt_len, 1);
+ for (int i = 1; i <= 5; i++) {
+ pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10);
+ ui_progress(PIN_PBKDF2_MS / 10);
+ }
+ pbkdf2_hmac_sha256_Final(&ctx, kek);
+
+ pbkdf2_hmac_sha256_Init(&ctx, pin, pin_len, salt, salt_len, 2);
+ for (int i = 6; i <= 10; i++) {
+ pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10);
+ ui_progress(PIN_PBKDF2_MS / 10);
+ }
+ pbkdf2_hmac_sha256_Final(&ctx, keiv);
+
+ memzero(&ctx, sizeof(PBKDF2_HMAC_SHA256_CTX));
+ memzero(&salt, sizeof(salt));
+}
+#endif
+
+#if USE_OPTIGA
+static void stretch_pin_optiga(const uint8_t *pin, size_t pin_len,
+ const uint8_t storage_salt[STORAGE_SALT_SIZE],
+ const uint8_t *ext_salt,
+ uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE]) {
+ // Combining the PIN with the storage salt aims to ensure that if the
+ // MCU-Optiga communication is compromised, then a user with a low-entropy PIN
+ // remains protected against an attacker who is not able to read the contents
+ // of the MCU storage. Stretching the PIN with PBKDF2 ensures that even if
+ // Optiga itself is completely compromised, it will not reduce the security
+ // of the device below that of earlier Trezor models which also use PBKDF2
+ // with the same number of iterations.
+
+ uint8_t salt[HARDWARE_SALT_SIZE + STORAGE_SALT_SIZE + EXTERNAL_SALT_SIZE] = {
+ 0};
+ size_t salt_len = 0;
+
+ memcpy(salt + salt_len, hardware_salt, HARDWARE_SALT_SIZE);
+ salt_len += HARDWARE_SALT_SIZE;
+
+ memcpy(salt + salt_len, storage_salt, STORAGE_SALT_SIZE);
+ salt_len += STORAGE_SALT_SIZE;
+
+ if (ext_salt != NULL) {
+ memcpy(salt + salt_len, ext_salt, EXTERNAL_SALT_SIZE);
+ salt_len += EXTERNAL_SALT_SIZE;
+ }
+
+ PBKDF2_HMAC_SHA256_CTX ctx = {0};
+ pbkdf2_hmac_sha256_Init(&ctx, pin, pin_len, salt, salt_len, 1);
+ memzero(&salt, sizeof(salt));
+
+ for (int i = 1; i <= 10; i++) {
+ pbkdf2_hmac_sha256_Update(&ctx, PIN_ITER_COUNT / 10);
+ ui_progress(PIN_PBKDF2_MS / 10);
+ }
+ pbkdf2_hmac_sha256_Final(&ctx, stretched_pin);
+
+ memzero(&ctx, sizeof(ctx));
+}
+#endif
+
+#if USE_OPTIGA
+static void derive_kek_optiga(
+ const uint8_t optiga_secret[OPTIGA_PIN_SECRET_SIZE],
+ uint8_t kek[SHA256_DIGEST_LENGTH], uint8_t keiv[SHA256_DIGEST_LENGTH]) {
+ PBKDF2_HMAC_SHA256_CTX ctx = {0};
+ pbkdf2_hmac_sha256_Init(&ctx, optiga_secret, OPTIGA_PIN_SECRET_SIZE, NULL, 0,
+ 1);
+ pbkdf2_hmac_sha256_Update(&ctx, 1);
+ pbkdf2_hmac_sha256_Final(&ctx, kek);
+
+ pbkdf2_hmac_sha256_Init(&ctx, optiga_secret, OPTIGA_PIN_SECRET_SIZE, NULL, 0,
+ 2);
+ pbkdf2_hmac_sha256_Update(&ctx, 1);
+ pbkdf2_hmac_sha256_Final(&ctx, keiv);
+
+ memzero(&ctx, sizeof(ctx));
+}
+#endif
+
+static secbool __wur derive_kek_set(const uint8_t *pin, size_t pin_len,
+ const uint8_t *storage_salt,
+ const uint8_t *ext_salt,
+ uint8_t kek[SHA256_DIGEST_LENGTH],
+ uint8_t keiv[SHA256_DIGEST_LENGTH]) {
+#if USE_OPTIGA
+ uint8_t optiga_secret[OPTIGA_PIN_SECRET_SIZE] = {0};
+ uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE] = {0};
+ stretch_pin_optiga(pin, pin_len, storage_salt, ext_salt, stretched_pin);
+ int ret = optiga_pin_set(ui_progress, stretched_pin, optiga_secret);
+ memzero(stretched_pin, sizeof(stretched_pin));
+ if (ret != OPTIGA_SUCCESS) {
+ memzero(optiga_secret, sizeof(optiga_secret));
+ return secfalse;
+ }
+ derive_kek_optiga(optiga_secret, kek, keiv);
+ memzero(optiga_secret, sizeof(optiga_secret));
+#else
+ derive_kek(pin, pin_len, storage_salt, ext_salt, kek, keiv);
+#endif
+ return sectrue;
+}
+
+static secbool __wur derive_kek_unlock(const uint8_t *pin, size_t pin_len,
+ const uint8_t *storage_salt,
+ const uint8_t *ext_salt,
+ uint8_t kek[SHA256_DIGEST_LENGTH],
+ uint8_t keiv[SHA256_DIGEST_LENGTH]) {
+#if USE_OPTIGA
+ uint8_t optiga_secret[OPTIGA_PIN_SECRET_SIZE] = {0};
+ uint8_t stretched_pin[OPTIGA_PIN_SECRET_SIZE] = {0};
+ stretch_pin_optiga(pin, pin_len, storage_salt, ext_salt, stretched_pin);
+ int ret = optiga_pin_verify(ui_progress, stretched_pin, optiga_secret);
+ memzero(stretched_pin, sizeof(stretched_pin));
+ if (ret != OPTIGA_SUCCESS) {
+ memzero(optiga_secret, sizeof(optiga_secret));
+ if (ret == OPTIGA_ERR_COUNTER_EXCEEDED) {
+ // Unreachable code. Wipe should have already been triggered in unlock().
+ storage_wipe();
+ show_pin_too_many_screen();
+ }
+ ensure(ret == OPTIGA_ERR_AUTH_FAIL ? sectrue : secfalse,
+ "optiga_pin_verify failed");
+ return secfalse;
+ }
+ derive_kek_optiga(optiga_secret, kek, keiv);
+ memzero(optiga_secret, sizeof(optiga_secret));
+#else
+ derive_kek(pin, pin_len, storage_salt, ext_salt, kek, keiv);
+#endif
+ return sectrue;
+}
+
+static secbool set_pin(const uint8_t *pin, size_t pin_len,
+ const uint8_t *ext_salt) {
+ // Encrypt the cached keys using the new PIN and set the new PVC.
+ uint8_t buffer[STORAGE_SALT_SIZE + KEYS_SIZE + POLY1305_TAG_SIZE] = {0};
+ uint8_t *rand_salt = buffer;
+ uint8_t *ekeys = buffer + STORAGE_SALT_SIZE;
+ uint8_t *pvc = buffer + STORAGE_SALT_SIZE + KEYS_SIZE;
+
+ uint8_t kek[SHA256_DIGEST_LENGTH] = {0};
+ uint8_t keiv[SHA256_DIGEST_LENGTH] = {0};
+ chacha20poly1305_ctx ctx = {0};
+ random_buffer(rand_salt, STORAGE_SALT_SIZE);
+ ui_progress(0);
+ ensure(derive_kek_set(pin, pin_len, rand_salt, ext_salt, kek, keiv),
+ "derive_kek_set failed");
+ rfc7539_init(&ctx, kek, keiv);
+ memzero(kek, sizeof(kek));
+ memzero(keiv, sizeof(keiv));
+ chacha20poly1305_encrypt(&ctx, cached_keys, ekeys, KEYS_SIZE);
+ rfc7539_finish(&ctx, 0, KEYS_SIZE, pvc);
+ memzero(&ctx, sizeof(ctx));
+ secbool ret = norcow_set(EDEK_PVC_KEY, buffer,
+ STORAGE_SALT_SIZE + KEYS_SIZE + PVC_SIZE);
+ memzero(buffer, sizeof(buffer));
+
+ if (ret == sectrue) {
+ if (pin_len == 0) {
+ ret = norcow_set(PIN_NOT_SET_KEY, &TRUE_BYTE, sizeof(TRUE_BYTE));
+ } else {
+ ret = norcow_set(PIN_NOT_SET_KEY, &FALSE_BYTE, sizeof(FALSE_BYTE));
+ }
+ }
+
+ return ret;
+}
+
+static secbool check_guard_key(const uint32_t guard_key) {
+ if (guard_key % GUARD_KEY_MODULUS != GUARD_KEY_REMAINDER) {
+ return secfalse;
+ }
+
+ // Check that each byte of (guard_key & 0xAAAAAAAA) has exactly two bits set.
+ uint32_t count = (guard_key & 0x22222222) + ((guard_key >> 2) & 0x22222222);
+ count = count + (count >> 4);
+ if ((count & 0x0e0e0e0e) != 0x04040404) {
+ return secfalse;
+ }
+
+ // Check that the guard_key does not contain a run of 5 (or more) zeros or
+ // ones.
+ uint32_t zero_runs = ~guard_key;
+ zero_runs = zero_runs & (zero_runs >> 2);
+ zero_runs = zero_runs & (zero_runs >> 1);
+ zero_runs = zero_runs & (zero_runs >> 1);
+
+ uint32_t one_runs = guard_key;
+ one_runs = one_runs & (one_runs >> 2);
+ one_runs = one_runs & (one_runs >> 1);
+ one_runs = one_runs & (one_runs >> 1);
+
+ if ((one_runs != 0) || (zero_runs != 0)) {
+ return secfalse;
+ }
+
+ return sectrue;
+}
+
+static uint32_t generate_guard_key(void) {
+ uint32_t guard_key = 0;
+ do {
+ guard_key = random_uniform((UINT32_MAX / GUARD_KEY_MODULUS) + 1) *
+ GUARD_KEY_MODULUS +
+ GUARD_KEY_REMAINDER;
+ } while (sectrue != check_guard_key(guard_key));
+ return guard_key;
+}
+
+static secbool expand_guard_key(const uint32_t guard_key, uint32_t *guard_mask,
+ uint32_t *guard) {
+ if (sectrue != check_guard_key(guard_key)) {
+ handle_fault("guard key check");
+ return secfalse;
+ }
+ *guard_mask = ((guard_key & LOW_MASK) << 1) | ((~guard_key) & LOW_MASK);
+ *guard = (((guard_key & LOW_MASK) << 1) & guard_key) |
+ (((~guard_key) & LOW_MASK) & (guard_key >> 1));
+ return sectrue;
+}
+
+static secbool pin_logs_init(uint32_t fails) {
+ if (fails >= PIN_MAX_TRIES) {
+ return secfalse;
+ }
+
+ // The format of the PIN_LOGS_KEY entry is:
+ // guard_key (1 word), pin_success_log (PIN_LOG_WORDS), pin_entry_log
+ // (PIN_LOG_WORDS)
+ uint32_t logs[GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS] = {0};
+
+ logs[0] = generate_guard_key();
+
+ uint32_t guard_mask = 0;
+ uint32_t guard = 0;
+ wait_random();
+ if (sectrue != expand_guard_key(logs[0], &guard_mask, &guard)) {
+ return secfalse;
+ }
+
+ uint32_t unused = guard | ~guard_mask;
+ for (size_t i = 0; i < 2 * PIN_LOG_WORDS; ++i) {
+ logs[GUARD_KEY_WORDS + i] = unused;
+ }
+
+ // Set the first word of the PIN entry log to indicate the requested number of
+ // fails.
+ logs[GUARD_KEY_WORDS + PIN_LOG_WORDS] =
+ ((((uint32_t)0xFFFFFFFF) >> (2 * fails)) & ~guard_mask) | guard;
+
+ return norcow_set(PIN_LOGS_KEY, logs, sizeof(logs));
+}
+
+/*
+ * Initializes the values of VERSION_KEY, EDEK_PVC_KEY, PIN_NOT_SET_KEY and
+ * PIN_LOGS_KEY using an empty PIN. This function should be called to initialize
+ * freshly wiped storage.
+ */
+static void init_wiped_storage(void) {
+ if (sectrue != initialized) {
+ // We cannot initialize the storage contents if the hardware_salt is not
+ // set.
+ return;
+ }
+
+#if USE_OPTIGA
+ ensure(optiga_random_buffer(cached_keys, sizeof(cached_keys)) ? sectrue
+ : secfalse,
+ "optiga_random_buffer failed");
+ random_xor(cached_keys, sizeof(cached_keys));
+#else
+ random_buffer(cached_keys, sizeof(cached_keys));
+#endif
+ unlocked = sectrue;
+ uint32_t version = NORCOW_VERSION;
+ ensure(auth_init(), "set_storage_auth_tag failed");
+ ensure(storage_set_encrypted(VERSION_KEY, &version, sizeof(version)),
+ "set_storage_version failed");
+ ensure(norcow_set(UNAUTH_VERSION_KEY, &version, sizeof(version)),
+ "set_unauth_storage_version failed");
+ ensure(norcow_set(STORAGE_UPGRADED_KEY, &FALSE_WORD, sizeof(FALSE_WORD)),
+ "set_storage_not_upgraded failed");
+ ensure(pin_logs_init(0), "init_pin_logs failed");
+ ensure(set_wipe_code(WIPE_CODE_EMPTY, WIPE_CODE_EMPTY_LEN),
+ "set_wipe_code failed");
+
+ ui_total = PIN_DERIVE_MS;
+ ui_rem = ui_total;
+ ui_message = PROCESSING_MSG;
+ ensure(set_pin(PIN_EMPTY, PIN_EMPTY_LEN, NULL), "init_pin failed");
+}
+
+void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt,
+ const uint16_t salt_len) {
+ initialized = secfalse;
+ unlocked = secfalse;
+ norcow_init(&norcow_active_version);
+ initialized = sectrue;
+ ui_callback = callback;
+
+ sha256_Raw(salt, salt_len, hardware_salt);
+
+ if (norcow_active_version < NORCOW_VERSION) {
+ if (sectrue != storage_upgrade()) {
+ storage_wipe();
+ ensure(secfalse, "storage_upgrade failed");
+ }
+ }
+
+ // If there is no EDEK, then generate a random DEK and SAK and store them.
+ const void *val = NULL;
+ uint16_t len = 0;
+ if (secfalse == norcow_get(EDEK_PVC_KEY, &val, &len)) {
+ init_wiped_storage();
+ storage_lock();
+ }
+ memzero(cached_keys, sizeof(cached_keys));
+}
+
+static secbool pin_fails_reset(void) {
+ const void *logs = NULL;
+ uint16_t len = 0;
+
+ if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) ||
+ len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) {
+ return secfalse;
+ }
+
+ uint32_t guard_mask = 0;
+ uint32_t guard = 0;
+ wait_random();
+ if (sectrue !=
+ expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) {
+ return secfalse;
+ }
+
+ uint32_t unused = guard | ~guard_mask;
+ const uint32_t *success_log = ((const uint32_t *)logs) + GUARD_KEY_WORDS;
+ const uint32_t *entry_log = success_log + PIN_LOG_WORDS;
+ for (size_t i = 0; i < PIN_LOG_WORDS; ++i) {
+ if (entry_log[i] == unused) {
+ return sectrue;
+ }
+ if (success_log[i] != guard) {
+ if (sectrue != norcow_update_word(
+ PIN_LOGS_KEY, sizeof(uint32_t) * (i + GUARD_KEY_WORDS),
+ entry_log[i])) {
+ return secfalse;
+ }
+ }
+ }
+ return pin_logs_init(0);
+}
+
+secbool storage_pin_fails_increase(void) {
+ if (sectrue != initialized) {
+ return secfalse;
+ }
+
+ const void *logs = NULL;
+ uint16_t len = 0;
+
+ wait_random();
+ if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) ||
+ len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) {
+ handle_fault("no PIN logs");
+ return secfalse;
+ }
+
+ uint32_t guard_mask = 0;
+ uint32_t guard = 0;
+ wait_random();
+ if (sectrue !=
+ expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) {
+ handle_fault("guard key expansion");
+ return secfalse;
+ }
+
+ const uint32_t *entry_log =
+ ((const uint32_t *)logs) + GUARD_KEY_WORDS + PIN_LOG_WORDS;
+ for (size_t i = 0; i < PIN_LOG_WORDS; ++i) {
+ wait_random();
+ if ((entry_log[i] & guard_mask) != guard) {
+ handle_fault("guard bits check");
+ return secfalse;
+ }
+ if (entry_log[i] != guard) {
+ wait_random();
+ uint32_t word = entry_log[i] & ~guard_mask;
+ word = ((word >> 1) | word) & LOW_MASK;
+ word = (word >> 2) | (word >> 1);
+
+ wait_random();
+ if (sectrue !=
+ norcow_update_word(
+ PIN_LOGS_KEY,
+ sizeof(uint32_t) * (i + GUARD_KEY_WORDS + PIN_LOG_WORDS),
+ (word & ~guard_mask) | guard)) {
+ handle_fault("PIN logs update");
+ return secfalse;
+ }
+ return sectrue;
+ }
+ }
+ handle_fault("PIN log exhausted");
+ return secfalse;
+}
+
+static uint32_t hamming_weight(uint32_t value) {
+ value = value - ((value >> 1) & 0x55555555);
+ value = (value & 0x33333333) + ((value >> 2) & 0x33333333);
+ value = (value + (value >> 4)) & 0x0F0F0F0F;
+ value = value + (value >> 8);
+ value = value + (value >> 16);
+ return value & 0x3F;
+}
+
+static secbool pin_get_fails(uint32_t *ctr) {
+ *ctr = PIN_MAX_TRIES;
+
+ const void *logs = NULL;
+ uint16_t len = 0;
+ wait_random();
+ if (sectrue != norcow_get(PIN_LOGS_KEY, &logs, &len) ||
+ len != WORD_SIZE * (GUARD_KEY_WORDS + 2 * PIN_LOG_WORDS)) {
+ handle_fault("no PIN logs");
+ return secfalse;
+ }
+
+ uint32_t guard_mask = 0;
+ uint32_t guard = 0;
+ wait_random();
+ if (sectrue !=
+ expand_guard_key(*(const uint32_t *)logs, &guard_mask, &guard)) {
+ handle_fault("guard key expansion");
+ return secfalse;
+ }
+ const uint32_t unused = guard | ~guard_mask;
+
+ const uint32_t *success_log = ((const uint32_t *)logs) + GUARD_KEY_WORDS;
+ const uint32_t *entry_log = success_log + PIN_LOG_WORDS;
+ volatile int current = -1;
+ volatile size_t i = 0;
+ for (i = 0; i < PIN_LOG_WORDS; ++i) {
+ if ((entry_log[i] & guard_mask) != guard ||
+ (success_log[i] & guard_mask) != guard ||
+ (entry_log[i] & success_log[i]) != entry_log[i]) {
+ handle_fault("PIN logs format check");
+ return secfalse;
+ }
+
+ if (current == -1) {
+ if (entry_log[i] != guard) {
+ current = i;
+ }
+ } else {
+ if (entry_log[i] != unused) {
+ handle_fault("PIN entry log format check");
+ return secfalse;
+ }
+ }
+ }
+
+ if (current < 0 || current >= PIN_LOG_WORDS || i != PIN_LOG_WORDS) {
+ handle_fault("PIN log exhausted");
+ return secfalse;
+ }
+
+ // Strip the guard bits from the current entry word and duplicate each data
+ // bit.
+ wait_random();
+ uint32_t word = entry_log[current] & ~guard_mask;
+ word = ((word >> 1) | word) & LOW_MASK;
+ word = word | (word << 1);
+ // Verify that the entry word has form 0*1*.
+ if ((word & (word + 1)) != 0) {
+ handle_fault("PIN entry log format check");
+ return secfalse;
+ }
+
+ if (current == 0) {
+ ++current;
+ }
+
+ // Count the number of set bits in the two current words of the success log.
+ wait_random();
+ *ctr = hamming_weight(success_log[current - 1] ^ entry_log[current - 1]) +
+ hamming_weight(success_log[current] ^ entry_log[current]);
+ return sectrue;
+}
+
+secbool storage_is_unlocked(void) {
+ if (sectrue != initialized) {
+ return secfalse;
+ }
+
+ return unlocked;
+}
+
+void storage_lock(void) {
+ unlocked = secfalse;
+ memzero(cached_keys, sizeof(cached_keys));
+ memzero(authentication_sum, sizeof(authentication_sum));
+}
+
+// Returns the storage version that was used to lock the storage.
+static uint32_t get_lock_version(void) {
+ const void *val = NULL;
+ uint16_t len = 0;
+ if (sectrue != norcow_get(UNAUTH_VERSION_KEY, &val, &len) ||
+ len != sizeof(uint32_t)) {
+ handle_fault("no lock version");
+ }
+
+ return *(uint32_t *)val;
+}
+
+secbool check_storage_version(void) {
+ uint32_t version = 0;
+ uint16_t len = 0;
+ if (sectrue !=
+ storage_get_encrypted(VERSION_KEY, &version, sizeof(version), &len) ||
+ len != sizeof(version)) {
+ handle_fault("storage version check");
+ return secfalse;
+ }
+
+ if (version != get_lock_version()) {
+ handle_fault("storage version check");
+ return secfalse;
+ }
+
+ const void *storage_upgraded = NULL;
+ if (sectrue != norcow_get(STORAGE_UPGRADED_KEY, &storage_upgraded, &len) ||
+ len != sizeof(TRUE_WORD)) {
+ handle_fault("storage version check");
+ return secfalse;
+ }
+
+ if (version > norcow_active_version) {
+ // Attack: Storage was downgraded.
+ storage_wipe();
+ handle_fault("storage version check");
+ return secfalse;
+ } else if (version < norcow_active_version) {
+ // Storage was upgraded.
+ if (*(const uint32_t *)storage_upgraded != TRUE_WORD) {
+ // Attack: The upgrade process was bypassed.
+ storage_wipe();
+ handle_fault("storage version check");
+ return secfalse;
+ }
+ norcow_set(STORAGE_UPGRADED_KEY, &FALSE_WORD, sizeof(FALSE_WORD));
+ storage_set_encrypted(VERSION_KEY, &norcow_active_version,
+ sizeof(norcow_active_version));
+ norcow_set(UNAUTH_VERSION_KEY, &norcow_active_version,
+ sizeof(norcow_active_version));
+ } else {
+ // Standard operation. The storage was neither upgraded nor downgraded.
+ if (*(const uint32_t *)storage_upgraded != FALSE_WORD) {
+ // Attack: The upgrade process was launched when it shouldn't have been.
+ storage_wipe();
+ handle_fault("storage version check");
+ return secfalse;
+ }
+ }
+ return sectrue;
+}
+
+static secbool decrypt_dek(const uint8_t *kek, const uint8_t *keiv) {
+ const void *buffer = NULL;
+ uint16_t len = 0;
+ if (sectrue != initialized ||
+ sectrue != norcow_get(EDEK_PVC_KEY, &buffer, &len) ||
+ len != STORAGE_SALT_SIZE + KEYS_SIZE + PVC_SIZE) {
+ handle_fault("no EDEK");
+ return secfalse;
+ }
+
+ const uint8_t *ekeys = (const uint8_t *)buffer + STORAGE_SALT_SIZE;
+ const uint32_t *pvc = (const uint32_t *)buffer +
+ (STORAGE_SALT_SIZE + KEYS_SIZE) / sizeof(uint32_t);
+ _Static_assert(((STORAGE_SALT_SIZE + KEYS_SIZE) & 3) == 0, "PVC unaligned");
+ _Static_assert((PVC_SIZE & 3) == 0, "PVC size unaligned");
+
+ uint8_t keys[KEYS_SIZE] = {0};
+ uint8_t tag[POLY1305_TAG_SIZE] __attribute__((aligned(sizeof(uint32_t))));
+ chacha20poly1305_ctx ctx = {0};
+
+ // Decrypt the data encryption key and the storage authentication key and
+ // check the PIN verification code.
+ rfc7539_init(&ctx, kek, keiv);
+ chacha20poly1305_decrypt(&ctx, ekeys, keys, KEYS_SIZE);
+ rfc7539_finish(&ctx, 0, KEYS_SIZE, tag);
+ memzero(&ctx, sizeof(ctx));
+ wait_random();
+ if (secequal32(tag, pvc, PVC_SIZE) != sectrue) {
+ memzero(keys, sizeof(keys));
+ memzero(tag, sizeof(tag));
+ return secfalse;
+ }
+ memcpy(cached_keys, keys, sizeof(keys));
+ memzero(keys, sizeof(keys));
+ memzero(tag, sizeof(tag));
+ return sectrue;
+}
+
+static void ensure_not_wipe_code(const uint8_t *pin, size_t pin_len) {
+ if (sectrue != is_not_wipe_code(pin, pin_len)) {
+ storage_wipe();
+ show_wipe_code_screen();
+ }
+}
+
+static secbool unlock(const uint8_t *pin, size_t pin_len,
+ const uint8_t *ext_salt) {
+ const uint8_t *unlock_pin = pin;
+ size_t unlock_pin_len = pin_len;
+
+ // In case of an upgrade from version 1 or 2, encode the PIN to the old format
+ // and bump the total time of UI progress to account for the set_pin() call in
+ // storage_upgrade_unlocked().
+ uint32_t legacy_pin = 0;
+ if (get_lock_version() <= 2) {
+ ui_total += PIN_DERIVE_MS;
+ ui_rem += PIN_DERIVE_MS;
+ legacy_pin = pin_to_int(pin, pin_len);
+ unlock_pin = (const uint8_t *)&legacy_pin;
+ unlock_pin_len = sizeof(legacy_pin);
+ }
+
+ // Now we can check for wipe code.
+ ensure_not_wipe_code(unlock_pin, unlock_pin_len);
+
+ // Get the pin failure counter
+ uint32_t ctr = 0;
+ if (sectrue != pin_get_fails(&ctr)) {
+ memzero(&legacy_pin, sizeof(legacy_pin));
+ return secfalse;
+ }
+
+ // Wipe storage if too many failures
+ wait_random();
+ if (ctr >= PIN_MAX_TRIES) {
+ storage_wipe();
+ show_pin_too_many_screen();
+ return secfalse;
+ }
+
+ // Sleep for 2^ctr - 1 seconds before checking the PIN.
+ uint32_t wait = (1 << ctr) - 1;
+ ui_total += wait * 1000;
+ ui_rem += wait * 1000;
+ ui_progress(0);
+ for (uint32_t i = 0; i < 10 * wait; i++) {
+ if (sectrue == ui_progress(100)) {
+ memzero(&legacy_pin, sizeof(legacy_pin));
+ return secfalse;
+ }
+ hal_delay(100);
+ }
+
+ // 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 != storage_pin_fails_increase()) {
+ return secfalse;
+ }
+
+ // Check that the PIN fail counter was incremented.
+ uint32_t ctr_ck = 0;
+ if (sectrue != pin_get_fails(&ctr_ck) || ctr + 1 != ctr_ck) {
+ handle_fault("PIN counter increment");
+ return secfalse;
+ }
+
+ // Read the random salt from EDEK_PVC_KEY and use it to derive the KEK and
+ // KEIV from the PIN.
+ const void *rand_salt = NULL;
+ uint16_t len = 0;
+ if (sectrue != initialized ||
+ sectrue != norcow_get(EDEK_PVC_KEY, &rand_salt, &len) ||
+ len != STORAGE_SALT_SIZE + KEYS_SIZE + PVC_SIZE) {
+ memzero(&legacy_pin, sizeof(legacy_pin));
+ handle_fault("no EDEK");
+ return secfalse;
+ }
+ uint8_t kek[SHA256_DIGEST_LENGTH] = {0};
+ uint8_t keiv[SHA256_DIGEST_LENGTH] = {0};
+
+ // Check whether the entered PIN is correct.
+ if (sectrue != derive_kek_unlock(unlock_pin, unlock_pin_len,
+ (const uint8_t *)rand_salt, ext_salt, kek,
+ keiv) ||
+ sectrue != decrypt_dek(kek, keiv)) {
+ memzero(&legacy_pin, sizeof(legacy_pin));
+ // Wipe storage if too many failures
+ wait_random();
+ if (ctr + 1 >= PIN_MAX_TRIES) {
+ storage_wipe();
+ show_pin_too_many_screen();
+ }
+
+ // Finish the countdown. Check for ui_rem underflow.
+ while (0 < ui_rem && ui_rem < ui_total) {
+ ui_message = WRONG_PIN_MSG;
+ if (sectrue == ui_progress(100)) {
+ return secfalse;
+ }
+ hal_delay(100);
+ }
+
+ return secfalse;
+ }
+ memzero(&legacy_pin, sizeof(legacy_pin));
+ memzero(kek, sizeof(kek));
+ memzero(keiv, sizeof(keiv));
+
+ // Check for storage upgrades that need to be performed after unlocking and
+ // check that the authenticated version number matches the unauthenticated
+ // version and norcow version.
+ // NOTE: This also initializes the authentication_sum by calling
+ // storage_get_encrypted() which calls auth_get().
+ if (sectrue != storage_upgrade_unlocked(pin, pin_len, ext_salt) ||
+ sectrue != check_storage_version()) {
+ return secfalse;
+ }
+
+ unlocked = sectrue;
+
+ // Finally set the counter to 0 to indicate success.
+ return pin_fails_reset();
+}
+
+secbool storage_unlock(const uint8_t *pin, size_t pin_len,
+ const uint8_t *ext_salt) {
+ if (sectrue != initialized || pin == NULL) {
+ return secfalse;
+ }
+
+ ui_total = PIN_DERIVE_MS;
+ ui_rem = ui_total;
+ if (pin_len == 0) {
+ if (ui_message == NULL) {
+ ui_message = STARTING_MSG;
+ } else {
+ ui_message = PROCESSING_MSG;
+ }
+ } else {
+ ui_message = VERIFYING_PIN_MSG;
+ }
+ return unlock(pin, pin_len, ext_salt);
+}
+
+/*
+ * Finds the encrypted data stored under key and writes its length to len.
+ * If val_dest is not NULL and max_len >= len, then the data is decrypted
+ * to val_dest using cached_dek as the decryption key.
+ */
+static secbool storage_get_encrypted(const uint16_t key, void *val_dest,
+ const uint16_t max_len, uint16_t *len) {
+ const void *val_stored = NULL;
+
+ if (sectrue != auth_get(key, &val_stored, len)) {
+ return secfalse;
+ }
+
+ if (*len < CHACHA20_IV_SIZE + POLY1305_TAG_SIZE) {
+ handle_fault("ciphertext length check");
+ return secfalse;
+ }
+ *len -= CHACHA20_IV_SIZE + POLY1305_TAG_SIZE;
+
+ if (val_dest == NULL) {
+ return sectrue;
+ }
+
+ if (*len > max_len) {
+ return secfalse;
+ }
+
+ const uint8_t *iv = (const uint8_t *)val_stored;
+ const uint8_t *tag_stored = (const uint8_t *)val_stored + CHACHA20_IV_SIZE;
+ const uint8_t *ciphertext =
+ (const uint8_t *)val_stored + CHACHA20_IV_SIZE + POLY1305_TAG_SIZE;
+ uint8_t tag_computed[POLY1305_TAG_SIZE] = {0};
+ chacha20poly1305_ctx ctx = {0};
+ rfc7539_init(&ctx, cached_dek, iv);
+ rfc7539_auth(&ctx, (const uint8_t *)&key, sizeof(key));
+ chacha20poly1305_decrypt(&ctx, ciphertext, (uint8_t *)val_dest, *len);
+ rfc7539_finish(&ctx, sizeof(key), *len, tag_computed);
+ memzero(&ctx, sizeof(ctx));
+
+ // Verify authentication tag.
+ if (secequal(tag_computed, tag_stored, POLY1305_TAG_SIZE) != sectrue) {
+ memzero(val_dest, max_len);
+ memzero(tag_computed, sizeof(tag_computed));
+ handle_fault("authentication tag check");
+ return secfalse;
+ }
+
+ memzero(tag_computed, sizeof(tag_computed));
+ return sectrue;
+}
+
+secbool storage_has(const uint16_t key) {
+ uint16_t len = 0;
+ return storage_get(key, NULL, 0, &len);
+}
+
+/*
+ * Finds the data stored under key and writes its length to len. If val_dest is
+ * not NULL and max_len >= len, then the data is copied to val_dest.
+ */
+secbool storage_get(const uint16_t key, void *val_dest, const uint16_t max_len,
+ uint16_t *len) {
+ const uint8_t app = key >> 8;
+ // APP == 0 is reserved for PIN related values
+ if (sectrue != initialized || app == APP_STORAGE) {
+ return secfalse;
+ }
+
+ // If the top bit of APP is set, then the value is not encrypted and can be
+ // read from a locked device.
+ if ((app & FLAG_PUBLIC) != 0) {
+ const void *val_stored = NULL;
+ if (sectrue != norcow_get(key, &val_stored, len)) {
+ return secfalse;
+ }
+ if (val_dest == NULL) {
+ return sectrue;
+ }
+ if (*len > max_len) {
+ return secfalse;
+ }
+ memcpy(val_dest, val_stored, *len);
+ return sectrue;
+ } else {
+ if (sectrue != unlocked) {
+ return secfalse;
+ }
+ return storage_get_encrypted(key, val_dest, max_len, len);
+ }
+}
+
+/*
+ * Encrypts the data at val using cached_dek as the encryption key and stores
+ * the ciphertext under key.
+ */
+static secbool storage_set_encrypted(const uint16_t key, const void *val,
+ const uint16_t len) {
+ if (len > UINT16_MAX - CHACHA20_IV_SIZE - POLY1305_TAG_SIZE) {
+ return secfalse;
+ }
+
+ // Preallocate space on the flash storage.
+ if (sectrue !=
+ auth_set(key, NULL, CHACHA20_IV_SIZE + POLY1305_TAG_SIZE + len)) {
+ return secfalse;
+ }
+
+ // Write the IV to the flash.
+ uint8_t buffer[CHACHA20_BLOCK_SIZE] = {0};
+ random_buffer(buffer, CHACHA20_IV_SIZE);
+ uint16_t offset = 0;
+ if (sectrue != norcow_update_bytes(key, offset, buffer, CHACHA20_IV_SIZE)) {
+ return secfalse;
+ }
+ offset += CHACHA20_IV_SIZE + POLY1305_TAG_SIZE;
+
+ // Encrypt all blocks except for the last one.
+ chacha20poly1305_ctx ctx = {0};
+ rfc7539_init(&ctx, cached_dek, buffer);
+ rfc7539_auth(&ctx, (const uint8_t *)&key, sizeof(key));
+ size_t i = 0;
+ for (i = 0; i + CHACHA20_BLOCK_SIZE < len;
+ i += CHACHA20_BLOCK_SIZE, offset += CHACHA20_BLOCK_SIZE) {
+ chacha20poly1305_encrypt(&ctx, ((const uint8_t *)val) + i, buffer,
+ CHACHA20_BLOCK_SIZE);
+ if (sectrue !=
+ norcow_update_bytes(key, offset, buffer, CHACHA20_BLOCK_SIZE)) {
+ memzero(&ctx, sizeof(ctx));
+ memzero(buffer, sizeof(buffer));
+ return secfalse;
+ }
+ }
+
+ // Encrypt final block and compute message authentication tag.
+ chacha20poly1305_encrypt(&ctx, ((const uint8_t *)val) + i, buffer, len - i);
+ secbool ret = norcow_update_bytes(key, offset, buffer, len - i);
+ if (sectrue == ret) {
+ rfc7539_finish(&ctx, sizeof(key), len, buffer);
+ ret = norcow_update_bytes(key, CHACHA20_IV_SIZE, buffer, POLY1305_TAG_SIZE);
+ }
+ memzero(&ctx, sizeof(ctx));
+ memzero(buffer, sizeof(buffer));
+ return ret;
+}
+
+secbool storage_set(const uint16_t key, const void *val, const uint16_t len) {
+ const uint8_t app = key >> 8;
+
+ // APP == 0 is reserved for PIN related values
+ if (sectrue != initialized || app == APP_STORAGE) {
+ return secfalse;
+ }
+
+ if (sectrue != unlocked && (app & FLAGS_WRITE) != FLAGS_WRITE) {
+ return secfalse;
+ }
+
+ secbool ret = secfalse;
+ if ((app & FLAG_PUBLIC) != 0) {
+ ret = norcow_set(key, val, len);
+ } else {
+ ret = storage_set_encrypted(key, val, len);
+ }
+ return ret;
+}
+
+secbool storage_delete(const uint16_t key) {
+ const uint8_t app = key >> 8;
+
+ // APP == 0 is reserved for storage related values
+ if (sectrue != initialized || app == APP_STORAGE) {
+ return secfalse;
+ }
+
+ if (sectrue != unlocked && (app & FLAGS_WRITE) != FLAGS_WRITE) {
+ return secfalse;
+ }
+
+ secbool ret = norcow_delete(key);
+ if (sectrue == ret) {
+ ret = auth_update(key);
+ }
+ return ret;
+}
+
+secbool storage_set_counter(const uint16_t key, const uint32_t count) {
+ const uint8_t app = key >> 8;
+ if ((app & FLAG_PUBLIC) == 0) {
+ return secfalse;
+ }
+
+ // The count is stored as a 32-bit integer followed by a tail of "1" bits,
+ // which is used as a tally.
+ uint32_t value[1 + COUNTER_TAIL_WORDS] = {0};
+ memset(value, 0xff, sizeof(value));
+ value[0] = count;
+ return storage_set(key, value, sizeof(value));
+}
+
+secbool storage_next_counter(const uint16_t key, uint32_t *count) {
+ const uint8_t app = key >> 8;
+ // APP == 0 is reserved for PIN related values
+ if (sectrue != initialized || app == APP_STORAGE ||
+ (app & FLAG_PUBLIC) == 0) {
+ return secfalse;
+ }
+
+ if (sectrue != unlocked && (app & FLAGS_WRITE) != FLAGS_WRITE) {
+ return secfalse;
+ }
+
+ uint16_t len = 0;
+ const uint32_t *val_stored = NULL;
+ if (sectrue != norcow_get(key, (const void **)&val_stored, &len)) {
+ *count = 0;
+ return storage_set_counter(key, 0);
+ }
+
+ if (len < sizeof(uint32_t) || len % sizeof(uint32_t) != 0) {
+ return secfalse;
+ }
+ uint16_t len_words = len / sizeof(uint32_t);
+
+ uint16_t i = 1;
+ while (i < len_words && val_stored[i] == 0) {
+ ++i;
+ }
+
+ *count = val_stored[0] + 1 + 32 * (i - 1);
+ if (*count < val_stored[0]) {
+ // Value overflow.
+ return secfalse;
+ }
+
+ if (i < len_words) {
+ *count += hamming_weight(~val_stored[i]);
+ if (*count < val_stored[0]) {
+ // Value overflow.
+ return secfalse;
+ }
+ return norcow_update_word(key, sizeof(uint32_t) * i, val_stored[i] >> 1);
+ } else {
+ return storage_set_counter(key, *count);
+ }
+}
+
+secbool storage_has_pin(void) {
+ if (sectrue != initialized) {
+ return secfalse;
+ }
+
+ const void *val = NULL;
+ uint16_t len = 0;
+ if (sectrue != norcow_get(PIN_NOT_SET_KEY, &val, &len) ||
+ (len > 0 && *(uint8_t *)val != FALSE_BYTE)) {
+ return secfalse;
+ }
+ return sectrue;
+}
+
+uint32_t storage_get_pin_rem(void) {
+ if (sectrue != initialized) {
+ return 0;
+ }
+
+ uint32_t ctr_mcu = 0;
+ if (sectrue != pin_get_fails(&ctr_mcu)) {
+ return 0;
+ }
+
+#if USE_OPTIGA
+ // Synchronize counters in case they diverged.
+ uint32_t ctr_optiga = 0;
+ ensure(
+ optiga_pin_get_fails(&ctr_optiga) == OPTIGA_SUCCESS ? sectrue : secfalse,
+ "optiga_pin_get_fails failed");
+
+ while (ctr_mcu < ctr_optiga) {
+ storage_pin_fails_increase();
+ ctr_mcu++;
+ }
+
+ if (ctr_optiga < ctr_mcu) {
+ ensure(optiga_pin_fails_increase(ctr_mcu - ctr_optiga) == OPTIGA_SUCCESS
+ ? sectrue
+ : secfalse,
+ "optiga_pin_fails_increase failed");
+ }
+#endif
+
+ return PIN_MAX_TRIES - ctr_mcu;
+}
+
+secbool storage_change_pin(const uint8_t *oldpin, size_t oldpin_len,
+ const uint8_t *newpin, size_t newpin_len,
+ const uint8_t *old_ext_salt,
+ const uint8_t *new_ext_salt) {
+ if (sectrue != initialized || oldpin == NULL || newpin == NULL) {
+ return secfalse;
+ }
+
+ ui_total = 2 * PIN_DERIVE_MS;
+ ui_rem = ui_total;
+ ui_message =
+ (oldpin_len != 0 && newpin_len == 0) ? VERIFYING_PIN_MSG : PROCESSING_MSG;
+
+ if (sectrue != unlock(oldpin, oldpin_len, old_ext_salt)) {
+ return secfalse;
+ }
+
+ // Fail if the new PIN is the same as the wipe code.
+ if (sectrue != is_not_wipe_code(newpin, newpin_len)) {
+ return secfalse;
+ }
+
+ return set_pin(newpin, newpin_len, new_ext_salt);
+}
+
+void storage_ensure_not_wipe_code(const uint8_t *pin, size_t pin_len) {
+ // If we are unlocking the storage during upgrade from version 2 or lower,
+ // then encode the PIN to the old format.
+ uint32_t legacy_pin = 0;
+ if (get_lock_version() <= 2) {
+ legacy_pin = pin_to_int(pin, pin_len);
+ pin = (const uint8_t *)&legacy_pin;
+ pin_len = sizeof(legacy_pin);
+ }
+
+ ensure_not_wipe_code(pin, pin_len);
+ memzero(&legacy_pin, sizeof(legacy_pin));
+}
+
+secbool storage_has_wipe_code(void) {
+ if (sectrue != initialized || sectrue != unlocked) {
+ return secfalse;
+ }
+
+ return is_not_wipe_code(WIPE_CODE_EMPTY, WIPE_CODE_EMPTY_LEN);
+}
+
+secbool storage_change_wipe_code(const uint8_t *pin, size_t pin_len,
+ const uint8_t *ext_salt,
+ const uint8_t *wipe_code,
+ size_t wipe_code_len) {
+ if (sectrue != initialized || pin == NULL || wipe_code == NULL ||
+ (pin_len != 0 && pin_len == wipe_code_len &&
+ memcmp(pin, wipe_code, pin_len) == 0)) {
+ return secfalse;
+ }
+
+ ui_total = PIN_DERIVE_MS;
+ ui_rem = ui_total;
+ ui_message =
+ (pin_len != 0 && wipe_code_len == 0) ? VERIFYING_PIN_MSG : PROCESSING_MSG;
+
+ secbool ret = secfalse;
+ if (sectrue == unlock(pin, pin_len, ext_salt)) {
+ ret = set_wipe_code(wipe_code, wipe_code_len);
+ }
+ return ret;
+}
+
+void storage_wipe(void) {
+ norcow_wipe();
+ norcow_active_version = NORCOW_VERSION;
+ memzero(authentication_sum, sizeof(authentication_sum));
+ memzero(cached_keys, sizeof(cached_keys));
+ init_wiped_storage();
+}
+
+static void __handle_fault(const char *msg, const char *file, int line,
+ const char *func) {
+ static secbool in_progress = secfalse;
+
+ // If fault handling is already in progress, then we are probably facing a
+ // fault injection attack, so wipe.
+ if (secfalse != in_progress) {
+ storage_wipe();
+ __fatal_error("Fault detected", msg, file, line, func);
+ }
+
+ // We use the PIN fail counter as a fault counter. Increment the counter,
+ // check that it was incremented and halt.
+ in_progress = sectrue;
+ uint32_t ctr = 0;
+ if (sectrue != pin_get_fails(&ctr)) {
+ storage_wipe();
+ __fatal_error("Fault detected", msg, file, line, func);
+ }
+
+ if (sectrue != storage_pin_fails_increase()) {
+ storage_wipe();
+ __fatal_error("Fault detected", msg, file, line, func);
+ }
+
+ uint32_t ctr_new = 0;
+ if (sectrue != pin_get_fails(&ctr_new) || ctr + 1 != ctr_new) {
+ storage_wipe();
+ }
+ __fatal_error("Fault detected", msg, file, line, func);
+}
+
+/*
+ * Reads the PIN fail counter in version 0 format. Returns the current number of
+ * failed PIN entries.
+ */
+static secbool v0_pin_get_fails(uint32_t *ctr) {
+ const uint16_t V0_PIN_FAIL_KEY = 0x0001;
+ // The PIN_FAIL_KEY points to an area of words, initialized to
+ // 0xffffffff (meaning no PIN failures). The first non-zero word
+ // in this area is the current PIN failure counter. If PIN_FAIL_KEY
+ // has no configuration or is empty, the PIN failure counter is 0.
+ // We rely on the fact that flash allows to clear bits and we clear one
+ // bit to indicate PIN failure. On success, the word is set to 0,
+ // indicating that the next word is the PIN failure counter.
+
+ // Find the current pin failure counter
+ const void *val = NULL;
+ uint16_t len = 0;
+ if (secfalse != norcow_get(V0_PIN_FAIL_KEY, &val, &len)) {
+ for (unsigned int i = 0; i < len / sizeof(uint32_t); i++) {
+ uint32_t word = ((const uint32_t *)val)[i];
+ if (word != 0) {
+ *ctr = hamming_weight(~word);
+ return sectrue;
+ }
+ }
+ }
+
+ // No PIN failures
+ *ctr = 0;
+ return sectrue;
+}
+
+// Legacy conversion of PIN to the uint32 scheme that was used prior to storage
+// version 3.
+static uint32_t pin_to_int(const uint8_t *pin, size_t pin_len) {
+ if (pin_len > V0_MAX_PIN_LEN) {
+ return 0;
+ }
+
+ uint32_t val = 1;
+ size_t i = 0;
+ for (i = 0; i < pin_len; ++i) {
+ if (pin[i] < '0' || pin[i] > '9') {
+ return 0;
+ }
+ val = 10 * val + pin[i] - '0';
+ }
+
+ return val;
+}
+
+// Legacy conversion of wipe code from the uint32 scheme that was used prior to
+// storage version 3.
+static char *int_to_wipe_code(uint32_t val) {
+ static char wipe_code[V0_MAX_PIN_LEN + 1] = {0};
+ size_t pos = sizeof(wipe_code) - 1;
+ wipe_code[pos] = '\0';
+
+ // Handle the special representation of an empty wipe code.
+ if (val == V2_WIPE_CODE_EMPTY) {
+ return &wipe_code[pos];
+ }
+
+ if (val == V0_PIN_EMPTY) {
+ return NULL;
+ }
+
+ // Convert a non-empty wipe code.
+ while (val != 1) {
+ if (pos == 0) {
+ return NULL;
+ }
+ pos--;
+ wipe_code[pos] = '0' + (val % 10);
+ val /= 10;
+ }
+ return &wipe_code[pos];
+}
+
+static secbool storage_upgrade(void) {
+ // Storage version 0: plaintext norcow
+ // Storage version 1: encrypted norcow
+ // Storage version 2: adds 9 digit wipe code
+ // Storage version 3: adds variable length PIN and wipe code
+
+ const uint16_t V0_PIN_KEY = 0x0000;
+ const uint16_t V0_PIN_FAIL_KEY = 0x0001;
+ uint16_t key = 0;
+ uint16_t len = 0;
+ const void *val = NULL;
+ secbool ret = secfalse;
+
+ if (norcow_active_version == 0) {
+ random_buffer(cached_keys, sizeof(cached_keys));
+
+ // Initialize the storage authentication tag.
+ auth_init();
+
+ // Set the new storage version number.
+ uint32_t version = 1;
+ if (sectrue !=
+ storage_set_encrypted(VERSION_KEY, &version, sizeof(version))) {
+ return secfalse;
+ }
+
+ // Set EDEK_PVC_KEY and PIN_NOT_SET_KEY.
+ ui_total = PIN_DERIVE_MS;
+ ui_rem = ui_total;
+ ui_message = PROCESSING_MSG;
+ secbool found = norcow_get(V0_PIN_KEY, &val, &len);
+ if (sectrue == found && *(const uint32_t *)val != V0_PIN_EMPTY) {
+ set_pin((const uint8_t *)val, len, NULL);
+ } else {
+ set_pin((const uint8_t *)&V0_PIN_EMPTY, sizeof(V0_PIN_EMPTY), NULL);
+ ret = norcow_set(PIN_NOT_SET_KEY, &TRUE_BYTE, sizeof(TRUE_BYTE));
+ }
+
+ // Convert PIN failure counter.
+ uint32_t fails = 0;
+ v0_pin_get_fails(&fails);
+ pin_logs_init(fails);
+
+ // Copy the remaining entries (encrypting the protected ones).
+ uint32_t offset = 0;
+ while (sectrue == norcow_get_next(&offset, &key, &val, &len)) {
+ if (key == V0_PIN_KEY || key == V0_PIN_FAIL_KEY) {
+ continue;
+ }
+
+ if (((key >> 8) & FLAG_PUBLIC) != 0) {
+ ret = norcow_set(key, val, len);
+ } else {
+ ret = storage_set_encrypted(key, val, len);
+ }
+
+ if (sectrue != ret) {
+ return secfalse;
+ }
+ }
+
+ unlocked = secfalse;
+ memzero(cached_keys, sizeof(cached_keys));
+ } else {
+ // Copy all entries.
+ uint32_t offset = 0;
+ while (sectrue == norcow_get_next(&offset, &key, &val, &len)) {
+ if (sectrue != norcow_set(key, val, len)) {
+ return secfalse;
+ }
+ }
+ }
+
+ // Set wipe code.
+ if (norcow_active_version <= 1) {
+ if (sectrue != set_wipe_code(WIPE_CODE_EMPTY, WIPE_CODE_EMPTY_LEN)) {
+ return secfalse;
+ }
+ }
+
+ if (norcow_active_version <= 2) {
+ // Set UNAUTH_VERSION_KEY, so that it matches VERSION_KEY.
+ uint32_t version = 1;
+
+ // The storage may have gone through an upgrade to version 2 without having
+ // been unlocked. We can tell by looking at STORAGE_UPGRADED_KEY.
+ if (sectrue == norcow_get(STORAGE_UPGRADED_KEY, &val, &len) &&
+ len == sizeof(FALSE_WORD) && *((uint32_t *)val) == FALSE_WORD) {
+ version = 2;
+ }
+
+ if (sectrue != norcow_set(UNAUTH_VERSION_KEY, &version, sizeof(version))) {
+ return secfalse;
+ }
+ }
+
+ norcow_set(STORAGE_UPGRADED_KEY, &TRUE_WORD, sizeof(TRUE_WORD));
+
+ norcow_active_version = NORCOW_VERSION;
+ return norcow_upgrade_finish();
+}
+
+static secbool storage_upgrade_unlocked(const uint8_t *pin, size_t pin_len,
+ const uint8_t *ext_salt) {
+ uint32_t version = 0;
+ uint16_t len = 0;
+ if (sectrue !=
+ storage_get_encrypted(VERSION_KEY, &version, sizeof(version), &len) ||
+ len != sizeof(version)) {
+ handle_fault("storage version check");
+ return secfalse;
+ }
+
+ secbool ret = sectrue;
+ if (version <= 2) {
+ // Upgrade EDEK_PVC_KEY from the old uint32 PIN scheme to the new
+ // variable-length PIN scheme.
+ if (sectrue != set_pin(pin, pin_len, ext_salt)) {
+ return secfalse;
+ }
+ }
+
+ if (version == 2) {
+ // Upgrade WIPE_CODE_DATA_KEY from the old uint32 scheme to the new
+ // variable-length scheme.
+ const void *wipe_code_data = NULL;
+ if (sectrue != norcow_get(WIPE_CODE_DATA_KEY, &wipe_code_data, &len) ||
+ len < sizeof(uint32_t)) {
+ handle_fault("no wipe code");
+ return secfalse;
+ }
+
+ char *wipe_code = int_to_wipe_code(*(uint32_t *)wipe_code_data);
+ if (wipe_code == NULL) {
+ handle_fault("invalid wipe code");
+ return secfalse;
+ }
+
+ size_t wipe_code_len = strnlen(wipe_code, V0_MAX_PIN_LEN);
+ ret = set_wipe_code((const uint8_t *)wipe_code, wipe_code_len);
+ memzero(wipe_code, wipe_code_len);
+ }
+
+ return ret;
+}
diff --git a/storage/tests/c3/storage.h b/storage/tests/c3/storage.h
new file mode 100644
index 000000000..dcaa8490f
--- /dev/null
+++ b/storage/tests/c3/storage.h
@@ -0,0 +1,87 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef __STORAGE_H__
+#define __STORAGE_H__
+
+#include
+#include
+#include "secbool.h"
+
+// The length of the external salt in bytes.
+#define EXTERNAL_SALT_SIZE 32
+
+// If the top bit of APP is set, then the value is not encrypted.
+#define FLAG_PUBLIC 0x80
+
+// If the top two bits of APP are set, then the value is not encrypted and it
+// can be written even when the storage is locked.
+#define FLAGS_WRITE 0xC0
+
+// The maximum value of app_id which is the six least significant bits of APP.
+#define MAX_APPID 0x3F
+
+// The PIN value corresponding to an empty PIN.
+extern const uint8_t *PIN_EMPTY;
+#define PIN_EMPTY_LEN 0
+
+// Maximum number of failed unlock attempts.
+// NOTE: The PIN counter logic relies on this constant being less than or equal
+// to 16.
+#define PIN_MAX_TRIES 16
+
+// The length of the random salt in bytes.
+#if USE_OPTIGA
+#define STORAGE_SALT_SIZE 32
+#else
+#define STORAGE_SALT_SIZE 4
+#endif
+
+typedef secbool (*PIN_UI_WAIT_CALLBACK)(uint32_t wait, uint32_t progress,
+ const char *message);
+
+void storage_init(PIN_UI_WAIT_CALLBACK callback, const uint8_t *salt,
+ const uint16_t salt_len);
+void storage_wipe(void);
+secbool storage_is_unlocked(void);
+void storage_lock(void);
+secbool storage_unlock(const uint8_t *pin, size_t pin_len,
+ const uint8_t *ext_salt);
+secbool storage_has_pin(void);
+secbool storage_pin_fails_increase(void);
+uint32_t storage_get_pin_rem(void);
+secbool storage_change_pin(const uint8_t *oldpin, size_t oldpin_len,
+ const uint8_t *newpin, size_t newpin_len,
+ const uint8_t *old_ext_salt,
+ const uint8_t *new_ext_salt);
+void storage_ensure_not_wipe_code(const uint8_t *pin, size_t pin_len);
+secbool storage_has_wipe_code(void);
+secbool storage_change_wipe_code(const uint8_t *pin, size_t pin_len,
+ const uint8_t *ext_salt,
+ const uint8_t *wipe_code,
+ size_t wipe_code_len);
+secbool storage_has(const uint16_t key);
+secbool storage_get(const uint16_t key, void *val, const uint16_t max_len,
+ uint16_t *len);
+secbool storage_set(const uint16_t key, const void *val, const uint16_t len);
+secbool storage_delete(const uint16_t key);
+secbool storage_set_counter(const uint16_t key, const uint32_t count);
+secbool storage_next_counter(const uint16_t key, uint32_t *count);
+
+#endif
diff --git a/storage/tests/c3/storage.py b/storage/tests/c3/storage.py
new file mode 100644
index 000000000..16c08edcf
--- /dev/null
+++ b/storage/tests/c3/storage.py
@@ -0,0 +1,116 @@
+import ctypes as c
+import os
+import sys
+
+sys.path.append(
+ os.path.normpath(
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "python", "src")
+ )
+)
+import consts
+
+EXTERNAL_SALT_LEN = 32
+sectrue = -1431655766 # 0xAAAAAAAAA
+
+
+class Storage:
+ def __init__(self, lib_name) -> None:
+ lib_path = os.path.join(os.path.dirname(__file__), lib_name)
+ self.lib = c.cdll.LoadLibrary(lib_path)
+ self.flash_size = c.cast(self.lib.FLASH_SIZE, c.POINTER(c.c_uint32))[0]
+ self.flash_buffer = c.create_string_buffer(self.flash_size)
+ c.cast(self.lib.FLASH_BUFFER, c.POINTER(c.c_void_p))[0] = c.addressof(
+ self.flash_buffer
+ )
+
+ def init(self, salt: bytes) -> None:
+ self.lib.storage_init(0, salt, c.c_uint16(len(salt)))
+
+ def wipe(self) -> None:
+ self.lib.storage_wipe()
+
+ def unlock(self, pin: str, ext_salt: bytes = None) -> bool:
+ if ext_salt is not None and len(ext_salt) != EXTERNAL_SALT_LEN:
+ raise ValueError
+ return sectrue == self.lib.storage_unlock(pin.encode(), len(pin), ext_salt)
+
+ def lock(self) -> None:
+ self.lib.storage_lock()
+
+ def has_pin(self) -> bool:
+ return sectrue == self.lib.storage_has_pin()
+
+ def get_pin_rem(self) -> int:
+ return self.lib.storage_get_pin_rem()
+
+ def change_pin(
+ self,
+ oldpin: str,
+ newpin: str,
+ old_ext_salt: bytes = None,
+ new_ext_salt: bytes = None,
+ ) -> bool:
+ if old_ext_salt is not None and len(old_ext_salt) != EXTERNAL_SALT_LEN:
+ raise ValueError
+ if new_ext_salt is not None and len(new_ext_salt) != EXTERNAL_SALT_LEN:
+ raise ValueError
+ return sectrue == self.lib.storage_change_pin(
+ oldpin.encode(),
+ len(oldpin),
+ newpin.encode(),
+ len(newpin),
+ old_ext_salt,
+ new_ext_salt,
+ )
+
+ def get(self, key: int) -> bytes:
+ val_len = c.c_uint16()
+ if sectrue != self.lib.storage_get(c.c_uint16(key), None, 0, c.byref(val_len)):
+ raise RuntimeError("Failed to find key in storage.")
+ s = c.create_string_buffer(val_len.value)
+ if sectrue != self.lib.storage_get(
+ c.c_uint16(key), s, val_len, c.byref(val_len)
+ ):
+ raise RuntimeError("Failed to get value from storage.")
+ return s.raw
+
+ def set(self, key: int, val: bytes) -> None:
+ if sectrue != self.lib.storage_set(c.c_uint16(key), val, c.c_uint16(len(val))):
+ raise RuntimeError("Failed to set value in storage.")
+
+ def set_counter(self, key: int, count: int) -> None:
+ if count > 0xFFFF_FFFF or sectrue != self.lib.storage_set_counter(
+ c.c_uint16(key), c.c_uint32(count)
+ ):
+ raise RuntimeError("Failed to set value in storage.")
+
+ def next_counter(self, key: int) -> int:
+ count = c.c_uint32()
+ if sectrue != self.lib.storage_next_counter(c.c_uint16(key), c.byref(count)):
+ raise RuntimeError("Failed to set value in storage.")
+ return count.value
+
+ def delete(self, key: int) -> bool:
+ return sectrue == self.lib.storage_delete(c.c_uint16(key))
+
+ def _dump(self) -> bytes:
+ # return just sectors 4 and 16 of the whole flash
+ return [
+ self.flash_buffer[0x010000 : 0x010000 + 0x10000],
+ self.flash_buffer[0x110000 : 0x110000 + 0x10000],
+ ]
+
+ def _get_flash_buffer(self) -> bytes:
+ return bytes(self.flash_buffer)
+
+ def _set_flash_buffer(self, buf: bytes) -> None:
+ if len(buf) != self.flash_size:
+ raise RuntimeError("Failed to set flash buffer due to length mismatch.")
+ self.flash_buffer.value = buf
+
+ def _get_active_sector(self) -> int:
+ if self._dump()[0][:8].hex() == consts.NORCOW_MAGIC_AND_VERSION.hex():
+ return 0
+ elif self._dump()[1][:8].hex() == consts.NORCOW_MAGIC_AND_VERSION.hex():
+ return 1
+ raise RuntimeError("Failed to get active sector.")
diff --git a/storage/tests/c3/test_layout.c b/storage/tests/c3/test_layout.c
new file mode 100644
index 000000000..2d23c38e9
--- /dev/null
+++ b/storage/tests/c3/test_layout.c
@@ -0,0 +1,21 @@
+
+#include "test_layout.h"
+
+const flash_area_t STORAGE_AREAS[STORAGE_AREAS_COUNT] = {
+ {
+ .num_subareas = 1,
+ .subarea[0] =
+ {
+ .first_sector = 4,
+ .num_sectors = 1,
+ },
+ },
+ {
+ .num_subareas = 1,
+ .subarea[0] =
+ {
+ .first_sector = 16,
+ .num_sectors = 1,
+ },
+ },
+};
diff --git a/storage/tests/c3/test_layout.h b/storage/tests/c3/test_layout.h
new file mode 100644
index 000000000..1223f198f
--- /dev/null
+++ b/storage/tests/c3/test_layout.h
@@ -0,0 +1,10 @@
+#ifndef TEST_LAYOUT_H
+#define TEST_LAYOUT_H
+
+#define STORAGE_AREAS_COUNT 2
+
+#include "flash_common.h"
+
+extern const flash_area_t STORAGE_AREAS[STORAGE_AREAS_COUNT];
+
+#endif
diff --git a/storage/tests/tests/test_upgrade.py b/storage/tests/tests/test_upgrade.py
index 5a465bf4b..1438fc61a 100644
--- a/storage/tests/tests/test_upgrade.py
+++ b/storage/tests/tests/test_upgrade.py
@@ -1,5 +1,6 @@
import pytest
from c0.storage import Storage as StorageC0
+from c3.storage import Storage as StorageC3
from c.storage import Storage as StorageC
from python.src.norcow import NorcowBitwise
@@ -60,6 +61,21 @@ def test_upgrade():
check_values(sc1)
+def test_upgrade_from_3():
+ sc3 = StorageC3("libtrezor-storage3.so")
+ sc3.init(common.test_uid)
+ assert sc3.unlock("")
+ set_values(sc3)
+ for _ in range(10):
+ assert not sc3.unlock("3")
+
+ sc = StorageC("libtrezor-storage.so")
+ sc._set_flash_buffer(sc3._get_flash_buffer())
+ sc.init(common.test_uid)
+ assert sc.get_pin_rem() == 6
+ check_values(sc)
+
+
@pytest.mark.parametrize("nc_class", [NorcowBitwise])
def test_python_set_sectors(nc_class):
sp0 = StoragePy(nc_class)