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)