Merge b6e05828e94e6ba0099a75135b67f9a74e947cde into 8d8f4c7fafe84ee198f6794ed4b9aa0a0aecdefe

This commit is contained in:
Michael Cardell Widerkrantz 2025-03-24 15:53:06 +00:00 committed by GitHub
commit 162acd07b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 2545 additions and 92 deletions

View File

@ -126,7 +126,15 @@ FIRMWARE_OBJS = \
$(P)/fw/tk1/proto.o \
$(P)/fw/tk1/blake2s/blake2s.o \
$(P)/fw/tk1/syscall_enable.o \
$(P)/fw/tk1/syscall_handler.o
$(P)/fw/tk1/syscall_handler.o \
$(P)/fw/tk1/spi.o \
$(P)/fw/tk1/flash.o \
$(P)/fw/tk1/storage.o \
$(P)/fw/tk1/partition_table.o \
$(P)/fw/tk1/auth_app.o \
$(P)/fw/tk1/rng.o \
$(P)/fw/tk1/preload_app.o \
$(P)/fw/tk1/mgmt_app.o
FIRMWARE_SOURCES = \
$(P)/fw/tk1/main.c \
@ -187,7 +195,7 @@ tkey-libs:
$(FIRMWARE_OBJS): $(FIRMWARE_DEPS)
$(TESTFW_OBJS): $(FIRMWARE_DEPS)
#firmware.elf: CFLAGS += -DTKEY_DEBUG
# firmware.elf: CFLAGS += -DTKEY_DEBUG
firmware.elf: tkey-libs $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map
@ -196,6 +204,9 @@ simfirmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map
qemu_firmware.elf: CFLAGS += -DQEMU_DEBUG
qemu_firmware.elf: ASFLAGS += -DQEMU_DEBUG
qemu_firmware.elf: CFLAGS += -DQEMU_SYSCALL
qemu_firmware.elf: ASFLAGS += -DQEMU_SYSCALL
qemu_firmware.elf: firmware.elf
mv firmware.elf qemu_firmware.elf

View File

@ -102,81 +102,154 @@ values of the Block RAMs used to construct the `FW_ROM`. The `FW_ROM`
start address is located at `0x0000_0000` in the CPU memory map, which
is also the CPU reset vector.
### Reset intentions
We have a number of reset options we call `startfrom`:
1. Start from flash slot 1 (default): `FLASH1`
2. Start from flash slot 2: `FLASH2`.
3. Load and start an app from flash slot 1 with a specific app hash:
`FLASH1_VER`
4. Load and start an app from flash slot 2 with a specific app hash:
`FLASH2_VER`.
5. Load and start a new app from client: `CLIENT`.
6. load and start an app from client with a specific app hash:
`CLIENT_VER`.
### Firmware state machine
This is the state diagram of the firmware. There are only four states.
Change of state occur when we receive specific I/O or a fatal error
occurs.
This is the state diagram of the firmware. Change of state occur when
we receive specific I/O or a fatal error occurs.
```mermaid
stateDiagram-v2
S1: initial
S0: initial
S1: waitcommand
S2: loading
S3: running
S3: flash_loading
S4: auth_app
S5: starting
S6: compute_cdi
SE: failed
[*] --> S1
[*] --> S0
S0 --> S1
S0 --> S4: Default
S1 --> S1: Commands
S1 --> S2: LOAD_APP
S1 --> SE: Error
S2 --> S2: LOAD_APP_DATA
S2 --> S3: Last block received
S2 --> S6: Last block received
S2 --> SE: Error
S3 --> [*]
S6 --> S3
S3 --> S5
S4 --> S5
S4 --> SE: Error
SE --> [*]
S5 --> [*]
```
States:
- `initial` - At start. Allows the commands `NAME_VERSION`, `GET_UDI`,
`LOAD_APP`.
- `loading` - Expect application data. Allows only the command
`LOAD_APP_DATA`.
- `run` - Computes CDI and starts the application. Allows no commands.
- `fail` - Stops waiting for commands, flashes LED forever. Allows no
commands.
- `initial`: We start by checking resetinfo data in `FW_RAM` for
`startfrom`.
- `waitcommand`: Waiting for initial commands from client. Allows the
commands `NAME_VERSION`, `GET_UDI`, `LOAD_APP`.
- `loading`: Expecting application data from client. Allows only the
command `LOAD_APP_DATA`.
- `flash_loading`: Loading and authentication app from flash. Computes CDI,
creates or checks the authentication of the flash app. Allows no commands.
- `starting`: Starts the application. Does not return to firmware.
Allows no commands.
- `failed` - Halts CPU. Allows no commands.
Commands in state `initial`:
Allowed data in state `resetinfo`:
| *startfrom* | *next state* |
|--------------|-----------------|
| `FLASH1` | `flash_loading` |
| `FLASH2` | `flash_loading` |
| `FLASH1_VER` | `flash_loading` |
| `FLASH2_VER` | `flash_loading` |
| `CLIENT` | `waitcommand` |
| `CLIENT_VER` | `waitcommand` |
I/O in state `flash_loading`:
| *I/O* | *next state* |
|--------------------|--------------|
| Last app data read | `starting` |
Commands in state `waitcommand`:
| *command* | *next state* |
|-----------------------|--------------|
| `FW_CMD_NAME_VERSION` | unchanged |
| `FW_CMD_GET_UDI` | unchanged |
| `FW_CMD_LOAD_APP` | `loading` |
| | |
Commands in state `loading`:
| *command* | *next state* |
|------------------------|----------------------------------|
| `FW_CMD_LOAD_APP_DATA` | unchanged or `run` on last chunk |
| *command* | *next state* |
|------------------------|---------------------------------------|
| `FW_CMD_LOAD_APP_DATA` | unchanged or `starting` on last chunk |
No other states allows commands.
See [Firmware protocol in the Dev
Handbook](http://dev.tillitis.se/protocol/#firmware-protocol) for the
definition of the specific commands and their responses.
State changes from "initial" to "loading" when receiving `LOAD_APP`,
which also sets the size of the number of data blocks to expect. After
that we expect several `LOAD_APP_DATA` commands until the last block
is received, when state is changed to "running".
Plain text explanation of the states:
In "running", the loaded device app is measured, the Compound Device
Identifier (CDI) is computed, we do some cleanup of firmware data
structures, enable the system calls, and finally start the app, which
ends the firmware state machine. Hardware guarantees that we leave
firmware mode automatically when the program counter leaves ROM.
- `initial`: Execution starts here. The firmware checks in the
`FW_RAM` for `startfrom` for what to do next.
The device app is now running in application mode. We can, however,
return to firmware mode (excepting access to the UDS) by doing system
calls. Note that ROM is still readable, but is now hardware protected
from execution, except through the system call mechanism.
For all `startfrom` values `FLASH_*` the next state is `startflash`.
Otherwise it goes to `waitcommand`, indicating that it should wait
for further commands from the client.
- `flash_loading` loads and measure an app from flash, the Compound
Device Identifier (CDI) is computed, then the app is authenticated
against a stored digest to see that no one has changed the app by
manipulating the flash. The compuation is done by:
digest = blake2s(cdi, nonce from flash)
and then compared against the stored digest in the app's flash slot.
- `waitcommand` waits for command from the client. State changes to
`loading` when receiving `LOAD_APP`, which also sets the size of the
number of data blocks to expect. After that we expect several
`LOAD_APP_DATA` commands until the last block is received, when
state is changed to `running`.
- `compute_cdi`: The the Compound Device Identifier (CDI) is computed
and we go to `starting`.
- `starting`: Clean up firmware data structures, enable the system
calls, and start the app, which ends the firmware state machine.
Hardware guarantees that we leave firmware mode automatically when
the program counter leaves ROM.
After `starting` the device app is now running in application mode. We
can, however, return to firmware mode (excepting access to the UDS) by
doing system calls. Note that ROM is still readable, but is now
hardware protected from execution, except through the system call
mechanism.
### Golden path
Firmware loads the application at the start of RAM (`0x4000_0000`). It
use a part of the special FW\_RAM for its own stack.
Firmware loads the application at the start of RAM (`0x4000_0000`)
from either flash or the UART. It use a part of the special FW\_RAM
for its own stack.
When reset is released, the CPU starts executing the firmware. It
begins in `start.S` by clearing all CPU registers, clears all FW\_RAM,
@ -186,54 +259,101 @@ the system calls, but the handler is not yet enabled.
Beginning at `main()` it fills the entire RAM with pseudo random data
and setting up the RAM address and data hardware scrambling with
values from the True Random Number Generator (TRNG). It then waits for
data coming in through the UART.
values from the True Random Number Generator (TRNG).
Typical expected use scenario:
1. Check the special resetinfo area in FW\_RAM to see if there is any
data about why a reset has been made. All zeroes(?) meaning default
behaviour.
1. The client sends the `FW_CMD_LOAD_APP` command with the size of
the device app and the optional 32 byte hash of the user-supplied
secret as arguments and gets a `FW_RSP_LOAD_APP` back. After
using this it's not possible to restart the loading of an
application.
2. If it was reset with intention to start a device app from client,
see App loaded from client below.
2. If the the client receive a sucessful response, it will send
multiple `FW_CMD_LOAD_APP_DATA` commands, together containing the
full application.
3. Default is to start the first device app from flash. If resetinfo
says otherwise it starts the other one.
3. On receiving`FW_CMD_LOAD_APP_DATA` commands the firmware places
the data into `0x4000_0000` and upwards. The firmware replies
with a `FW_RSP_LOAD_APP_DATA` response to the client for each
received block except the last data block.
4. Load flash app into RAM without USS.
4. When the final block of the application image is received with a
`FW_CMD_LOAD_APP_DATA`, the firmware measure the application by
computing a BLAKE2s digest over the entire application. Then
firmware send back the `FW_RSP_LOAD_APP_DATA_READY` response
containing the digest.
5. Compute digest of loaded app.
5. The Compound Device Identifier
([CDI]((#compound-device-identifier-computation))) is then
computed by doing a new BLAKE2s using the Unique Device Secret
(UDS), the application digest, and any User Supplied Secret
(USS) digest already received.
6. Compare against stored app digest in partition table to note if app
has been corrupted on flash. If corrupted, halt CPU.
6. The start address of the device app, currently `0x4000_0000`, is
written to `APP_ADDR` and the size of the binary to `APP_SIZE` to
let the device application know where it is loaded and how large
it is, if it wants to relocate in RAM.
7. Proceed to [Start the device app](#start-the-device-app) below.
7. The firmware now clears the part of the special `FW_RAM` where it
keeps it stack.
If the app is the first set in a chain, it's the job of the app itself
to reset the TKey when it has done its job. For instance, a verified
boot loader app:
8. The interrupt handler for system calls is enabled.
- includes a security policy, for instance a public key and code to
check a signature.
9. Firmware starts the application by jumping to the contents of
`APP_ADDR`. Hardware automatically switches from firmware mode to
application mode. In this mode some memory access is restricted,
e.g. some addresses are inaccessible (`UDS`), and some are
switched from read/write to read-only (see [the memory
map](https://dev.tillitis.se/memory/)).
- the app reads the message and the signature over the message (the
digest of the next app in the chain) from the filesystem or from
the client.
- if the signature provided over the message is verified to be done
by the corresponding private key, this app would do a `reset()`,
passing the digest to the firmware for control and instructing it
to start *just* that app.
- firmware would see the instructions about the reset in FW\_RAM:
1. Where to expect the next app from: client, a slot in the
filesystem?
2. The expected digest of the next app.
#### App loaded from client
Firmware waits for data coming in through the UART.
1. The client sends the `FW_CMD_LOAD_APP` command with the size of
the device app and the optional 32 byte hash of the user-supplied
secret as arguments and gets a `FW_RSP_LOAD_APP` back. After
using this it's not possible to restart the loading of an
application.
2. If the the client receive a sucessful response, it will send
multiple `FW_CMD_LOAD_APP_DATA` commands, together containing the
full application.
3. On receiving`FW_CMD_LOAD_APP_DATA` commands the firmware places
the data into `0x4000_0000` and upwards. The firmware replies
with a `FW_RSP_LOAD_APP_DATA` response to the client for each
received block except the last data block.
4. When the final block of the application image is received with a
`FW_CMD_LOAD_APP_DATA`, the firmware measure the application by
computing a BLAKE2s digest over the entire application. Then
firmware send back the `FW_RSP_LOAD_APP_DATA_READY` response
containing the digest.
#### Start the device app
1. If there is an app digest in the resetinfo left from previous app,
compare the digests. Halt CPU if differences.
2. The Compound Device Identifier
([CDI]((#compound-device-identifier-computation))) is then computed
by doing a new BLAKE2s using the Unique Device Secret (UDS), the
application digest, and any User Supplied Secret (USS) digest
already received.
3. The start address of the device app, currently `0x4000_0000`, is
written to `APP_ADDR` and the size of the binary to `APP_SIZE` to
let the device application know where it is loaded and how large it
is, if it wants to relocate in RAM.
4. The firmware now clears the part of the special `FW_RAM` where it
keeps it stack.
5. The interrupt handler for system calls is enabled.
6. Firmware starts the application by jumping to the contents of
`APP_ADDR`. Hardware automatically switches from firmware mode to
application mode. In this mode some memory access is restricted,
e.g. some addresses are inaccessible (`UDS`), and some are switched
from read/write to read-only (see [the memory
map](https://dev.tillitis.se/memory/)).
If during this whole time any commands are received which are not
allowed in the current state, or any errors occur, we enter the

View File

@ -0,0 +1,76 @@
P := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
LIBDIR ?= ../../tkey-libs
OBJCOPY ?= llvm-objcopy
CC = clang
CFLAGS = \
-target riscv32-unknown-none-elf \
-march=rv32iczmmul \
-mabi=ilp32 \
-mcmodel=medany \
-static \
-std=gnu99 \
-O2 \
-ffast-math \
-fno-common \
-fno-builtin-printf \
-fno-builtin-putchar \
-fno-builtin-memcpy \
-nostdlib \
-mno-relax \
-Wall \
-Wpedantic \
-Wno-language-extension-token \
-Werror \
-flto \
-g \
-I $(LIBDIR)/include \
-I $(LIBDIR) \
-DTKEY_DEBUG
AS = clang
ASFLAGS = \
-target riscv32-unknown-none-elf \
-march=rv32iczmmul \
-mabi=ilp32 \
-mno-relax
LDFLAGS = \
-T $(P)/app.lds \
-L $(LIBDIR) -lcommon
.PHONY: all
all: reset_test.bin
# Turn elf into bin for device
%.bin: %.elf
$(OBJCOPY) --input-target=elf32-littleriscv --output-target=binary $^ $@
chmod a-x $@
.PHONY: tkey-libs
tkey-libs:
make -C $(LIBDIR)
RESET_TEST_FMTFILES = \
$(P)/main.c \
RESET_TEST_OBJS = \
$(P)/main.o \
$(P)/crt0.o \
$(P)/syscall.o
reset_test.elf: tkey-libs $(RESET_TEST_OBJS)
$(CC) $(CFLAGS) $(RESET_TEST_OBJS) $(LDFLAGS) -o $@
.PHONY: fmt
fmt:
clang-format --dry-run --ferror-limit=0 $(RESET_TEST_FMTFILES)
clang-format --verbose -i $(RESET_TEST_FMTFILES)
.PHONY: checkfmt
checkfmt:
clang-format --dry-run --ferror-limit=0 --Werror $(RESET_TEST_FMTFILES)
.PHONY: clean
clean:
rm -f reset_test.bin reset_test.elf $(RESET_TEST_OBJS)

View File

@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
* SPDX-License-Identifier: BSD-2-Clause
*/
OUTPUT_ARCH( "riscv" )
ENTRY(_start)
MEMORY
{
RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */
}
SECTIONS
{
.text.init :
{
*(.text.init)
} >RAM
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
*(.srodata) /* .rodata sections (constants, strings, etc.) */
*(.srodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
_etext = .;
_sidata = _etext;
} >RAM
.data : AT (_etext)
{
. = ALIGN(4);
_sdata = .;
. = ALIGN(4);
*(.data) /* .data sections */
*(.data*) /* .data* sections */
*(.sdata) /* .sdata sections */
*(.sdata*) /* .sdata* sections */
. = ALIGN(4);
_edata = .;
} >RAM
/* Uninitialized data section */
.bss :
{
. = ALIGN(4);
_sbss = .;
*(.bss)
*(.bss*)
*(.sbss)
*(.sbss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} >RAM
/* libcrt0/crt0.S inits stack to start just below end of RAM */
}

View File

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
.section ".text.init"
.global _start
_start:
li x1, 0
li x2, 0
li x3, 0
li x4, 0
li x5, 0
li x6, 0
li x7, 0
li x8, 0
li x9, 0
li x10,0
li x11,0
li x12,0
li x13,0
li x14,0
li x15,0
li x16,0
li x17,0
li x18,0
li x19,0
li x20,0
li x21,0
li x22,0
li x23,0
li x24,0
li x25,0
li x26,0
li x27,0
li x28,0
li x29,0
li x30,0
li x31,0
/* init stack below 0x40020000 (TK1_RAM_BASE+TK1_RAM_SIZE) */
li sp, 0x4001fff0
/* zero-init bss section */
la a0, _sbss
la a1, _ebss
bge a0, a1, end_init_bss
loop_init_bss:
sw zero, 0(a0)
addi a0, a0, 4
blt a0, a1, loop_init_bss
end_init_bss:
call main

View File

@ -0,0 +1,134 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/debug.h>
#include <tkey/io.h>
#include <tkey/led.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "../testapp/syscall.h"
#include "../tk1/proto.h"
#include "../tk1/resetinfo.h"
#include "../tk1/syscall_num.h"
// Converts a single hex character to its integer value
static uint8_t hex_char_to_byte(uint8_t c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return 0; // Invalid character, should not happen if input is valid
}
// Converts a 64-char hex string into a 32-byte array
int hex_string_to_bytes(uint8_t *hex_str, uint8_t *out_bytes, size_t out_len)
{
if (!hex_str || !out_bytes || out_len < 32)
return -1; // Error handling
for (size_t i = 0; i < 32; i++) {
out_bytes[i] = (hex_char_to_byte(hex_str[i * 2]) << 4) |
hex_char_to_byte(hex_str[i * 2 + 1]);
}
return 0; // Success
}
//---------------------------------------
#define BUFSIZE 32
int main(void)
{
uint8_t available = 0;
uint8_t cmdbuf[BUFSIZE] = {0};
enum ioend endpoint = IO_NONE;
led_set(LED_BLUE);
struct reset rst = {0};
while (1) {
debug_puts("reset_test: Waiting for command\n");
memset(cmdbuf, 0, BUFSIZE);
// Wait for data
if (readselect(IO_TKEYCTRL, &endpoint, &available) < 0) {
assert(1 == 2);
}
if (read(IO_TKEYCTRL, cmdbuf, BUFSIZE, available) < 0) {
// read failed! I/O broken? Just redblink.
assert(1 == 2);
}
led_set(LED_BLUE | LED_RED);
switch (cmdbuf[0]) {
case '1':
rst.type = START_DEFAULT;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
break;
case '2':
rst.type = START_CLIENT;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
break;
case '3':
rst.type = START_FLASH1;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
break;
case '4': {
uint8_t string[] = "0123456789abcdef0123456789abcdef012"
"3456789abcdef0123456789abcdef";
rst.type = START_CLIENT_VER;
hex_string_to_bytes(string, (uint8_t *)&rst.app_digest,
sizeof(rst.app_digest));
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
} break;
case '5': {
uint8_t tkeylibs_example_app_digest[] =
"96bb4c90603dbbbe09b9a1d7259b5e9e61bedd89a897105c30"
"c9d4bf66a98d97";
rst.type = START_CLIENT_VER;
hex_string_to_bytes(tkeylibs_example_app_digest,
(uint8_t *)&rst.app_digest,
sizeof(rst.app_digest));
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
} break;
case '6': {
uint8_t string[] = "0123456789abcdef0123456789abcdef012"
"3456789abcdef0123456789abcdef";
rst.type = START_FLASH2_VER;
hex_string_to_bytes(string, (uint8_t *)&rst.app_digest,
sizeof(rst.app_digest));
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
} break;
case '7': {
uint8_t tkeylibs_example_app_digest[] =
"a97f6ec2112067c4b5b5860521e252a095d221652f7b3d056b"
"d98eaba40b4967";
rst.type = START_FLASH2_VER;
hex_string_to_bytes(tkeylibs_example_app_digest,
(uint8_t *)&rst.app_digest,
sizeof(rst.app_digest));
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
} break;
default:
break;
}
}
}

View File

@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: 2024 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include "../tk1/picorv32/custom_ops.S"
.section ".text"
.globl syscall
syscall:
// Save registers to stack
addi sp, sp, -32*4
sw x0, 0*4(sp)
sw x1, 1*4(sp)
// x2 (sp) is assumed to be preserved by the interrupt handler.
sw x3, 3*4(sp)
sw x4, 4*4(sp)
sw x5, 5*4(sp)
sw x6, 6*4(sp)
sw x7, 7*4(sp)
sw x8, 8*4(sp)
sw x9, 9*4(sp)
// x10 (a0) will contain syscall return value. And should not be saved.
sw x11, 11*4(sp)
sw x12, 12*4(sp)
sw x13, 13*4(sp)
sw x14, 14*4(sp)
sw x15, 15*4(sp)
sw x16, 16*4(sp)
sw x17, 17*4(sp)
sw x18, 18*4(sp)
sw x19, 19*4(sp)
sw x20, 20*4(sp)
sw x21, 21*4(sp)
sw x22, 22*4(sp)
sw x23, 23*4(sp)
sw x24, 24*4(sp)
sw x25, 25*4(sp)
sw x26, 26*4(sp)
sw x27, 27*4(sp)
sw x28, 28*4(sp)
sw x29, 29*4(sp)
sw x30, 30*4(sp)
sw x31, 31*4(sp)
// Trigger syscall interrupt
li t1, 0xe1000000 // Syscall interrupt trigger address
sw zero, 0(t1) // Trigger interrupt
// Restore registers from stack
lw x0, 0*4(sp)
lw x1, 1*4(sp)
// x2 (sp) is assumed to be preserved by the interrupt handler.
lw x3, 3*4(sp)
lw x4, 4*4(sp)
lw x5, 5*4(sp)
lw x6, 6*4(sp)
lw x7, 7*4(sp)
lw x8, 8*4(sp)
lw x9, 9*4(sp)
// x10 (a0) contains syscall return value. And should not be destroyed.
lw x11, 11*4(sp)
lw x12, 12*4(sp)
lw x13, 13*4(sp)
lw x14, 14*4(sp)
lw x15, 15*4(sp)
lw x16, 16*4(sp)
lw x17, 17*4(sp)
lw x18, 18*4(sp)
lw x19, 19*4(sp)
lw x20, 20*4(sp)
lw x21, 21*4(sp)
lw x22, 22*4(sp)
lw x23, 23*4(sp)
lw x24, 24*4(sp)
lw x25, 25*4(sp)
lw x26, 26*4(sp)
lw x27, 27*4(sp)
lw x28, 28*4(sp)
lw x29, 29*4(sp)
lw x30, 30*4(sp)
lw x31, 31*4(sp)
addi sp, sp, 32*4
ret

View File

@ -121,13 +121,48 @@ int main(void)
}
// But a syscall to get parts of UDI should be able to run
int vidpid = syscall(TK1_SYSCALL_GET_VIDPID, 0);
int vidpid = syscall(TK1_SYSCALL_GET_VIDPID, 0, 0, 0);
if (vidpid != 0x00010203) {
failmsg("Expected VID/PID to be 0x00010203");
anyfailed = 1;
}
puts(IO_CDC, "\r\nAllocating storage area...");
if (syscall(TK1_SYSCALL_ALLOC_AREA, 0, 0, 0) != 0) {
failmsg("Failed to allocate storage area");
}
puts(IO_CDC, "done.\r\n");
puts(IO_CDC, "\r\nWriting to storage area...");
uint8_t out_data[14] = { 0, 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13 };
if (syscall(TK1_SYSCALL_WRITE_DATA, 0, (uint32_t)out_data, sizeof(out_data)) != 0) {
failmsg("Failed to write to storage area");
}
puts(IO_CDC, "done.\r\n");
puts(IO_CDC, "\r\nReading from storage area...");
uint8_t in_data[14] = { 0 };
if (syscall(TK1_SYSCALL_READ_DATA, 0, (uint32_t)in_data, sizeof(in_data)) != 0) {
failmsg("Failed to write to storage area");
}
if (!memeq(in_data, out_data, sizeof(in_data))) {
failmsg("Failed to read back data from storage area");
anyfailed = 1;
}
puts(IO_CDC, "done.\r\n");
puts(IO_CDC, "\r\nDeallocating storage area...");
if (syscall(TK1_SYSCALL_DEALLOC_AREA, 0, 0, 0) != 0) {
failmsg("Failed to deallocate storage area");
}
puts(IO_CDC, "done.\r\n");
uint32_t cdi_local[CDI_WORDS];
uint32_t cdi_local2[CDI_WORDS];
wordcpy_s(cdi_local, CDI_WORDS, (void *)cdi, CDI_WORDS);
@ -223,7 +258,7 @@ int main(void)
}
if (in == '+') {
syscall(TK1_SYSCALL_RESET, 0);
syscall(TK1_SYSCALL_RESET, 0, 0, 0);
}
write(IO_CDC, &in, 1);

View File

@ -6,6 +6,7 @@
#ifndef TKEY_APP_SYSCALL_H
#define TKEY_APP_SYSCALL_H
int syscall(uint32_t number, uint32_t arg1);
int syscall(uint32_t number, uint32_t arg1, uint32_t arg2,
uint32_t arg3);
#endif

View File

@ -0,0 +1,74 @@
P := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
LIBDIR ?= ../../tkey-libs
OBJCOPY ?= llvm-objcopy
CC = clang
CFLAGS = \
-target riscv32-unknown-none-elf \
-march=rv32iczmmul \
-mabi=ilp32 \
-mcmodel=medany \
-static \
-std=gnu99 \
-Os \
-ffast-math \
-fno-common \
-fno-builtin-printf \
-fno-builtin-putchar \
-fno-builtin-memcpy \
-nostdlib \
-mno-relax \
-Wall \
-Wpedantic \
-Wno-language-extension-token \
-Werror \
-flto \
-g \
-I $(LIBDIR)/include \
-I $(LIBDIR)
AS = clang
ASFLAGS = \
-target riscv32-unknown-none-elf \
-march=rv32iczmmul \
-mabi=ilp32 \
-mno-relax
LDFLAGS = \
-T $(LIBDIR)/app.lds \
-L $(LIBDIR) -lcrt0 -lcommon -lmonocypher -lblake2s
.PHONY: all
all: testloadapp.bin
# Turn elf into bin for device
%.bin: %.elf
$(OBJCOPY) --input-target=elf32-littleriscv --output-target=binary $^ $@
chmod a-x $@
.PHONY: tkey-libs
tkey-libs:
make -C $(LIBDIR)
TESTLOADAPP_FMTFILES = \
$(P)/main.c
TESTLOADAPP_OBJS = \
$(P)/main.o \
../testapp/syscall.o \
testloadapp.elf: tkey-libs $(TESTLOADAPP_OBJS)
$(CC) $(CFLAGS) $(TESTLOADAPP_OBJS) $(LDFLAGS) -o $@
.PHONY: fmt
fmt:
clang-format --dry-run --ferror-limit=0 $(TESTLOADAPP_FMTFILES)
clang-format --verbose -i $(TESTLOADAPP_FMTFILES)
.PHONY: checkfmt
checkfmt:
clang-format --dry-run --ferror-limit=0 --Werror $(TESTLOADAPP_FMTFILES)
.PHONY: clean
clean:
rm -f testloadapp.bin testloadapp.elf $(TESTLOADAPP_OBJS)

View File

@ -0,0 +1,24 @@
#ifndef BLINK_APP_H
#define BLINK_APP_H
uint8_t blink[] = {
0x81, 0x40, 0x01, 0x41, 0x81, 0x41, 0x01, 0x42, 0x81, 0x42, 0x01, 0x43, 0x81, 0x43, 0x01, 0x44,
0x81, 0x44, 0x01, 0x45, 0x81, 0x45, 0x01, 0x46, 0x81, 0x46, 0x01, 0x47, 0x81, 0x47, 0x01, 0x48,
0x81, 0x48, 0x01, 0x49, 0x81, 0x49, 0x01, 0x4a, 0x81, 0x4a, 0x01, 0x4b, 0x81, 0x4b, 0x01, 0x4c,
0x81, 0x4c, 0x01, 0x4d, 0x81, 0x4d, 0x01, 0x4e, 0x81, 0x4e, 0x01, 0x4f, 0x81, 0x4f, 0x37, 0x01,
0x02, 0x40, 0x41, 0x11, 0x17, 0x05, 0x00, 0x00, 0x13, 0x05, 0x45, 0x0c, 0x97, 0x05, 0x00, 0x00,
0x93, 0x85, 0xc5, 0x0b, 0x63, 0x57, 0xb5, 0x00, 0x23, 0x20, 0x05, 0x00, 0x11, 0x05, 0xe3, 0x4d,
0xb5, 0xfe, 0x97, 0x00, 0x00, 0x00, 0xe7, 0x80, 0xa0, 0x00, 0x00, 0x00, 0x41, 0x11, 0x37, 0x05,
0x00, 0xff, 0x11, 0x48, 0xe1, 0x66, 0x13, 0x86, 0xf6, 0x69, 0x93, 0x86, 0x06, 0x6a, 0x09, 0x47,
0x85, 0x47, 0x23, 0x22, 0x05, 0x03, 0x02, 0xc2, 0x92, 0x45, 0x63, 0x68, 0xb6, 0x00, 0x92, 0x45,
0x85, 0x05, 0x2e, 0xc2, 0x92, 0x45, 0xe3, 0xec, 0xd5, 0xfe, 0x58, 0xd1, 0x02, 0xc4, 0xa2, 0x45,
0x63, 0x68, 0xb6, 0x00, 0xa2, 0x45, 0x85, 0x05, 0x2e, 0xc4, 0xa2, 0x45, 0xe3, 0xec, 0xd5, 0xfe,
0x5c, 0xd1, 0x02, 0xc6, 0xb2, 0x45, 0xe3, 0x66, 0xb6, 0xfc, 0xb2, 0x45, 0x85, 0x05, 0x2e, 0xc6,
0xb2, 0x45, 0xe3, 0xec, 0xd5, 0xfe, 0x75, 0xbf, 0x19, 0xca, 0x2a, 0x96, 0xaa, 0x86, 0x03, 0xc7,
0x05, 0x00, 0x23, 0x80, 0xe6, 0x00, 0x85, 0x06, 0x85, 0x05, 0xe3, 0x9a, 0xc6, 0xfe, 0x82, 0x80,
0x11, 0xca, 0x0a, 0x06, 0x2a, 0x96, 0xaa, 0x86, 0x98, 0x41, 0x98, 0xc2, 0x91, 0x06, 0x91, 0x05,
0xe3, 0x9c, 0xc6, 0xfe, 0x82, 0x80, 0x01, 0xca, 0x2a, 0x96, 0xaa, 0x86, 0x23, 0x80, 0xb6, 0x00,
0x85, 0x06, 0xe3, 0x9d, 0xc6, 0xfe, 0x82, 0x80
};
#endif

View File

@ -0,0 +1,187 @@
#include <blake2s/blake2s.h>
#include <monocypher/monocypher-ed25519.h>
#include <stdint.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include <tkey/debug.h>
#include "../testapp/syscall.h"
#include "../tk1/resetinfo.h"
#include "../tk1/syscall_num.h"
#include "blink.h"
#include "tkey/assert.h"
// clang-format off
static volatile uint32_t *cdi = (volatile uint32_t *) TK1_MMIO_TK1_CDI_FIRST;
// clang-format on
int install_app(uint8_t secret_key[64])
{
uint8_t app_digest[32];
uint8_t app_signature[64];
size_t app_size = sizeof(blink);
if (syscall(TK1_SYSCALL_REG_MGMT, 0, 0, 0) < 0) {
puts(IO_CDC, "couldn't register as mgmt\r\n");
return -1;
}
if (syscall(TK1_SYSCALL_PRELOAD_DELETE, 0, 0, 0) < 0) {
puts(IO_CDC, "couldn't delete preloaded app\r\n");
return -1;
}
int err = syscall(TK1_SYSCALL_PRELOAD_STORE, 0, (uint32_t)blink,
sizeof(blink));
if (err < 0) {
puts(IO_CDC, "couldn't store app, error: ");
putinthex(IO_CDC, err);
puts(IO_CDC, "\r\n");
return -1;
}
puts(IO_CDC, "blink: ");
putinthex(IO_CDC, (uint32_t)blink);
puts(IO_CDC, "\r\n");
puts(IO_CDC, "blink[0]: ");
putinthex(IO_CDC, blink[0]);
puts(IO_CDC, "\r\n");
puts(IO_CDC, "sizeof(blink): ");
putinthex(IO_CDC, sizeof(blink));
puts(IO_CDC, "\r\n");
if (blake2s(app_digest, 32, NULL, 0, blink, sizeof(blink)) != 0) {
puts(IO_CDC, "couldn't compute digest\r\n");
return -1;
}
crypto_ed25519_sign(app_signature, secret_key, app_digest,
sizeof(app_digest));
puts(IO_CDC, "app_digest:\r\n");
hexdump(IO_CDC, app_digest, sizeof(app_digest));
puts(IO_CDC, "\r\n");
puts(IO_CDC, "app_signature:\r\n");
hexdump(IO_CDC, app_signature, sizeof(app_signature));
puts(IO_CDC, "\r\n");
puts(IO_CDC, "secret_key:\r\n");
hexdump(IO_CDC, secret_key, 64);
puts(IO_CDC, "\r\n");
if (syscall(TK1_SYSCALL_PRELOAD_STORE_FIN, app_size,
(uint32_t)app_digest, (uint32_t)app_signature) < 0) {
puts(IO_CDC, "couldn't finalize storing app\r\n");
return -1;
}
return 0;
}
int verify(uint8_t pubkey[32])
{
uint8_t app_digest[32];
uint8_t app_signature[64];
// pubkey we already have
// read signature
// read digest
syscall(TK1_SYSCALL_PRELOAD_GET_DIGSIG, (uint32_t)app_digest,
(uint32_t)app_signature, 0);
puts(IO_CDC, "app_digest:\r\n");
hexdump(IO_CDC, app_digest, sizeof(app_digest));
puts(IO_CDC, "\r\n");
puts(IO_CDC, "app_signature:\r\n");
hexdump(IO_CDC, app_signature, sizeof(app_signature));
puts(IO_CDC, "\r\n");
puts(IO_CDC, "pubkey:\r\n");
hexdump(IO_CDC, pubkey, 32);
puts(IO_CDC, "\r\n");
puts(IO_CDC, "Checking signature...\r\n");
if (crypto_ed25519_check(app_signature, pubkey, app_digest,
sizeof(app_digest)) != 0) {
return -1;
}
puts(IO_CDC, "Resetting into pre loaded app (slot 2)...\r\n");
// syscall reset flash2_ver with app_digest
struct reset rst;
rst.type = START_FLASH2_VER;
memcpy_s(rst.app_digest, sizeof(rst.app_digest), app_digest,
sizeof(app_digest));
memset(rst.next_app_data, 0, sizeof(rst.next_app_data));
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
return -2;
}
int main(void)
{
uint8_t secret_key[64];
uint8_t pubkey[32];
enum ioend endpoint;
uint8_t available;
uint8_t in = 0;
// Generate a key pair from CDI
crypto_ed25519_key_pair(secret_key, pubkey, (uint8_t *)cdi);
if (readselect(IO_CDC, &endpoint, &available) < 0) {
// readselect failed! I/O broken? Just redblink.
assert(1 == 2);
}
if (read(IO_CDC, &in, 1, 1) < 0) {
// read failed! I/O broken? Just redblink.
assert(1 == 2);
}
puts(IO_CDC, "Hello from testloadapp! 0 = install app in slot 1, 1 = "
"verify app\r\n");
for (;;) {
if (readselect(IO_CDC, &endpoint, &available) < 0) {
// readselect failed! I/O broken? Just redblink.
assert(1 == 2);
}
if (read(IO_CDC, &in, 1, 1) < 0) {
// read failed! I/O broken? Just redblink.
assert(1 == 2);
}
switch (in) {
case '0':
if (install_app(secret_key) < 0) {
puts(IO_CDC, "Failed to install app\r\n");
} else {
puts(IO_CDC, "Installed app!\r\n");
}
break;
case '1':
if (verify(pubkey) < 0) {
puts(IO_CDC, "Failed to verify app\r\n");
} else {
puts(IO_CDC, "Verified app!\r\n");
}
break;
default:
break;
}
}
}

View File

@ -0,0 +1,68 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stdint.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "auth_app.h"
#include "blake2s/blake2s.h"
#include "partition_table.h"
#include "rng.h"
static volatile uint32_t *cdi = (volatile uint32_t *)TK1_MMIO_TK1_CDI_FIRST;
/* Calculates the authentication digest based on a supplied nonce and the CDI.
* Requires that the CDI is already calculated and stored */
static void calculate_auth_digest(uint8_t *nonce, uint8_t *auth_digest)
{
/* TODO: Check so the CDI is non-zero? */
blake2s_ctx ctx = {0};
// Generate a 16 byte authentication digest
blake2s_init(&ctx, 16, NULL, 0);
blake2s_update(&ctx, (const void *)cdi, 32);
blake2s_update(&ctx, nonce, 16);
blake2s_final(&ctx, auth_digest);
}
/* Generates a 16 byte nonce */
static void generate_nonce(uint32_t *nonce)
{
for (uint8_t i = 0; i < 4; i++) {
nonce[i] = rng_get_word();
}
return;
}
/* Returns the authentication digest and random nonce. Requires that the CDI is
* already calculated and stored */
void auth_app_create(struct auth_metadata *auth_table)
{
uint8_t nonce[16];
uint8_t auth_digest[16];
generate_nonce((uint32_t *)nonce);
calculate_auth_digest(nonce, auth_digest);
memcpy_s(auth_table->authentication_digest, 16, auth_digest, 16);
memcpy_s(auth_table->nonce, 16, nonce, 16);
return;
}
bool auth_app_authenticate(struct auth_metadata *auth_table)
{
uint8_t auth_digest[16];
calculate_auth_digest(auth_table->nonce, auth_digest);
if (memeq(auth_digest, auth_table->authentication_digest, 16)) {
return true;
}
return false;
}

View File

@ -0,0 +1,14 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef AUTH_APP_H
#define AUTH_APP_H
#include "partition_table.h"
#include <stdbool.h>
void auth_app_create(struct auth_metadata *auth_table);
bool auth_app_authenticate(struct auth_metadata *auth_table);
#endif

View File

@ -7,7 +7,7 @@ OUTPUT_ARCH("riscv")
ENTRY(_start)
/* Define stack size */
STACK_SIZE = 0xEF0; /* 3824 B */
STACK_SIZE = 3000;
MEMORY
{

View File

@ -0,0 +1,217 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tkey/tk1_mem.h>
#include "flash.h"
#include "spi.h"
// clang-format off
static volatile uint32_t *timer = (volatile uint32_t *)TK1_MMIO_TIMER_TIMER;
static volatile uint32_t *timer_prescaler = (volatile uint32_t *)TK1_MMIO_TIMER_PRESCALER;
static volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER_STATUS;
static volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL;
// clang-format on
// CPU clock frequency in Hz
#define CPUFREQ 21000000
#define PAGE_SIZE 256
static void delay(int timeout_ms)
{
// Tick once every centisecond
*timer_prescaler = CPUFREQ / 100;
*timer = timeout_ms / 10;
*timer_ctrl |= (1 << TK1_MMIO_TIMER_CTRL_START_BIT);
while (*timer_status != 0) {
}
// Stop timer
*timer_ctrl |= (1 << TK1_MMIO_TIMER_CTRL_STOP_BIT);
}
bool flash_is_busy(void)
{
uint8_t tx_buf = READ_STATUS_REG_1;
uint8_t rx_buf = {0x00};
spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, &rx_buf, sizeof(rx_buf));
if (rx_buf & (1 << STATUS_REG_BUSY_BIT)) {
return true;
}
return false;
}
// Blocking until !busy
void flash_wait_busy(void)
{
while (flash_is_busy()) {
delay(10);
}
}
void flash_write_enable(void)
{
uint8_t tx_buf = WRITE_ENABLE;
spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0);
}
void flash_write_disable(void)
{
uint8_t tx_buf = WRITE_DISABLE;
spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0);
}
void flash_sector_erase(uint32_t address)
{
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = SECTOR_ERASE;
tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
/* tx_buf[3] is within a sector, and hence does not make a difference */
flash_write_enable();
spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0);
flash_wait_busy();
}
void flash_block_32_erase(uint32_t address)
{
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = BLOCK_ERASE_32K;
tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF;
flash_write_enable();
spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0);
flash_wait_busy();
}
// 64 KiB block erase, only cares about address bits 16 and above.
void flash_block_64_erase(uint32_t address)
{
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = BLOCK_ERASE_64K;
tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
/* tx_buf[2] and tx_buf[3] is within a block, and hence does not make a
* difference */
flash_write_enable();
spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0);
flash_wait_busy();
}
void flash_release_powerdown(void)
{
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = RELEASE_POWER_DOWN;
spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0);
}
void flash_powerdown(void)
{
uint8_t tx_buf = POWER_DOWN;
spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0);
}
void flash_read_manufacturer_device_id(uint8_t *device_id)
{
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = READ_MANUFACTURER_ID;
spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, device_id, 2);
}
void flash_read_jedec_id(uint8_t *jedec_id)
{
uint8_t tx_buf = READ_JEDEC_ID;
spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, jedec_id, 3);
}
void flash_read_unique_id(uint8_t *unique_id)
{
uint8_t tx_buf[5] = {0x00};
tx_buf[0] = READ_UNIQUE_ID;
spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, unique_id, 8);
}
void flash_read_status(uint8_t *status_reg)
{
uint8_t tx_buf = READ_STATUS_REG_1;
spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg, 1);
tx_buf = READ_STATUS_REG_2;
spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg + 1, 1);
}
int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size)
{
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = READ_DATA;
tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF;
return spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, dest_buf, size);
}
// Only handles writes where the least significant byte of the start address is
// zero.
int flash_write_data(uint32_t address, uint8_t *data, size_t size)
{
if (size <= 0 || size > 4096) {
return -1;
}
size_t left = size;
uint8_t *p_data = data;
size_t n_bytes = 0;
uint8_t tx_buf[4] = {
PAGE_PROGRAM, /* tx_buf[0] */
(address >> ADDR_BYTE_3_BIT) & 0xFF, /* tx_buf[1] */
(address >> ADDR_BYTE_2_BIT) & 0xFF, /* tx_buf[2] */
0x00, /* tx_buf[3] */
};
while (left > 0) {
if (left >= PAGE_SIZE) {
n_bytes = PAGE_SIZE;
} else {
n_bytes = left;
}
flash_write_enable();
if (spi_transfer(tx_buf, sizeof(tx_buf), p_data, n_bytes, NULL,
0) != 0) {
return -1;
}
left -= n_bytes;
p_data += n_bytes;
address += n_bytes;
tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
flash_wait_busy();
}
return 0;
}

View File

@ -0,0 +1,58 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef TKEY_FLASH_H
#define TKEY_FLASH_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define WRITE_ENABLE 0x06
#define WRITE_DISABLE 0x04
#define READ_STATUS_REG_1 0x05
#define READ_STATUS_REG_2 0x35
#define WRITE_STATUS_REG 0x01
#define PAGE_PROGRAM 0x02
#define SECTOR_ERASE 0x20
#define BLOCK_ERASE_32K 0x52
#define BLOCK_ERASE_64K 0xD8
#define CHIP_ERASE 0xC7
#define POWER_DOWN 0xB9
#define READ_DATA 0x03
#define RELEASE_POWER_DOWN 0xAB
#define READ_MANUFACTURER_ID 0x90
#define READ_JEDEC_ID 0x9F
#define READ_UNIQUE_ID 0x4B
#define ENABLE_RESET 0x66
#define RESET 0x99
#define ADDR_BYTE_3_BIT 16
#define ADDR_BYTE_2_BIT 8
#define ADDR_BYTE_1_BIT 0
#define STATUS_REG_BUSY_BIT 0
#define STATUS_REG_WEL_BIT 1
bool flash_is_busy(void);
void flash_wait_busy(void);
void flash_write_enable(void);
void flash_write_disable(void);
void flash_sector_erase(uint32_t address);
void flash_block_32_erase(uint32_t address);
void flash_block_64_erase(uint32_t address);
void flash_release_powerdown(void);
void flash_powerdown(void);
void flash_read_manufacturer_device_id(uint8_t *device_id);
void flash_read_jedec_id(uint8_t *jedec_id);
void flash_read_unique_id(uint8_t *unique_id);
void flash_read_status(uint8_t *status_reg);
int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size);
int flash_write_data(uint32_t address, uint8_t *data, size_t size);
#endif

View File

@ -10,11 +10,16 @@
#include <tkey/debug.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include <tkey/led.h>
#include "auth_app.h"
#include "blake2s/blake2s.h"
#include "partition_table.h"
#include "preload_app.h"
#include "proto.h"
#include "state.h"
#include "syscall_enable.h"
#include "resetinfo.h"
// clang-format off
static volatile uint32_t *uds = (volatile uint32_t *)TK1_MMIO_UDS_FIRST;
@ -33,8 +38,24 @@ static volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER
static volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL;
static volatile uint32_t *ram_addr_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_ADDR_RAND;
static volatile uint32_t *ram_data_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_DATA_RAND;
static volatile struct reset *resetinfo = (volatile struct reset *)TK1_MMIO_RESETINFO_BASE;
// clang-format on
struct partition_table part_table;
// Locked down what app can start from first flash slot to be exactly
// this size, producing this digest.
//
// To update this, compute the BLAKE2s digest of the app.bin and
// insert the size in bytes.
#define APP_SIZE_SLOT0 21684
// BLAKE2s digest of testloadapp.bin
const uint8_t allowed_app_digest[32] = {
0x3a, 0x34, 0x6f, 0x1f, 0xb7, 0x7f, 0xa6, 0x71, 0x9b, 0x69, 0x8,
0x36, 0xa0, 0x5, 0xe, 0x26, 0x48, 0x8d, 0xab, 0x6a, 0x51, 0xa6,
0xe1, 0x18, 0x53, 0xa3, 0x64, 0xc6, 0x5b, 0x42, 0x49, 0xb7,
};
// Context for the loading of a TKey program
struct context {
uint32_t left; // Bytes left to receive
@ -42,6 +63,8 @@ struct context {
uint8_t *loadaddr; // Where we are currently loading a TKey program
bool use_uss; // Use USS?
uint8_t uss[32]; // User Supplied Secret, if any
uint8_t flash_slot; // App is loaded from flash slot number
volatile uint8_t *ver_digest; // Verify loaded app against this digest
};
static void print_hw_version(void);
@ -56,11 +79,14 @@ static enum state initial_commands(const struct frame_header *hdr,
static enum state loading_commands(const struct frame_header *hdr,
const uint8_t *cmd, enum state state,
struct context *ctx);
static void run(const struct context *ctx);
#if !defined(SIMULATION)
static uint32_t xorwow(uint32_t state, uint32_t acc);
#endif
static void scramble_ram(void);
static int compute_app_digest(uint8_t *digest);
static int load_flash_app(struct partition_table *part_table,
uint8_t digest[32], uint8_t slot);
static enum state start_where(struct context *ctx);
static void print_hw_version(void)
{
@ -300,7 +326,7 @@ static enum state loading_commands(const struct frame_header *hdr,
memcpy_s(&rsp[1], CMDSIZE - 1, &ctx->digest, 32);
fwreply(*hdr, FW_RSP_LOAD_APP_DATA_READY, rsp);
state = FW_STATE_RUN;
state = FW_STATE_CDI;
break;
}
@ -320,13 +346,11 @@ static enum state loading_commands(const struct frame_header *hdr,
return state;
}
static void run(const struct context *ctx)
static void jump_to_app(void)
{
/* Start of app is always at the beginning of RAM */
*app_addr = TK1_RAM_BASE;
// CDI = hash(uds, hash(app), uss)
compute_cdi(ctx->digest, ctx->use_uss, ctx->uss);
debug_puts("Flipping to app mode!\n");
debug_puts("Jumping to ");
debug_putinthex(*app_addr);
@ -365,6 +389,51 @@ static void run(const struct context *ctx)
__builtin_unreachable();
}
static int load_flash_app(struct partition_table *part_table,
uint8_t digest[32], uint8_t slot)
{
if (slot >= N_PRELOADED_APP) {
return -1;
}
if (preload_load(part_table, slot) == -1) {
return -1;
}
*app_size = part_table->pre_app_data[slot].size;
if (*app_size > TK1_APP_MAX_SIZE) {
return -1;
}
int digest_err = compute_app_digest(digest);
assert(digest_err == 0);
print_digest(digest);
return 0;
}
static enum state auth_flash_app(const struct context *ctx, struct partition_table *part_table)
{
if (ctx->flash_slot >= N_PRELOADED_APP) {
return FW_STATE_FAIL;
}
if (part_table->pre_app_data[ctx->flash_slot].status == PRE_LOADED_STATUS_PRESENT) {
debug_puts("Create auth\n");
auth_app_create(&part_table->pre_app_data[ctx->flash_slot].auth);
part_table->pre_app_data[ctx->flash_slot].status = PRE_LOADED_STATUS_AUTH;
part_table_write(part_table);
}
if (!auth_app_authenticate(&part_table->pre_app_data[ctx->flash_slot].auth)) {
debug_puts("!Authenticated\n");
return FW_STATE_FAIL;
}
return FW_STATE_START;
}
#if !defined(SIMULATION)
static uint32_t xorwow(uint32_t state, uint32_t acc)
{
@ -399,6 +468,62 @@ static void scramble_ram(void)
*ram_data_rand = rnd_word();
}
/* Computes the blake2s digest of the app loaded into RAM */
static int compute_app_digest(uint8_t *digest)
{
blake2s_ctx b2s_ctx = {0};
return blake2s(digest, 32, NULL, 0, (const void *)TK1_RAM_BASE,
*app_size, &b2s_ctx);
}
static enum state start_where(struct context *ctx)
{
// Where do we start? Read resetinfo 'startfrom'
switch (resetinfo->type) {
case START_DEFAULT:
// fallthrough
case START_FLASH1:
ctx->flash_slot = 0;
ctx->ver_digest = NULL;
return FW_STATE_LOAD_FLASH;
case START_FLASH2:
ctx->flash_slot = 1;
ctx->ver_digest = NULL;
return FW_STATE_LOAD_FLASH;
case START_FLASH1_VER:
ctx->flash_slot = 0;
ctx->ver_digest = resetinfo->app_digest;
return FW_STATE_LOAD_FLASH;
case START_FLASH2_VER:
ctx->flash_slot = 1;
ctx->ver_digest = resetinfo->app_digest;
return FW_STATE_LOAD_FLASH;
case START_CLIENT:
ctx->ver_digest = NULL;
return FW_STATE_WAITCOMMAND;
case START_CLIENT_VER:
ctx->ver_digest = resetinfo->app_digest;
return FW_STATE_WAITCOMMAND;
default:
debug_puts("Unknown startfrom\n");
return FW_STATE_FAIL;
}
}
int main(void)
{
struct context ctx = {0};
@ -406,6 +531,8 @@ int main(void)
uint8_t cmd[CMDSIZE] = {0};
enum state state = FW_STATE_INITIAL;
led_set(LED_BLUE);
print_hw_version();
/*@-mustfreeonly@*/
@ -418,13 +545,50 @@ int main(void)
scramble_ram();
// TODO Remove
// Wait for terminal program and a character to be typed
/*
enum ioend endpoint = IO_NONE;
uint8_t available = 0;
uint8_t in = 0;
if (readselect(IO_CDC, &endpoint, &available) < 0) {
// readselect failed! I/O broken? Just redblink.
assert(1 == 2);
}
if (read(IO_CDC, &in, 1, 1) < 0) {
// read failed! I/O broken? Just redblink.
assert(1 == 2);
}
*/
// TODO end of remove block
if (part_table_read(&part_table) != 0) {
// Couldn't read or create partition table
assert(1 != 2);
}
#if defined(SIMULATION)
run(&ctx);
#endif
// Hardocde size of slot 0
part_table.pre_app_data[0].size = APP_SIZE_SLOT0;
// part_table.pre_app_data[1].size = 0x20000;
// TODO Just start something from flash without looking in
// FW_RAM.
//state = FW_STATE_LOAD_FLASH;
for (;;) {
switch (state) {
case FW_STATE_INITIAL:
state = start_where(&ctx);
break;
case FW_STATE_WAITCOMMAND:
if (readcommand(&hdr, cmd, state) == -1) {
state = FW_STATE_FAIL;
break;
@ -445,9 +609,52 @@ int main(void)
state = loading_commands(&hdr, cmd, state, &ctx);
break;
case FW_STATE_RUN:
run(&ctx);
break; // This is never reached!
case FW_STATE_CDI:
// CDI = hash(uds, hash(app), uss)
compute_cdi(ctx.digest, ctx.use_uss, ctx.uss);
state = FW_STATE_START;
break;
case FW_STATE_LOAD_FLASH:
// TODO Just lie and say that an app is present but not yet
// authenticated.
part_table.pre_app_data[ctx.flash_slot].status = PRE_LOADED_STATUS_PRESENT;
if (load_flash_app(&part_table, ctx.digest, ctx.flash_slot) < 0) {
debug_puts("Couldn't load app from flash\n");
state = FW_STATE_FAIL;
break;
}
if (ctx.flash_slot == 0) {
print_digest(allowed_app_digest);
if (!memeq(ctx.digest, allowed_app_digest, 32)) {
puts(IO_CDC, "app not allowed!\r\n");
assert(1 == 2);
}
}
// CDI = hash(uds, hash(app), uss)
compute_cdi(ctx.digest, ctx.use_uss, ctx.uss);
state = auth_flash_app(&ctx, &part_table);
break;
case FW_STATE_START:
if (ctx.ver_digest != NULL) {
print_digest(ctx.digest);
if (!memeq(ctx.digest, (void*)ctx.ver_digest, sizeof(ctx.digest))) {
debug_puts("Digests do not match\n");
state = FW_STATE_FAIL;
break;
}
}
memset((void*)resetinfo->app_digest, 0, sizeof(resetinfo->app_digest));
jump_to_app();
break; // Not reached
case FW_STATE_FAIL:
// fallthrough
@ -462,5 +669,6 @@ int main(void)
/*@ -compdestroy @*/
/* We don't care about memory leaks here. */
return (int)0xcafebabe;
}

View File

@ -0,0 +1,75 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <tkey/lib.h>
#include <stdbool.h>
#include "mgmt_app.h"
#include "auth_app.h"
#include "partition_table.h"
/* Returns true if an management app is already registered */
static bool mgmt_app_registered(struct management_app_metadata *mgmt_table)
{
if (mgmt_table->status == 0x00) {
/* No management app registered */
return false;
// TODO: Should we also check nonce, authentication digest for
// non-zero?
}
return true;
}
/* Authenticate an management app */
bool mgmt_app_authenticate(struct management_app_metadata *mgmt_table)
{
if (!mgmt_app_registered(mgmt_table)) {
return false;
}
return auth_app_authenticate(&mgmt_table->auth);
}
/* Register an management app, returns zero on success */
int mgmt_app_register(struct partition_table *part_table)
{
/* Check if the current app is the mgmt app */
if (mgmt_app_authenticate(&part_table->mgmt_app_data)) {
return 0;
}
/* Check if another management app is registered */
if (mgmt_app_registered(&part_table->mgmt_app_data)) {
return -1;
}
auth_app_create(&part_table->mgmt_app_data.auth);
part_table->mgmt_app_data.status = 0x01;
part_table_write(part_table);
return 0;
}
/* Unregister the currently registered app, returns zero on success */
int mgmt_app_unregister(struct partition_table *part_table)
{
/* Only the management app should be able to unregister itself */
if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) {
return -1;
}
part_table->mgmt_app_data.status = 0;
memset(part_table->mgmt_app_data.auth.nonce, 0x00,
sizeof(part_table->mgmt_app_data.auth.nonce));
memset(part_table->mgmt_app_data.auth.authentication_digest, 0x00,
sizeof(part_table->mgmt_app_data.auth.authentication_digest));
part_table_write(part_table);
return 0;
}

View File

@ -0,0 +1,15 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef MGMT_APP_H
#define MGMT_APP_H
#include "partition_table.h"
#include <stdbool.h>
bool mgmt_app_authenticate(struct management_app_metadata *mgmt_table);
int mgmt_app_register(struct partition_table *part_table);
int mgmt_app_unregister(struct partition_table *part_table);
#endif

View File

@ -0,0 +1,50 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdint.h>
#include <tkey/lib.h>
#include "flash.h"
#include "partition_table.h"
#include "proto.h"
int part_table_read(struct partition_table *part_table)
{
// Read from flash, if it exists, otherwise create a new one.
flash_release_powerdown();
memset(part_table, 0x00, sizeof(*part_table));
flash_read_data(ADDR_PARTITION_TABLE, (uint8_t *)part_table,
sizeof(*part_table));
// TODO: Implement redundancy and consistency check
if (part_table->header.version != PART_TABLE_VERSION) {
// Partition table is not ours. Make a new one, and store it.
memset(part_table, 0x00, sizeof(*part_table));
part_table->header.version = PART_TABLE_VERSION;
for (int i = 0; i < 4; i++) {
part_table->app_storage[i].addr_start =
(ADDR_STORAGE_AREA + i * SIZE_STORAGE_AREA);
part_table->app_storage[i].size = SIZE_STORAGE_AREA;
}
part_table_write(part_table);
}
// Now the partition table is synced between flash and RAM.
return 0;
}
int part_table_write(struct partition_table *part_table)
{
flash_sector_erase(ADDR_PARTITION_TABLE);
flash_write_data(ADDR_PARTITION_TABLE, (uint8_t *)part_table,
sizeof(*part_table));
return 0;
}

View File

@ -0,0 +1,124 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef PARTITION_TABLE_H
#define PARTITION_TABLE_H
#include <stdint.h>
/* ---- Flash ---- ---- */
/* name size start addr */
/* ---- ---- ---- */
/* bitstream 128KiB 0x00 */
/* ---- ---- ---- */
/* Partition 64KiB 0x20000 */
/* ---- ---- ---- */
/* Pre load 1 128KiB 0x30000 */
/* Pre load 2 128KiB 0x50000 */
/* ---- ---- ---- */
/* storage 1 128KiB 0x70000 */
/* storage 2 128KiB 0x90000 */
/* storage 3 128KiB 0xB0000 */
/* storage 4 128KiB 0xD0000 */
/* ---- ---- ---- */
/* To simplify all blocks are aligned with the 64KiB blocks on the W25Q80DL
* flash. */
#define PART_TABLE_VERSION 1
#define ADDR_BITSTREAM 0UL
#define SIZE_BITSTREAM 0x20000UL // 128KiB
#define ADDR_PARTITION_TABLE (ADDR_BITSTREAM + SIZE_BITSTREAM)
#define SIZE_PARTITION_TABLE \
0x10000UL // 64KiB, 60 KiB reserved, 2 flash pages (2 x 4KiB) for the
// partition table
#define N_PRELOADED_APP 2
#define ADDR_PRE_LOADED_APP_0 (ADDR_PARTITION_TABLE + SIZE_PARTITION_TABLE)
#define SIZE_PRE_LOADED_APP 0x20000UL // 128KiB
// Pre-loaded app present and authenticated
#define PRE_LOADED_STATUS_AUTH 0x01
// Pre-loaded app present but not yet authenticated
#define PRE_LOADED_STATUS_PRESENT 0x02
#define ADDR_STORAGE_AREA (ADDR_PRE_LOADED_APP_0 + (N_PRELOADED_APP * SIZE_PRE_LOADED_APP))
#define SIZE_STORAGE_AREA 0x20000UL // 128KiB
#define N_STORAGE_AREA 4
#define EMPTY_AREA
/* Partition Table */
/*- Table header */
/* - 1 bytes Version */
/**/
/*- Management device app */
/* - Status. */
/* - 16 byte random nonce. */
/* - 16 byte authentication digest. */
/**/
/*- Pre-loaded device app 1 */
/* - 1 byte status. */
/* - 4 bytes length. */
/* - 16 bytes random nonce. */
/* - 16 bytes authentication digest. */
/* - 32 bytes digest. */
/* - 64 bytes signature. */
/**/
/*- Pre-loaded device app 2 */
/* - 1 byte status. */
/* - 4 bytes length. */
/* - 16 bytes random nonce. */
/* - 16 bytes authentication digest. */
/* - 32 bytes digest. */
/* - 64 bytes signature. */
/**/
/*- Device app storage area */
/* - 1 byte status. */
/* - 16 bytes random nonce. */
/* - 16 bytes authentication tag. */
/* - 4 bytes physical start address. */
/* - 4 bytes physical end address. */
struct auth_metadata {
uint8_t nonce[16];
uint8_t authentication_digest[16];
} __attribute__((packed));
struct management_app_metadata {
uint8_t status;
struct auth_metadata auth;
} __attribute__((packed));
struct pre_loaded_app_metadata {
uint8_t status;
uint32_t size;
struct auth_metadata auth;
uint8_t digest[32];
uint8_t signature[64];
} __attribute__((packed));
struct app_storage_area {
uint8_t status;
struct auth_metadata auth;
uint32_t addr_start;
uint32_t size;
} __attribute__((packed));
struct table_header {
uint8_t version;
} __attribute__((packed));
struct partition_table {
struct table_header header;
struct management_app_metadata mgmt_app_data;
struct pre_loaded_app_metadata pre_app_data[N_PRELOADED_APP];
struct app_storage_area app_storage[N_STORAGE_AREA];
} __attribute__((packed));
int part_table_read(struct partition_table *part_table);
int part_table_write(struct partition_table *part_table);
#endif

View File

@ -0,0 +1,180 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tkey/debug.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "flash.h"
#include "mgmt_app.h"
#include "partition_table.h"
#include "preload_app.h"
static uint32_t slot_to_start_address(uint8_t slot) {
return ADDR_PRE_LOADED_APP_0 + slot * SIZE_PRE_LOADED_APP;
}
/* Returns non-zero if the app is valid */
bool preload_check_valid_app(struct partition_table *part_table,
uint8_t slot)
{
if (slot >= N_PRELOADED_APP) {
return false;
}
if (part_table->pre_app_data[slot].status == 0x00 &&
part_table->pre_app_data[slot].size == 0) {
/*No valid app*/
return false;
}
return true;
}
/* Loads a preloaded app from flash to app RAM */
int preload_load(struct partition_table *part_table, uint8_t from_slot)
{
if (from_slot >= N_PRELOADED_APP) {
return -1;
}
/*Check for a valid app in flash */
if (!preload_check_valid_app(part_table, from_slot)) {
return -1;
}
uint8_t *loadaddr = (uint8_t *)TK1_RAM_BASE;
/* Read from flash, straight into RAM */
int ret = flash_read_data(slot_to_start_address(from_slot), loadaddr,
part_table->pre_app_data[from_slot].size);
return ret;
}
/* Expects to receive chunks of data up to 4096 bytes to store into the
* preloaded area. The offset needs to be kept and updated between each call.
* Once done, call preload_store_finalize() with the last parameters.
* */
int preload_store(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size, uint8_t to_slot)
{
/* Check if we are allowed to store */
if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) {
return -3;
}
/* Check for a valid app in flash, bale out if it already exists */
if (preload_check_valid_app(part_table, to_slot)) {
return -1;
}
if ((offset + size) > SIZE_PRE_LOADED_APP || size > 4096) {
/* Writing outside of area */
return -2;
}
uint32_t address = slot_to_start_address(to_slot) + offset;
debug_puts("preload_store: write to addr: ");
debug_putinthex(address);
debug_lf();
return flash_write_data(address, data, size);
}
int preload_store_finalize(struct partition_table *part_table, size_t app_size,
uint8_t app_digest[32], uint8_t app_signature[64],
uint8_t to_slot)
{
if (to_slot >= N_PRELOADED_APP) {
return -4;
}
/* Check if we are allowed to store */
if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) {
return -3;
}
/* Check for a valid app in flash, bale out if it already exists */
if (preload_check_valid_app(part_table, to_slot)) {
return -1;
}
if (app_size == 0 || app_size > SIZE_PRE_LOADED_APP) {
return -2;
}
part_table->pre_app_data[to_slot].size = app_size;
part_table->pre_app_data[to_slot].status =
PRE_LOADED_STATUS_PRESENT; /* Stored but not yet authenticated */
memcpy_s(part_table->pre_app_data[to_slot].digest,
sizeof(part_table->pre_app_data[to_slot].digest),
app_digest, 32);
memcpy_s(part_table->pre_app_data[to_slot].signature,
sizeof(part_table->pre_app_data[to_slot].signature),
app_signature, 64);
debug_puts("preload_*_final: size: ");
debug_putinthex(app_size);
debug_lf();
part_table_write(part_table);
/* Force a restart to authenticate the stored app */
/* TODO: Should this be done by the management app or by firmware? */
return 0;
}
int preload_delete(struct partition_table *part_table, uint8_t slot)
{
if (slot >= N_PRELOADED_APP) {
return -4;
}
/* Check if we are allowed to deleted */
if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) {
return -3;
}
/*Check for a valid app in flash */
if (!preload_check_valid_app(part_table, slot)) {
return 0;
// TODO: Nothing here, return zero like all is good?
}
part_table->pre_app_data[slot].size = 0;
part_table->pre_app_data[slot].status = 0;
memset(part_table->pre_app_data[slot].auth.nonce, 0x00,
sizeof(part_table->pre_app_data[slot].auth.nonce));
memset(part_table->pre_app_data[slot].auth.authentication_digest, 0x00,
sizeof(part_table->pre_app_data[slot].auth.authentication_digest));
memset(part_table->pre_app_data[slot].digest, 0,
sizeof(part_table->pre_app_data[slot].digest));
memset(part_table->pre_app_data[slot].signature, 0,
sizeof(part_table->pre_app_data[slot].signature));
part_table_write(part_table);
/* Assumes the area is 64 KiB block aligned */
flash_block_64_erase(slot_to_start_address(slot)); // Erase first 64 KB block
flash_block_64_erase(slot_to_start_address(slot) + 0x10000); // Erase first 64 KB block
return 0;
}
int preload_get_digsig(struct partition_table *part_table, uint8_t app_digest[32], uint8_t app_signature[64], uint8_t slot) {
if (slot >= N_PRELOADED_APP) {
return -1;
}
memcpy_s(app_digest, 32, part_table->pre_app_data[slot].digest, sizeof(part_table->pre_app_data[slot].digest));
memcpy_s(app_signature, 64, part_table->pre_app_data[slot].signature, sizeof(part_table->pre_app_data[slot].signature));
return 0;
}

View File

@ -0,0 +1,25 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef PRELOAD_APP_H
#define PRELOAD_APP_H
#include "partition_table.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
bool preload_check_valid_app(struct partition_table *part_table,
uint8_t slot);
int preload_load(struct partition_table *part_table, uint8_t from_slot);
int preload_store(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size, uint8_t to_slot);
int preload_store_finalize(struct partition_table *part_table, size_t app_size,
uint8_t app_digest[32], uint8_t app_signature[64],
uint8_t to_slot);
int preload_delete(struct partition_table *part_table, uint8_t slot);
int preload_get_digsig(struct partition_table *part_table,
uint8_t app_digest[32], uint8_t app_signature[64],
uint8_t slot);
#endif

View File

@ -0,0 +1,30 @@
// Copyright (C) 2025 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef TKEY_RESETINFO_H
#define TKEY_RESETINFO_H
#include <stdint.h>
#define TK1_MMIO_RESETINFO_BASE 0xd0000f00
#define TK1_MMIO_RESETINFO_SIZE 0x100
enum reset_start {
START_DEFAULT = 0, // Probably cold boot
START_FLASH1 = 1,
START_FLASH2 = 2,
START_FLASH1_VER = 3,
START_FLASH2_VER = 4,
START_CLIENT = 5,
START_CLIENT_VER = 6,
};
struct reset {
uint32_t type; // Reset type
uint8_t app_digest[32]; // Program digest
uint8_t next_app_data[220]; // Data to leave around for next app
};
#endif

View File

@ -0,0 +1,29 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include "rng.h"
#include <tkey/tk1_mem.h>
#include <stdint.h>
// clang-format off
static volatile uint32_t *trng_status = (volatile uint32_t *)TK1_MMIO_TRNG_STATUS;
static volatile uint32_t *trng_entropy = (volatile uint32_t *)TK1_MMIO_TRNG_ENTROPY;
// clang-format on
//
//
uint32_t rng_get_word(void)
{
while ((*trng_status & (1 << TK1_MMIO_TRNG_STATUS_READY_BIT)) == 0) {
}
return *trng_entropy;
}
uint32_t rng_xorwow(uint32_t state, uint32_t acc)
{
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state += acc;
return state;
}

View File

@ -0,0 +1,11 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef RNG_H
#define RNG_H
#include <stdint.h>
uint32_t rng_get_word(void);
uint32_t rng_xorwow(uint32_t state, uint32_t acc);
#endif

View File

@ -0,0 +1,90 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include "spi.h"
#include <tkey/tk1_mem.h>
#include <stddef.h>
#include <stdint.h>
// clang-format off
static volatile uint32_t *spi_en = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x200);
static volatile uint32_t *spi_xfer = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x204);
static volatile uint32_t *spi_data = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x208);
// clang-format on
// returns non-zero when the SPI-master is ready, and zero if not
// ready. This can be used to check if the SPI-master is available
// in the hardware.
int spi_ready(void)
{
return *spi_xfer;
}
static void spi_enable(void)
{
*spi_en = 1;
}
static void spi_disable(void)
{
*spi_en = 0;
}
static void _spi_write(uint8_t *cmd, size_t size)
{
for (size_t i = 0; i < size; i++) {
while (!spi_ready()) {
}
*spi_data = cmd[i];
*spi_xfer = 1;
}
while (!spi_ready()) {
}
}
static void _spi_read(uint8_t *buf, size_t size)
{
while (!spi_ready()) {
}
for (size_t i = 0; i < size; i++) {
*spi_data = 0x00;
*spi_xfer = 1;
// wait until spi master is done
while (!spi_ready()) {
}
buf[i] = (*spi_data & 0xff);
}
}
// Function to both read and write data to the connected SPI flash.
int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size,
uint8_t *rx_buf, size_t rx_size)
{
if (cmd == NULL || cmd_size == 0) {
return -1;
}
spi_enable();
_spi_write(cmd, cmd_size);
if (tx_buf != NULL || tx_size != 0) {
_spi_write(tx_buf, tx_size);
}
if (rx_buf != NULL && rx_size != 0) {
_spi_read(rx_buf, rx_size);
}
spi_disable();
return 0;
}

View File

@ -0,0 +1,14 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef TKEY_SPI_H
#define TKEY_SPI_H
#include <stddef.h>
#include <stdint.h>
int spi_ready(void);
int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size,
uint8_t *rx_buf, size_t rx_size);
#endif

View File

@ -4,8 +4,19 @@
*/
#include <tkey/tk1_mem.h>
#ifdef QEMU_SYSCALL
#define picorv32_retirq_insn(...) \
mv ra, x3; \
ret
#else
#include "picorv32/custom_ops.S" // PicoRV32 custom instructions
#endif
#define illegal_insn() .word 0
// Variables in bss

View File

@ -8,8 +8,11 @@
enum state {
FW_STATE_INITIAL,
FW_STATE_WAITCOMMAND,
FW_STATE_LOADING,
FW_STATE_RUN,
FW_STATE_CDI,
FW_STATE_LOAD_FLASH,
FW_STATE_START,
FW_STATE_FAIL,
FW_STATE_MAX,
};

View File

@ -0,0 +1,197 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tkey/debug.h>
#include <tkey/lib.h>
#include "auth_app.h"
#include "flash.h"
#include "partition_table.h"
#include "storage.h"
/* Returns the index of the first empty area. If there is no empty area -1 is
* returned. */
static int get_first_empty(struct partition_table *part_table)
{
for (uint8_t i = 0; i < N_STORAGE_AREA; i++) {
if (part_table->app_storage[i].status == 0x00) {
return i;
}
}
return -1;
}
/* Returns the index of the area an app has allocated. If no area is
* authenticated -1 is returned. */
static int storage_get_area(struct partition_table *part_table)
{
for (uint8_t i = 0; i < N_STORAGE_AREA; i++) {
if (part_table->app_storage[i].status != 0x00) {
if (auth_app_authenticate(
&part_table->app_storage[i].auth)) {
return i;
}
}
}
return -1;
}
/* Allocate a new area for an app. Returns zero if a new area is allocated, one
* if an area already was allocated, and negative values for errors. */
int storage_allocate_area(struct partition_table *part_table)
{
if (storage_get_area(part_table) != -1) {
/* Already has an area */
return 1;
}
int index = get_first_empty(part_table);
if (index == -1) {
/* No empty slot */
return -1;
}
/* Allocate the empty index found */
/* Erase area first */
/* Assumes the area is 64 KiB block aligned */
flash_block_64_erase(part_table->app_storage[index]
.addr_start); // Erase first 64 KB block
flash_block_64_erase(part_table->app_storage[index].addr_start +
0x10000); // Erase second 64 KB block
/* Write partition table lastly */
part_table->app_storage[index].status = 0x01;
auth_app_create(&part_table->app_storage[index].auth);
part_table_write(part_table);
return 0;
}
/* Dealloacate a previously allocated storage area. Returns zero on success, and
* non-zero on errors. */
int storage_deallocate_area(struct partition_table *part_table)
{
int index = storage_get_area(part_table);
if (index == -1) {
/* No area to deallocate */
return -1;
}
/* Erase area first */
/* Assumes the area is 64 KiB block aligned */
flash_block_64_erase(part_table->app_storage[index]
.addr_start); // Erase first 64 KB block
flash_block_64_erase(part_table->app_storage[index].addr_start +
0x10000); // Erase second 64 KB block
/* Clear partition table lastly */
part_table->app_storage[index].status = 0;
memset(part_table->app_storage[index].auth.nonce, 0x00,
sizeof(part_table->app_storage[index].auth.nonce));
memset(
part_table->app_storage[index].auth.authentication_digest, 0x00,
sizeof(part_table->app_storage[index].auth.authentication_digest));
part_table_write(part_table);
return 0;
}
/* Erases sector. Offset of a sector to begin erasing, must be a multiple of
* the sector size. Size to erase in bytes, must be a multiple of the sector *
* size. Returns zero on success, negative error code on failure */
int storage_erase_sector(struct partition_table *part_table, uint32_t offset,
size_t size)
{
int index = storage_get_area(part_table);
if (index == -1) {
/* No allocated area */
return -1;
}
/* Cannot erase less than one sector */
if (size < 4096 || size > part_table->app_storage[index].size ||
size % 4096 != 0) {
return -2;
}
if ((offset) >= part_table->app_storage[index].size) {
return -2;
}
uint32_t address = part_table->app_storage[index].addr_start + offset;
debug_puts("storage: erase addr: ");
debug_putinthex(address);
debug_lf();
for (size_t i = 0; i < size; i += 4096) {
flash_sector_erase(address);
address += 4096;
}
return 0;
}
/* Writes the specified data to the offset inside of the
* allocated area. Assumes area has been erased before hand.
* Currently only handles writes to one sector, hence max size of 4096 bytes.
* Returns zero on success. */
int storage_write_data(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size)
{
int index = storage_get_area(part_table);
if (index == -1) {
/* No allocated area */
return -1;
}
if ((offset + size) > part_table->app_storage[index].size ||
size > 4096) {
/* Writing outside of area */
return -2;
}
uint32_t address = part_table->app_storage[index].addr_start + offset;
debug_puts("storage: write to addr: ");
debug_putinthex(address);
debug_lf();
return flash_write_data(address, data, size);
}
/* Reads size bytes of data at the specified offset inside of
* the allocated area. Returns zero on success. Only read limit
* is the size of the allocated area */
int storage_read_data(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size)
{
int index = storage_get_area(part_table);
if (index == -1) {
/* No allocated area */
return -1;
}
if ((offset + size) > part_table->app_storage[index].size) {
/* Reading outside of area */
return -2;
}
uint32_t address = part_table->app_storage[index].addr_start + offset;
debug_puts("storage: read from addr: ");
debug_putinthex(address);
debug_lf();
return flash_read_data(address, data, size);
}

View File

@ -0,0 +1,22 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef STORAGE_H
#define STORAGE_H
#include "partition_table.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
int storage_allocate_area(struct partition_table *part_table);
int storage_deallocate_area(struct partition_table *part_table);
int storage_erase_sector(struct partition_table *part_table, uint32_t offset,
size_t size);
int storage_write_data(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size);
int storage_read_data(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size);
#endif

View File

@ -1,5 +1,13 @@
#ifdef QEMU_SYSCALL
#define picorv32_maskirq_insn(...)
#else
#include "../tk1/picorv32/custom_ops.S"
#endif
.section ".text"
.globl syscall_enable

View File

@ -5,20 +5,65 @@
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/debug.h>
#include <tkey/led.h>
#include <tkey/lib.h>
#include "mgmt_app.h"
#include "partition_table.h"
#include "preload_app.h"
#include "storage.h"
#include "../tk1/resetinfo.h"
#include "../tk1/syscall_num.h"
// clang-format off
static volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_RESET;
static volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST;
static volatile uint8_t *resetinfo = (volatile uint8_t *) TK1_MMIO_RESETINFO_BASE;
// clang-format on
int32_t syscall_handler(uint32_t number, uint32_t arg1)
extern struct partition_table part_table;
int32_t syscall_handler(uint32_t number, uint32_t arg1, uint32_t arg2,
uint32_t arg3)
{
switch (number) {
case TK1_SYSCALL_RESET:
// TODO: Take length from user
memcpy((uint8_t *)resetinfo, (uint8_t *)arg1, sizeof(struct reset));
*system_reset = 1;
return 0;
case TK1_SYSCALL_ALLOC_AREA:
if (storage_allocate_area(&part_table) < 0) {
debug_puts("couldn't allocate storage area\n");
return -1;
}
return 0;
case TK1_SYSCALL_DEALLOC_AREA:
if (storage_deallocate_area(&part_table) < 0) {
debug_puts("couldn't deallocate storage area\n");
return -1;
}
return 0;
case TK1_SYSCALL_WRITE_DATA:
if (storage_write_data(&part_table, arg1, (uint8_t *)arg2,
arg3) < 0) {
debug_puts("couldn't write storage area\n");
return -1;
}
return 0;
case TK1_SYSCALL_READ_DATA:
if (storage_read_data(&part_table, arg1, (uint8_t *)arg2,
arg3) < 0) {
debug_puts("couldn't read storage area\n");
return -1;
}
return 0;
case TK1_SYSCALL_SET_LED:
led_set(arg1);
@ -28,6 +73,30 @@ int32_t syscall_handler(uint32_t number, uint32_t arg1)
// first word. Serial is kept secret to the device
// app.
return udi[0];
case TK1_SYSCALL_PRELOAD_DELETE:
return preload_delete(&part_table, 1);
case TK1_SYSCALL_PRELOAD_STORE:
// arg1 offset
// arg2 data
// arg3 size
// always using slot 1
return preload_store(&part_table, arg1, (uint8_t *)arg2, arg3, 1);
case TK1_SYSCALL_PRELOAD_STORE_FIN:
// arg1 app_size
// arg2 app_digest
// arg3 app_signature
// always using slot 1
return preload_store_finalize(&part_table, arg1, (uint8_t *)arg2, (uint8_t *)arg3, 1);
case TK1_SYSCALL_PRELOAD_GET_DIGSIG:
return preload_get_digsig(&part_table, (uint8_t *)arg1, (uint8_t *)arg2, 1);
case TK1_SYSCALL_REG_MGMT:
return mgmt_app_register(&part_table);
default:
assert(1 == 2);
}

View File

@ -6,8 +6,18 @@
enum syscall_num {
TK1_SYSCALL_RESET = 1,
TK1_SYSCALL_SET_LED = 10,
TK1_SYSCALL_GET_VIDPID = 12,
TK1_SYSCALL_ALLOC_AREA = 2,
TK1_SYSCALL_DEALLOC_AREA = 3,
TK1_SYSCALL_WRITE_DATA = 4,
TK1_SYSCALL_READ_DATA = 5,
TK1_SYSCALL_ERASE_DATA = 6,
TK1_SYSCALL_GET_VIDPID = 7,
TK1_SYSCALL_PRELOAD_STORE = 8,
TK1_SYSCALL_PRELOAD_STORE_FIN = 9,
TK1_SYSCALL_PRELOAD_DELETE = 10,
TK1_SYSCALL_PRELOAD_GET_DIGSIG = 11,
TK1_SYSCALL_REG_MGMT = 12,
TK1_SYSCALL_SET_LED = 30,
};
#endif

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
import argparse
FLASH_SIZE_BYTES = 1 * 1024 * 1024
PRELOADED_APP_0_START = 0x30000
def run(output_path, preloaded_app_0_path):
with (
open(output_path, "wb") as output_file,
open(preloaded_app_0_path, "rb") as preloaded_app_0_file,
):
content = bytearray(b"\xFF") * FLASH_SIZE_BYTES
preloaded_app = preloaded_app_0_file.read()
content[
PRELOADED_APP_0_START : PRELOADED_APP_0_START + len(preloaded_app)
] = preloaded_app
output_file.write(content)
if __name__ == "__main__":
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("OUTPUT_PATH")
arg_parser.add_argument("PRELOADED_APP_0_PATH")
args = arg_parser.parse_args()
run(args.OUTPUT_PATH, args.PRELOADED_APP_0_PATH)

View File

@ -0,0 +1,33 @@
#!/bin/bash -e
if [ $# != 2 ]
then
echo "Usage: $0 slot_num app_file"
echo ""
echo "Where slot_num is 0 or 1."
exit
fi
SLOT_NUM="$1"
APP="$2"
if [ "$SLOT_NUM" = "0" ]; then
START_ADDRESS=0x30000
elif [ "$SLOT_NUM" = "1" ]; then
START_ADDRESS=0x50000
else
echo "Invalid slot_num"
exit 1
fi
echo "WARNING: Will erase entire partition table."
read -p "Press CTRL-C to abort. Press key to continue." -n1 -s
# Erase partition table
tillitis-iceprog -o 0x20000 -e 64k
# Erase existing pre loaded app
tillitis-iceprog -o "$START_ADDRESS" -e 128k
# Write pre loaded app
tillitis-iceprog -o "$START_ADDRESS" "$APP"