Compare commits

...

17 Commits

Author SHA1 Message Date
Michael Cardell Widerkrantz
e10f7007a4
doc: Update release notes with filesystem things 2025-04-16 13:36:18 +02:00
Michael Cardell Widerkrantz
93704287d5
doc: Update firmware README
- Describe all the new functionality.
- Revise text.
2025-04-16 13:35:42 +02:00
Mikael Ågren
ba7df35064
fw: Use globbing for FMTFILES 2025-04-16 13:28:16 +02:00
Michael Cardell Widerkrantz
2746070402
fw: Make splint work on current code. 2025-04-16 13:28:16 +02:00
Michael Cardell Widerkrantz
b70d42471b
fw: Rename FIRMWARE_SOURCES, use globbing
The symbol is only used for the check targets (with clangd and splint)
and doesn't include all the source files in the firmware. Let's just
use globbing instead.
2025-04-16 13:28:16 +02:00
Mikael Ågren
8e7108f7ee
tool: Add script to load pre-loaded app into flash 2025-04-16 13:28:15 +02:00
Michael Cardell Widerkrantz
6e793bea26
tool: Add default_partition.bin
Add default partition table

Partition table built with `./partition_table/partition_table -o
default_partition.bin --app0 ../fw/testloadapp/testloadapp.bin`
2025-04-16 13:28:15 +02:00
Michael Cardell Widerkrantz
d34e8e2c12
tool: Introduce b2s tool to help compute BLAKE2s digests 2025-04-16 13:28:15 +02:00
Mikael Ågren
91471c2962
tool: Add tool to inspect and create partition table binaries 2025-04-16 13:28:14 +02:00
Mikael Ågren
0a0f5bcec8
tool: Add tool to create a flash image containing a preloaded app at slot 0 2025-04-16 13:28:14 +02:00
Michael Cardell Widerkrantz
7e859bd06a
testloadapp: Add app for testing preloaded app functionality 2025-04-16 13:28:14 +02:00
Jonas Thörnblad
403013a0e8
resettestapp: Add resetinfo testapp
Co-authored-by: Mikael Ågren <mikael@tillitis.se>
Co-authored-by: Michael Cardell Widerkrantz <mc@tillitis.se>
2025-04-16 13:28:13 +02:00
Mikael Ågren
c9a7910965
fw: Replace custom picorv32 instructions when building for qemu 2025-04-16 13:07:22 +02:00
Mikael Ågren
50966f010d
testapp: Call storage syscalls
Calls
- TK1_SYSCALL_ALLOC_AREA
- TK1_SYSCALL_WRITE_DATA
- TK1_SYSCALL_READ_DATA
- TK1_SYSCALL_DEALLOC_AREA
2025-04-16 13:07:22 +02:00
Michael Cardell Widerkrantz
e9ddf29ce9
fw: Add pre loaded flash app and flash data storage
- Add per app flash storage
  - Adds four data areas. An app can allocate an area. Once allocated
    the area is tied to the CDI of the app and can only be
    read/written/deallocated by the same app.
- Add two pre loaded app slots to flash
  - Load an app from the first slot at boot. The app digest must match a
    specific digest specified in firmware.
  - Optionally load an app from the second slot
- Add a resetinfo area in FW_RAM which is used to signal an apps intent
  of resetting the system and, optionally, pass data to firmware or the
  next app in a bootchain.

Co-authored-by: Jonas Thörnblad <jonas@tillitis.se>
Co-authored-by: Mikael Ågren <mikael@tillitis.se>
Co-authored-by: Daniel Jobson <jobson@tillitis.se>
2025-04-16 13:07:22 +02:00
Michael Cardell Widerkrantz
1ff6e0262f
fw: Use BLAKE2s functions from tkey-libs
Instead of using the firmware's own copy of BLAKE2s functions, use the
functions from tkey-libs.
2025-04-16 13:07:21 +02:00
Michael Cardell Widerkrantz
81f3195592
tkey-libs: Import tag fw-3 of tkey-libs
- Use tag fw-3 from https://github.com/tillitis/tkey-libs/
2025-04-16 13:07:16 +02:00
69 changed files with 3769 additions and 306 deletions

4
.gitignore vendored
View File

@ -31,6 +31,9 @@
/hw/application_fpga/tkey-libs/libcommon.a /hw/application_fpga/tkey-libs/libcommon.a
/hw/application_fpga/tkey-libs/libcrt0.a /hw/application_fpga/tkey-libs/libcrt0.a
/hw/application_fpga/tkey-libs/libmonocypher.a /hw/application_fpga/tkey-libs/libmonocypher.a
/hw/application_fpga/tkey-libs/libblake2s.a
/hw/application_fpga/tools/partition_table/partition_table
/hw/application_fpga/tools/b2s/b2s
synth.json synth.json
synth.txt synth.txt
synth.v synth.v
@ -43,6 +46,7 @@ verilated/
*.o *.o
*.asc *.asc
*.bin *.bin
!/hw/application_fpga/tools/default_partition.bin
*.elf *.elf
*.map *.map
*.tmp *.tmp

View File

@ -122,7 +122,17 @@ official version tag.
Easiest is probably to just remove the tkey-libs directory and then Easiest is probably to just remove the tkey-libs directory and then
git clone the desired tag. Use the entire repo, but remove the .-files git clone the desired tag. Use the entire repo, but remove the .-files
like `.git`, `.github`, et cetera. like `.git`, `.github`, et cetera. Something like:
```
$ rm -rf tkey-libs
$ git clone git@github.com:tillitis/tkey-libs.git
$ cd tkey-libs
$ git checkout fw-3
```
Note that you need to change the optimization flag in the tkey-libs'
Makefile to `-Os`.
## Measured boot ## Measured boot

View File

@ -30,7 +30,7 @@ For full change log [see](https://github.com/tillitis/tillitis-key1/compare/TK1-
- Security Monitor memory access checks are now more complete. - Security Monitor memory access checks are now more complete.
- Add SPI main controller mainly to access flash. - Add SPI main controller mainly to access the flash chip.
- Add system reset API. Device apps can reset the system and restart - Add system reset API. Device apps can reset the system and restart
the firmware. The FPGA is not reset. the firmware. The FPGA is not reset.
@ -76,8 +76,16 @@ For full change log [see](https://github.com/tillitis/tillitis-key1/compare/TK1-
- Add support for the new USB Mode Protocol to communicate with - Add support for the new USB Mode Protocol to communicate with
different endpoints. different endpoints.
- Introduce a system call mechanism and the first syscalls: RESET, - Support a filesystem on flash.
SET\_LED, GET\_VIDPID.
- Add a system call mechanism and system calls: `RESET`, `ALLOC_AREA`,
`DEALLOC_AREA`, `WRITE_DATA`, `READ_DATA`, `PRELOAD_DELETE`,
`PRELOAD_STORE`, `PRELOAD_STORE_FIN`, `PRELOAD_GET_DIGSIG`,
`STATUS`, and `GET_VIDPID`. See [firmware's
README](../hw/application_fpga/fw/README.md) for documentation.
- Harmonize with [tkey-libs](https://github.com/tillitis/tkey-libs).
Import tkey-libs to this repo for convenience.
### CH552 ### CH552

View File

@ -60,10 +60,12 @@ CFLAGS = \
-Wall \ -Wall \
-Wpedantic \ -Wpedantic \
-Wno-language-extension-token \ -Wno-language-extension-token \
-Wextra \
-flto \ -flto \
-g \ -g \
-I $(LIBDIR)/include \ -I $(LIBDIR)/include \
-I $(LIBDIR) -I $(LIBDIR) \
-I $(LIBDIR)/blake2s
AS = clang AS = clang
@ -124,15 +126,19 @@ FIRMWARE_OBJS = \
$(P)/fw/tk1/main.o \ $(P)/fw/tk1/main.o \
$(P)/fw/tk1/start.o \ $(P)/fw/tk1/start.o \
$(P)/fw/tk1/proto.o \ $(P)/fw/tk1/proto.o \
$(P)/fw/tk1/blake2s/blake2s.o \
$(P)/fw/tk1/syscall_enable.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 = \ CHECK_SOURCES = \
$(P)/fw/tk1/main.c \ $(P)/fw/tk1/*.[ch]
$(P)/fw/tk1/proto.c \
$(P)/fw/tk1/blake2s/blake2s.c \
$(P)/fw/tk1/syscall_handler.c
TESTFW_OBJS = \ TESTFW_OBJS = \
$(P)/fw/testfw/main.o \ $(P)/fw/testfw/main.o \
@ -176,7 +182,7 @@ secret:
LDFLAGS = \ LDFLAGS = \
-T $(P)/fw/tk1/firmware.lds \ -T $(P)/fw/tk1/firmware.lds \
-Wl,--cref,-M \ -Wl,--cref,-M \
-L $(LIBDIR) -lcommon -L $(LIBDIR) -lcommon -lblake2s
# Common libraries the firmware and testfw depend on. See # Common libraries the firmware and testfw depend on. See
# https://github.com/tillitis/tkey-libs/ # https://github.com/tillitis/tkey-libs/
@ -196,6 +202,9 @@ simfirmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map $(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map
qemu_firmware.elf: CFLAGS += -DQEMU_DEBUG 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 qemu_firmware.elf: firmware.elf
mv firmware.elf qemu_firmware.elf mv firmware.elf qemu_firmware.elf
@ -208,12 +217,12 @@ compile_commands.json:
.PHONY: check .PHONY: check
check: check:
clang-tidy -header-filter=.* -checks=cert-* $(FIRMWARE_SOURCES) -- $(CFLAGS) clang-tidy -header-filter=.* -checks=cert-* $(CHECK_SOURCES) -- $(CFLAGS)
.PHONY: splint .PHONY: splint
splint: splint:
splint \ splint \
-nolib \ +unixlib \
-predboolint \ -predboolint \
+boolint \ +boolint \
-nullpass \ -nullpass \
@ -224,7 +233,13 @@ splint:
-unreachable \ -unreachable \
-unqualifiedtrans \ -unqualifiedtrans \
-fullinitblock \ -fullinitblock \
$(FIRMWARE_SOURCES) +gnuextensions \
-fixedformalarray \
-mustfreeonly \
-I $(LIBDIR)/include \
-I $(LIBDIR) \
-I $(LIBDIR)/blake2s \
$(CHECK_SOURCES)
testfw.elf: tkey-libs $(TESTFW_OBJS) $(P)/fw/tk1/firmware.lds testfw.elf: tkey-libs $(TESTFW_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(TESTFW_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map $(CC) $(CFLAGS) $(TESTFW_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map

View File

@ -8,13 +8,14 @@ see [the TKey Developer Handbook](https://dev.tillitis.se/).
## Definitions ## Definitions
- Firmware: Software in ROM responsible for loading, measuring, and - Firmware: Software in ROM responsible for loading, measuring,
starting applications. The firmware is included as part of the FPGA starting applications, and providing system calls. The firmware is
bitstream and not replacable on a usual consumer TKey. included as part of the FPGA bitstream and not replacable on a usual
consumer TKey.
- Client: Software running on a computer or a mobile phone the TKey is - Client: Software running on a computer or a mobile phone the TKey is
inserted into. inserted into.
- Device application or app: Software supplied by the client that runs - Device application or app: Software supplied by the client or from
on the TKey. flash that runs on the TKey.
## CPU modes and firmware ## CPU modes and firmware
@ -74,10 +75,10 @@ commands to the `CH552` control endpoint. When the TKey starts only
the `CH552` and the `CDC` endpoints are active. To change this, send a the `CH552` and the `CDC` endpoints are active. To change this, send a
command to `CH552` in this form: command to `CH552` in this form:
| *Name* | *Size* | *Comment* | | *Name* | *Size* | *Comment* |
|---------|--------|-------------------------------| |----------|--------|-------------------------------|
| Command | 1B | Command to the CH552 firmware | | Command | 1B | Command to the CH552 firmware |
| Payload | 1B | Data for the command | | Argument | 1B | Data for the command |
Commands: Commands:
@ -90,7 +91,7 @@ Protocol](https://dev.tillitis.se/protocol/) which is described in the
Developer Handbook. Developer Handbook.
The firmware uses a protocol on top of this framing layer which is The firmware uses a protocol on top of this framing layer which is
used to bootstrap an application. All commands are initiated by the used to load a device application. All commands are initiated by the
client. All commands receive a reply. See [Firmware client. All commands receive a reply. See [Firmware
protocol](http://dev.tillitis.se/protocol/#firmware-protocol) in the protocol](http://dev.tillitis.se/protocol/#firmware-protocol) in the
Dev Handbook for specific details. Dev Handbook for specific details.
@ -105,95 +106,191 @@ Dev Handbook for specific details.
* FW\_RAM is divided into the following areas: * FW\_RAM is divided into the following areas:
- fw stack: 3824 bytes. - fw stack: 3000 bytes.
- resetinfo: 256 bytes. - resetinfo: 256 bytes.
- rest is available for .data and .bss. - .data and .bss: 840 bytes.
## Firmware behaviour ## Firmware behaviour
The purpose of the firmware is to load, measure, and start an The purpose of the firmware is to:
application received from the client over the USB/UART.
1. Load, measure, and start an application received from the client
over the USB/UART or from one of two flash app slots.
2. Provide functionality to run only app's with specific BLAKE2s
digests.
3. Provide system calls to access the filesystem and get other data.
The firmware binary is part of the FPGA bitstream as the initial The firmware binary is part of the FPGA bitstream as the initial
values of the Block RAMs used to construct the `FW_ROM`. The `FW_ROM` values of the Block RAMs used to construct the ROM. The ROM is located
start address is located at `0x0000_0000` in the CPU memory map, which at `0x0000_0000`. This is also the CPU reset vector.
is also the CPU reset vector.
### Reset type
When the TKey is started or resetted it can load an app from different
sources. We call this the reset type. Reset type is located in the
resetinfo part of FW\_RAM. The different reset types loads and start
an app from:
1. Flash slot 0 (default): `FLASH0` with a specific app hash defined
in a constant in firmware.
2. Flash slot 1: `FLASH1`.
3. Flash slot 0 with a specific app hash left from previous app:
`FLASH0_VER`
4. Flash slot 1 with a specific app hash left from previous app:
`FLASH1_VER`.
5. Client: `CLIENT`.
6. Client with a specific app hash left from previous app:
`CLIENT_VER`.
### Firmware state machine ### 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 Change of state occur when we receive specific I/O or a fatal error
occurs. occurs.
```mermaid ```mermaid
stateDiagram-v2 stateDiagram-v2
S1: initial S0: INITIAL
S2: loading S1: WAITCOMMAND
S3: running S2: LOADING
SE: failed S3: LOAD_FLASH
S4: LOAD_FLASH_MGMT
S5: START
SE: FAIL
[*] --> S1 [*] --> S0
S1 --> S1: Commands S0 --> S1
S1 --> S2: LOAD_APP S0 --> S4: Default
S1 --> SE: Error S0 --> S3
S2 --> S2: LOAD_APP_DATA S1 --> S1: Other commands
S2 --> S3: Last block received S1 --> S2: LOAD_APP
S2 --> SE: Error S1 --> SE: Error
S3 --> [*] S2 --> S2: LOAD_APP_DATA
S2 --> S5: Last block received
S2 --> SE: Error
S3 --> S5
S3 --> SE
S4 --> S5
S4 --> SE
SE --> [*]
S5 --> [*]
``` ```
States: States:
- `initial` - At start. Allows the commands `NAME_VERSION`, `GET_UDI`, - *INITIAL*: Transitions to next state through reset type left in
`LOAD_APP`. `FW_RAM`.
- `loading` - Expect application data. Allows only the command - *WAITCOMMAND*: Waiting for initial commands from client. Allows the
`LOAD_APP_DATA`. commands `NAME_VERSION`, `GET_UDI`, `LOAD_APP`.
- `run` - Computes CDI and starts the application. Allows no commands. - *LOADING*: Expecting application data from client. Allows only the
- `fail` - Stops waiting for commands, flashes LED forever. Allows no command `LOAD_APP_DATA` to continue loading the device app.
commands. - *LOAD_FLASH*: Loading an app from flash. Allows no commands.
- *LOAD_FLASH_MGMT*: Loading and verifyiing a device app from flash.
Allows no commands.
- *START*: Computes CDI. Possibly verifies app. Starts the
application. Does not return to firmware. Allows no commands.
- *FAIL* - Halts CPU. Allows no commands.
Commands in state `initial`: Allowed data in state *INITIAL*:
| *reset type* | *next state* |
|--------------|-------------------|
| `FLASH0` | *LOAD_FLASH_MGMT* |
| `FLASH1` | *LOAD_FLASH* |
| `FLASH0_VER` | *LOAD_FLASH* |
| `FLASH1_VER` | *LOAD_FLASH* |
| `CLIENT` | *WAITCOMMAND* |
| `CLIENT_VER` | *WAITCOMMAND* |
I/O in state *LOAD_FLASH*:
| *I/O* | *next state* |
|--------------------|--------------|
| Last app data read | *START* |
I/O in state *LOAD_FLASH_MGMT*:
| *I/O* | *next state* |
|--------------------|--------------|
| Last app data read | *START* |
Commands in state `waitcommand`:
| *command* | *next state* | | *command* | *next state* |
|-----------------------|--------------| |-----------------------|--------------|
| `FW_CMD_NAME_VERSION` | unchanged | | `FW_CMD_NAME_VERSION` | unchanged |
| `FW_CMD_GET_UDI` | unchanged | | `FW_CMD_GET_UDI` | unchanged |
| `FW_CMD_LOAD_APP` | `loading` | | `FW_CMD_LOAD_APP` | *LOADING* |
| | |
Commands in state `loading`: Commands in state `loading`:
| *command* | *next state* | | *command* | *next state* |
|------------------------|----------------------------------| |------------------------|------------------------------------|
| `FW_CMD_LOAD_APP_DATA` | unchanged or `run` on last chunk | | `FW_CMD_LOAD_APP_DATA` | unchanged or *START* on last chunk |
No other states allows commands.
See [Firmware protocol in the Dev See [Firmware protocol in the Dev
Handbook](http://dev.tillitis.se/protocol/#firmware-protocol) for the Handbook](http://dev.tillitis.se/protocol/#firmware-protocol) for the
definition of the specific commands and their responses. definition of the specific commands and their responses.
State changes from "initial" to "loading" when receiving `LOAD_APP`, Plain text explanation of the states:
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".
In "running", the loaded device app is measured, the Compound Device - *INITIAL*: Start here. Check the `FW_RAM` for the `resetinfo` type
Identifier (CDI) is computed, we do some cleanup of firmware data for what to do next.
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.
The device app is now running in application mode. We can, however, For all types which begins with `FLASH_*`, set next state to
return to firmware mode (excepting access to the UDS) by doing system *LOAD_FLASH*, otherwise set next state to *WAITCOMMAND*.
calls. Note that ROM is still readable, but is now hardware protected
from execution, except through the system call mechanism.
### Golden path - *LOAD_FLASH*: Load device app from flash into RAM, app slot taken
from context. Compute a BLAKE2s digest over the entire app.
Transition to *START*.
Firmware loads the application at the start of RAM (`0x4000_0000`). It - *LOAD_FLASH_MGMT*: Load device app from flash into RAM, app slot
use a part of the special FW\_RAM for its own stack. alway 0. Compute a BLAKE2s digest over the entire app. Register the
app as a prospective management app if it later goes through
verification. Transition to *START*.
- *WAITCOMMAND*: Wait for commands from the client. Transition to
*LOADING* on `LOAD_APP` command, which also sets the size of the
number of data blocks to expect.
- *LOADING*: Wait for several `LOAD_APP_DATA` commands until the last
block is received, then transition to *START*.
- *START*: Compute the Compound Device Identifier (CDI). If we have a
registered verification digest, verify that the app we are about to
start is indeed the correct app.
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.
- *FAIL*: Execute an illegal instruction which traps the CPU. Hardware
detects a trapped CPU and blinks the status LED in red until power
loss. No further instructions are executed.
After leaving *START* 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.
If during this whole time any commands are received which are not
allowed in the current state, or any errors occur, we enter the *FAIL*
state.
### Golden path from start to default app
Firmware loads the device application at the start of RAM
(`0x4000_0000`) from either flash or from the client through the UART.
Firmware uses a part of the FW\_RAM for its own stack.
When reset is released, the CPU starts executing the firmware. It When reset is released, the CPU starts executing the firmware. It
begins in `start.S` by clearing all CPU registers, clears all FW\_RAM, begins in `start.S` by clearing all CPU registers, clears all FW\_RAM,
@ -203,60 +300,167 @@ the system calls, but the handler is not yet enabled.
Beginning at `main()` it fills the entire RAM with pseudo random data Beginning at `main()` it fills the entire RAM with pseudo random data
and setting up the RAM address and data hardware scrambling with and setting up the RAM address and data hardware scrambling with
values from the True Random Number Generator (TRNG). It then waits for values from the True Random Number Generator (TRNG).
data coming in through the UART.
Typical expected use scenario: 1. Check the special resetinfo area in FW\_RAM for reset type. Type
zero means default behaviour, load from flash app slot 0, expecting
the app there to have a specific hardcoded BLAKE2s digest.
1. The client sends the `FW_CMD_LOAD_APP` command with the size of 2. Load app data from flash slot 0 into RAM.
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 3. Compute a BLAKE2s digest of the loaded app.
multiple `FW_CMD_LOAD_APP_DATA` commands, together containing the
full application.
3. On receiving`FW_CMD_LOAD_APP_DATA` commands the firmware places 4. Compare the computed digest against the allowed app digest
the data into `0x4000_0000` and upwards. The firmware replies hardcoded in the firmware. If it's not equal, halt CPU.
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 7. [Start the device app](#start-the-device-app).
`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. The Compound Device Identifier ### Start the device app
([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. The start address of the device app, currently `0x4000_0000`, is 1. Check if there is a verification digest left from the previous app
written to `APP_ADDR` and the size of the binary to `APP_SIZE` to in the resetinfo. If it is, compare with the loaded app's already
let the device application know where it is loaded and how large computed digest. Halt CPU if different.
it is, if it wants to relocate in RAM.
7. The firmware now clears the part of the special `FW_RAM` where it 2. Compute the Compound Device Identifier
keeps it stack. ([CDI]((#compound-device-identifier-computation))) by doing a
BLAKE2s using the Unique Device Secret (UDS), the application
digest, and any User Supplied Secret (USS) digest already received.
8. The interrupt handler for system calls is enabled. 3. Write the start address of the device app, currently `0x4000_0000`,
to `APP_ADDR` and the size of the loaded 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.
9. Firmware starts the application by jumping to the contents of 4. Clear the stack part of `FW_RAM`.
`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 5. Enable system call interrupt handler.
allowed in the current state, or any errors occur, we enter the
"failed" state and execute an illegal instruction. An illegal 6. Start the application by jumping to the contents of `APP_ADDR`.
instruction traps the CPU and hardware blinks the status LED red until Hardware automatically switch from firmware mode to application
a power cycle. No further instructions are executed. 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/)).
### Management app, chaining apps and verified boot
Normally, the TKey measures a device app and mixes it together with
the Unique Device Secret in hardware to produce the [Compound Device
Identifier]((#compound-device-identifier-computation)). The CDI can
then be used for creating key material. However, since any key
material created like this will change if the app is changed even the
slightest, this make it hard to upgrade apps and keep the key
material.
This is where a combination of measured boot and verified boot comes
in!
To support verified boot the firmware supports reset types with
verification. This means that the firmware will load an app as usual
either from flash or from the client, but before starting the app it
will verify the new app's computed digest with a verification digest.
The verification digest can either be stored in the firmware itself or
left to it from a previous app, a verified boot loader app.
Such a verified boot loader app:
- Might be loaded from either flash or client.
- Typically includes a security policy, for instance a public key and
code to check a crytographic signature.
- Can be specifically trusted by firmware to be able to do filesystem
management to be able to update an app slot on flash. Add the app's
digest to `allowed_app_digest` in `mgmt_app.c` to allow it to allow
it to use `PRELOAD_DELETE`, `PRELOAD_STORE`, and
`PRELOAD_STORE_FIN`.
It works like this:
- The app reads a digest of the next app in the chain and the
signature over the digest from either the filesystem (syscall
`PRELOAD_GET_DIGSIG`) or sent from the client.
- If the signature provided over the digest is verified against the
public key the app use the system call `RESET` with the reset type
set to `START_FLASH0_VER`, `START_FLASH1_VER`, or `START_CLIENT_VER`
depending on where it wants the next app to start from. It also
sends the now verified app digest to the firmware in the same system
call.
- The key is reset and firmware starts again. It checks:
1. The reset type. Start from client or a slot in the filesystem?
2. The expected digest of the next app.
- Firmware loads the app from the expected source.
- Firmware refuses to start if the loaded app has a different digest.
- If the app was allowed to start it can now use something
deterministic left for it in resetinfo by the verified boot loader
app as a seed for it's key material and no longer use CDI for the
purpose.
We propose that a loader app can derive the seed for the next app by
creating a shared secret, perhaps something as easy as:
```
secret = blake2s(cdi, "name-of-next-app")
```
The loader shares the secret with the next app by putting it in the
part of `resetinfo` that is reserved for inter-app communication.
The next app can now use the secret as a seed for it's own key
material. Depending on the app's behaviour and the numer of keys it
needs it can derive more keys, for instance by having nonces stored on
its flash area and doing:
```
secret1 = blake2s(secret, nonce1)
secret2 = blake2s(secret, nonce2)
...
```
Now it can create many secrets deterministically, as long as there is
some space left on flash for the nonces and all of them can be traced
to the measured identity of the loader app, giving all the features of
the measured boot system.
### App loaded from client
The default is always to start from a verified app in flash slot
0. To be able to load an app from the client you have to send
something to the app to reset the TKey with a reset type of
`START_CLIENT` or `START_CLIENT_VER`.
After reset, firmware will:
1. Wait for data coming in through the UART.
2. 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.
3. On a sucessful response, the client will send multiple
`FW_CMD_LOAD_APP_DATA` commands, together containing the full
application.
4. 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.
5. 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.
6. [Start the device app](#start-the-device-app).
### User-supplied Secret (USS) ### User-supplied Secret (USS)
@ -276,7 +480,7 @@ CDI = blake2s(UDS, blake2s(app), USS)
In an ideal world, software would never be able to read UDS at all and In an ideal world, software would never be able to read UDS at all and
we would have a BLAKE2s function in hardware that would be the only we would have a BLAKE2s function in hardware that would be the only
thing able to read the UDS. Unfortunately, we couldn't fit a BLAKE2s thing able to read the UDS. Unfortunately, we couldn't fit a BLAKE2s
implementation in the FPGA at this time. implementation in the FPGA.
The firmware instead does the CDI computation using the special The firmware instead does the CDI computation using the special
firmware-only `FW_RAM` which is invisible after switching to app mode. firmware-only `FW_RAM` which is invisible after switching to app mode.
@ -294,7 +498,7 @@ Then we continue with the CDI computation by updating with an optional
USS digest and finalizing the hash, storing the resulting digest in USS digest and finalizing the hash, storing the resulting digest in
`CDI`. `CDI`.
### Firmware system calls ### System calls
The firmware provides a system call mechanism through the use of the The firmware provides a system call mechanism through the use of the
PicoRV32 interrupt handler. They are triggered by writing to the PicoRV32 interrupt handler. They are triggered by writing to the
@ -302,10 +506,11 @@ trigger address: 0xe1000000. It's typically done with a function
signature like this: signature like this:
``` ```
int syscall(uint32_t number, uint32_t arg1); int syscall(uint32_t number, uint32_t arg1, uint32_t arg2,
uint32_t arg3);
``` ```
Arguments are system call number and upto 6 generic arguments passed Arguments are system call number and up to 6 generic arguments passed
to the system call handler. The caller should place the system call to the system call handler. The caller should place the system call
number in the a0 register and the arguments in registers a1 to a7 number in the a0 register and the arguments in registers a1 to a7
according to the RISC-V calling convention. The caller is responsible according to the RISC-V calling convention. The caller is responsible
@ -315,16 +520,160 @@ The syscall handler returns execution on the next instruction after
the store instruction to the trigger address. The return value from the store instruction to the trigger address. The return value from
the syscall is now available in x10 (a0). the syscall is now available in x10 (a0).
To add or change syscalls, see the `syscall_handler()` in The syscall numbers are kept in `syscall_num.h`. The syscalls are
`syscall_handler.c`. handled in `syscall_handler()` in `syscall_handler.c`.
Currently supported syscalls: #### `RESET`
| *Name* | *Number* | *Argument* | *Description* | ```
|-------------|----------|------------|----------------------------------| struct reset {
| RESET | 1 | Unused | Reset the TKey | uint32_t type; // Reset type
| SET\_LED | 10 | Colour | Set the colour of the status LED | uint8_t app_digest[32]; // Digest of next app in chain to verify
| GET\_VIDPID | 12 | Unused | Get Vendor and Product ID | uint8_t next_app_data[220]; // Data to leave around for next app
};
struct reset rst;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
```
Resets the TKey. Does not return.
You can pass data to the firmware about the reset type `type` and a
digest that the next app must have. You can also leave some data to
the next app in the chain in `next_app_data`.
The types of the reset are defined in `resetinfo.h`:
| *Name* | *Comment* |
|--------------------|------------------------------------------------|
| `START_FLASH0` | Load next app from flash slot 0 |
| `START_FLASH1` | Load next app from flash slot 1 |
| `START_FLASH0_VER` | Load next app from flash slot 0, but verify it |
| `START_FLASH1_VER` | Load next app from flash slot 1, but verify it |
| `START_CLIENT` | Load next app from client |
| `START_CLIENT_VER` | Load next app from client |
#### `ALLOC_AREA`
```
syscall(TK1_SYSCALL_ALLOC_AREA, 0, 0, 0);
```
Allocate a flash area for the current app. Returns 0 on success.
#### `DEALLOC_AREA`
```
syscall(TK1_SYSCALL_DEALLOC_AREA, 0, 0, 0);
```
Free an already allocated flash area for the current app. Returns 0 on
success.
#### `WRITE_DATA`
```
uint32_t offset = 0;
uint8_t buf[17];
TK1_SYSCALL_WRITE_DATA, offset, (uint32_t)buf, sizeof(buf))
```
Write data in `buf` to the app's flash area at byte `offset` within
the area. Returns 0 on success.
#### `READ_DATA`
```
uint32_t offset = 0;
uint8_t buf[17];
syscall(TK1_SYSCALL_READ_DATA, offset, (uint32_t)buf, sizeof(buf);
```
Read into `buf` at byte `offset` from the app's flash area.
#### `PRELOAD_DELETE`
```
syscall(TK1_SYSCALL_PRELOAD_DELETE, 0, 0, 0);
```
Delete the app in flash slot 1. Returns 0 on success. Only available
for the verified management app.
#### `PRELOAD_STORE`
```
uint8_t *appbinary;
uint32_t offset;
uint32_t size;
syscall(TK1_SYSCALL_PRELOAD_STORE, offset, (uint32_t)appbinary,
size);
```
Store an app, or possible just a block of an app, from the `appbinary`
buffer in flash slot 1 at byte `offset` If you can't find your entire
app in the buffer, call `PRELOAD_STORE` many times as you receive the
binary from the client. Returns 0 on success.
Only available for the verified management app.
#### `PRELOAD_STORE_FIN`
```
uint8_t app_digest[32];
uint8_t app_signature[64];
size_t app_size;
syscall(TK1_SYSCALL_PRELOAD_STORE_FIN, app_size,
(uint32_t)app_digest, (uint32_t)app_signature)
```
Finalize storing of an app where the complete binary size is
`app_size` in flash slot 1. Returns 0 on success. Only available for
the verified management app.
Compute the `app_digest` with BLAKE2s over the entire binary.
Sign `app_digest` with your Ed25519 private key and pass the
resulting signature in `app_signature`.
#### `PRELOAD_GET_DIGSIG`
```
uint8_t app_digest[32];
uint8_t app_signature[64];
syscall(TK1_SYSCALL_PRELOAD_GET_DIGSIG, (uint32_t)app_digest,
(uint32_t)app_signature, 0);
```
Copies the digest and signature of app in flash slot 1 to `app_digest`
and `app_signature`. Returns 0 on success. Only available for the
verified management app.
#### `STATUS`
```
syscall(TK1_SYSCALL_PRELOAD_STATUS, 0, 0, 0);
```
Returns filesystem status. Non-zero when problems have been detected,
so far only that the first copy of the partition table didn't pass
checks.
#### `GET_VIDPID`
```
syscall(TK1_SYSCALL_PRELOAD_STATUS, 0, 0, 0);
```
Returns Vendor and Product ID. Notably the serial number is not
returned, so a device app can't identify what particular TKey it is
running on.
## Developing firmware ## Developing firmware
@ -345,7 +694,10 @@ you might need to `make clean` before building, if you have already
built before. built before.
If you want debug prints to show up on the special TKey HID debug If you want debug prints to show up on the special TKey HID debug
endpoint instead, define `-DTKEY_DEBUG`. endpoint instead, define `-DTKEY_DEBUG`. This might mean you can't fit
the firmware in the ROM space available, however. You will get a
warning if it doesn't fit. In that case, just use explicit
`puts(IO_DEBUG, ...)` or `puts(IO_CDC, ...)` and so on.
Note that if you use `TKEY_DEBUG` you *must* have something listening Note that if you use `TKEY_DEBUG` you *must* have something listening
on the corresponding HID device. It's usually the last HID device on the corresponding HID device. It's usually the last HID device
@ -362,6 +714,45 @@ Most of the utility functions that the firmware use lives in
but we have vendored it in for firmware use in `../tkey-libs`. See top but we have vendored it in for firmware use in `../tkey-libs`. See top
README for how to update. README for how to update.
### Preparing the filesystem
The TKey supports a simple filesystem. This filesystem must be
initiated before starting for the first time. You need a [TKey
Programmer Board](https://shop.tillitis.se/products/tkey-dev-kit) for
this part.
1. Choose your pre-loaded app. You /must/ have a pre-loaded app, for
example `testloadapp`. Build it with the OCI image we use. The
binary needs to produce the BLAKE2s digest in `allowed_app_digest`
`tk1/mgmt_app.c`.
2. Write the filesystem to flash:
```
$ cd ../tools
$ ./load_preloaded_app.sh 0 ../fw/testloadapp/testloadapp.bin
```
If you want to use a different pre-loaded app you have to
1. Check the BLAKE2s digest of the app. You can use `tools/b2s` to
compute it.
2. Update the `allowed_app_digest` in `tk1/mgmt_app.c`.
3. Create a new `default_partition.bin` using the
`tools/partition_table`, typically:
```
$ partition_table -app0 path/to/your/app.bin -o default_partition.bin
```
4. Flash the filesystem image:
```
$ ./load_preloaded_app.sh 0 path/to/your/app.bin
```
### Test firmware ### Test firmware
The test firmware is in `testfw`. It's currently a bit of a hack and The test firmware is in `testfw`. It's currently a bit of a hack and
@ -373,5 +764,23 @@ terminal program to the serial port device, even if it's running in
qemu. It waits for you to type a character before starting the tests. qemu. It waits for you to type a character before starting the tests.
It needs to be compiled with `-Os` instead of `-O2` in `CFLAGS` in the It needs to be compiled with `-Os` instead of `-O2` in `CFLAGS` in the
ordinary `application_fpga/Makefile` to be able to fit in the 6 kByte ordinary `application_fpga/Makefile` to be able to fit in ROM.
ROM.
### Test apps
There are a couple of test apps. All of them are controlled through
the USB CDC, typically by running picocom or similar terminal program,
like:
```
$ picocom /dev/ttyACM1
```
or similar.
- `fw/testapp`: Runs through a couple of tests that are now impossible
to do in the `testfw`.
- `fw/reset_test`: Interactively test different reset scenarios.
- `fw/testloadapp`: Interactively test management app things like
installing an app (hardcoded for a small happy blinking app, see
`blink.h` for the entire binary!) and to test verified boot.

View File

@ -0,0 +1,75 @@
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 = *.[ch]
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,161 @@
/*
* 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) {
puts(IO_CDC, "reset_test: Waiting for command\r\n");
memset(cmdbuf, 0, BUFSIZE);
// Wait for data
if (readselect(IO_CDC, &endpoint, &available) < 0) {
assert(1 == 2);
}
if (read(IO_CDC, 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':
// Reset into default state
rst.type = START_DEFAULT;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
break;
case '2':
// Reset and load app from client
rst.type = START_CLIENT;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
break;
case '3':
// Reset and load app from second flash slot
rst.type = START_FLASH1;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
break;
case '4': {
// Reset and load app from client with verification
// using an invalid digest.
//
// Should cause firmware to refuse to start app.
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': {
// Reset and load app from client with verification
// using a digest matching the example app (blue.bin)
// from tkey-libs
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': {
// Reset and load app from second flash slot with
// verification using an invalid digest.
//
// Should cause firmware to refuse to start app.
uint8_t string[] = "0123456789abcdef0123456789abcdef012"
"3456789abcdef0123456789abcdef";
rst.type = START_FLASH1_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': {
// Reset and load app from second flash slot with
// verification using a digest matching the example app
// (blue.bin) from tkey-libs
//
// Blue.bin has to be present on flash in the second
// preloaded app slot (slot 1).
uint8_t tkeylibs_example_app_digest[] =
"96bb4c90603dbbbe09b9a1d7259b5e9e61bedd89a897105c30"
"c9d4bf66a98d97";
rst.type = START_FLASH1_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

@ -50,9 +50,7 @@ all: testapp.bin
tkey-libs: tkey-libs:
make -C $(LIBDIR) make -C $(LIBDIR)
TESTAPP_FMTFILES = \ TESTAPP_FMTFILES = *.[ch]
$(P)/main.c \
$(P)/syscall.h
TESTAPP_OBJS = \ TESTAPP_OBJS = \
$(P)/main.o \ $(P)/main.o \

View File

@ -11,6 +11,7 @@
#include <tkey/tk1_mem.h> #include <tkey/tk1_mem.h>
#include "../tk1/proto.h" #include "../tk1/proto.h"
#include "../tk1/resetinfo.h"
#include "../tk1/syscall_num.h" #include "../tk1/syscall_num.h"
#include "syscall.h" #include "syscall.h"
@ -121,13 +122,49 @@ int main(void)
} }
// But a syscall to get parts of UDI should be able to run // 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) { if (vidpid != 0x00010203) {
failmsg("Expected VID/PID to be 0x00010203"); failmsg("Expected VID/PID to be 0x00010203");
anyfailed = 1; 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_local[CDI_WORDS];
uint32_t cdi_local2[CDI_WORDS]; uint32_t cdi_local2[CDI_WORDS];
wordcpy_s(cdi_local, CDI_WORDS, (void *)cdi, CDI_WORDS); wordcpy_s(cdi_local, CDI_WORDS, (void *)cdi, CDI_WORDS);
@ -223,7 +260,10 @@ int main(void)
} }
if (in == '+') { if (in == '+') {
syscall(TK1_SYSCALL_RESET, 0); struct reset rst;
memset(&rst, 0, sizeof(rst));
rst.type = START_DEFAULT;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
} }
write(IO_CDC, &in, 1); write(IO_CDC, &in, 1);

View File

@ -6,6 +6,6 @@
#ifndef TKEY_APP_SYSCALL_H #ifndef TKEY_APP_SYSCALL_H
#define 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 #endif

View File

@ -1,5 +1,5 @@
# Uses ../.clang-format # Uses ../.clang-format
FMTFILES=main.c FMTFILES=*.[ch]
.PHONY: fmt .PHONY: fmt
fmt: fmt:
clang-format --dry-run --ferror-limit=0 $(FMTFILES) clang-format --dry-run --ferror-limit=0 $(FMTFILES)

View File

@ -0,0 +1,73 @@
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 = *.[ch]
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,28 @@
#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,218 @@
#include <blake2s/blake2s.h>
#include <monocypher/monocypher-ed25519.h>
#include <stdint.h>
#include <tkey/debug.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.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);
int ret = 0;
ret = syscall(TK1_SYSCALL_PRELOAD_DELETE, 0, 0, 0);
if (ret != 0) {
puts(IO_CDC, "couldn't delete preloaded app. error: 0x");
putinthex(IO_CDC, ret);
puts(IO_CDC, "\r\n");
return -1;
}
ret = syscall(TK1_SYSCALL_PRELOAD_STORE, 0, (uint32_t)blink,
sizeof(blink));
if (ret != 0) {
puts(IO_CDC, "couldn't store app, error: 0x");
putinthex(IO_CDC, ret);
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");
ret = syscall(TK1_SYSCALL_PRELOAD_STORE_FIN, app_size,
(uint32_t)app_digest, (uint32_t)app_signature);
if (ret != 0) {
puts(IO_CDC, "couldn't finalize storing app, error:");
putinthex(IO_CDC, ret);
puts(IO_CDC, "\r\n");
return -1;
}
return 0;
}
int verify(uint8_t pubkey[32])
{
uint8_t app_digest[32];
uint8_t app_signature[64];
int ret = 0;
// pubkey we already have
// read signature
// read digest
ret = syscall(TK1_SYSCALL_PRELOAD_GET_DIGSIG, (uint32_t)app_digest,
(uint32_t)app_signature, 0);
if (ret != 0) {
puts(IO_CDC, "couldn't get digsig, error:");
putinthex(IO_CDC, ret);
puts(IO_CDC, "\r\n");
return -1;
}
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) {
puts(IO_CDC, "signature check failed\r\n");
return -1;
}
puts(IO_CDC, "Resetting into pre loaded app (slot 2)...\r\n");
// syscall reset flash1_ver with app_digest
struct reset rst;
rst.type = START_FLASH1_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;
}
void reset_from_client(void)
{
struct reset rst = {0};
rst.type = START_CLIENT;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
}
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, 2 == load app from client\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;
case '2':
reset_from_client();
break;
default:
break;
}
}
}

View File

@ -0,0 +1,76 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stdint.h>
#include <tkey/assert.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)
{
assert(nonce != NULL);
assert(auth_digest != NULL);
blake2s_ctx ctx = {0};
// Generate a 16 byte authentication digest
int blake2err = blake2s_init(&ctx, 16, NULL, 0);
assert(blake2err == 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)
{
assert(nonce != NULL);
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)
{
assert(auth_table != NULL);
uint8_t nonce[16] = {0};
uint8_t auth_digest[16] = {0};
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)
{
assert(auth_table != NULL);
uint8_t auth_digest[16] = {0};
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

@ -1,11 +0,0 @@
# blake2s
A Blake2s implementation taken from Joachim Strömbergson's
https://github.com/secworks/blake2s
Specifically from
https://github.com/secworks/blake2s/tree/master/src/model
Minor local changes for build purposes.

View File

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

View File

@ -0,0 +1,244 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tkey/assert.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 bool flash_is_busy(void);
static void flash_wait_busy(void);
static void flash_write_enable(void);
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);
}
static bool flash_is_busy(void)
{
uint8_t tx_buf = READ_STATUS_REG_1;
uint8_t rx_buf = {0x00};
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, &rx_buf,
sizeof(rx_buf)) == 0);
if (rx_buf & (1 << STATUS_REG_BUSY_BIT)) {
return true;
}
return false;
}
// Blocking until !busy
static void flash_wait_busy(void)
{
while (flash_is_busy()) {
delay(10);
}
}
static void flash_write_enable(void)
{
uint8_t tx_buf = WRITE_ENABLE;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
}
void flash_write_disable(void)
{
uint8_t tx_buf = WRITE_DISABLE;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 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();
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 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();
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 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();
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
flash_wait_busy();
}
void flash_release_powerdown(void)
{
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = RELEASE_POWER_DOWN;
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
}
void flash_powerdown(void)
{
uint8_t tx_buf = POWER_DOWN;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
}
void flash_read_manufacturer_device_id(uint8_t *device_id)
{
assert(device_id != NULL);
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = READ_MANUFACTURER_ID;
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, device_id, 2) ==
0);
}
void flash_read_jedec_id(uint8_t *jedec_id)
{
assert(jedec_id != NULL);
uint8_t tx_buf = READ_JEDEC_ID;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, jedec_id, 3) ==
0);
}
void flash_read_unique_id(uint8_t *unique_id)
{
assert(unique_id != NULL);
uint8_t tx_buf[5] = {0x00};
tx_buf[0] = READ_UNIQUE_ID;
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, unique_id, 8) ==
0);
}
void flash_read_status(uint8_t *status_reg)
{
assert(status_reg != NULL);
uint8_t tx_buf = READ_STATUS_REG_1;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg, 1) ==
0);
tx_buf = READ_STATUS_REG_2;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg + 1,
1) == 0);
}
int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size)
{
if (dest_buf == NULL) {
return -1;
}
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 (data == NULL) {
return -1;
}
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,55 @@
// 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
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

@ -3,16 +3,21 @@
* SPDX-License-Identifier: GPL-2.0-only * SPDX-License-Identifier: GPL-2.0-only
*/ */
#include <blake2s/blake2s.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <tkey/assert.h> #include <tkey/assert.h>
#include <tkey/debug.h> #include <tkey/debug.h>
#include <tkey/led.h>
#include <tkey/lib.h> #include <tkey/lib.h>
#include <tkey/tk1_mem.h> #include <tkey/tk1_mem.h>
#include "blake2s/blake2s.h" #include "mgmt_app.h"
#include "partition_table.h"
#include "preload_app.h"
#include "proto.h" #include "proto.h"
#include "resetinfo.h"
#include "state.h" #include "state.h"
#include "syscall_enable.h" #include "syscall_enable.h"
@ -33,8 +38,11 @@ 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 *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_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 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 // clang-format on
struct partition_table_storage part_table_storage;
// Context for the loading of a TKey program // Context for the loading of a TKey program
struct context { struct context {
uint32_t left; // Bytes left to receive uint32_t left; // Bytes left to receive
@ -42,6 +50,9 @@ struct context {
uint8_t *loadaddr; // Where we are currently loading a TKey program uint8_t *loadaddr; // Where we are currently loading a TKey program
bool use_uss; // Use USS? bool use_uss; // Use USS?
uint8_t uss[32]; // User Supplied Secret, if any uint8_t uss[32]; // User Supplied Secret, if any
uint8_t flash_slot; // App is loaded from flash slot number
/*@null@*/ volatile uint8_t
*ver_digest; // Verify loaded app against this digest
}; };
static void print_hw_version(void); static void print_hw_version(void);
@ -56,11 +67,14 @@ static enum state initial_commands(const struct frame_header *hdr,
static enum state loading_commands(const struct frame_header *hdr, static enum state loading_commands(const struct frame_header *hdr,
const uint8_t *cmd, enum state state, const uint8_t *cmd, enum state state,
struct context *ctx); struct context *ctx);
static void run(const struct context *ctx);
#if !defined(SIMULATION) #if !defined(SIMULATION)
static uint32_t xorwow(uint32_t state, uint32_t acc); static uint32_t xorwow(uint32_t state, uint32_t acc);
#endif #endif
static void scramble_ram(void); 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) static void print_hw_version(void)
{ {
@ -80,6 +94,7 @@ static void print_digest(uint8_t *md)
for (int j = 0; j < 4; j++) { for (int j = 0; j < 4; j++) {
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
debug_puthex(md[i + 8 * j]); debug_puthex(md[i + 8 * j]);
(void)md;
} }
debug_lf(); debug_lf();
} }
@ -144,6 +159,7 @@ static void compute_cdi(const uint8_t *digest, const uint8_t use_uss,
static void copy_name(uint8_t *buf, const size_t bufsiz, const uint32_t word) static void copy_name(uint8_t *buf, const size_t bufsiz, const uint32_t word)
{ {
assert(bufsiz >= 4); assert(bufsiz >= 4);
assert(buf != NULL);
buf[0] = word >> 24; buf[0] = word >> 24;
buf[1] = word >> 16; buf[1] = word >> 16;
@ -279,7 +295,6 @@ static enum state loading_commands(const struct frame_header *hdr,
ctx->left -= nbytes; ctx->left -= nbytes;
if (ctx->left == 0) { if (ctx->left == 0) {
blake2s_ctx b2s_ctx = {0};
int blake2err = 0; int blake2err = 0;
debug_puts("Fully loaded "); debug_puts("Fully loaded ");
@ -288,9 +303,7 @@ static enum state loading_commands(const struct frame_header *hdr,
// Compute Blake2S digest of the app, // Compute Blake2S digest of the app,
// storing it for FW_STATE_RUN // storing it for FW_STATE_RUN
blake2err = blake2s(&ctx->digest, 32, NULL, 0, blake2err = compute_app_digest(ctx->digest);
(const void *)TK1_RAM_BASE,
*app_size, &b2s_ctx);
assert(blake2err == 0); assert(blake2err == 0);
print_digest(ctx->digest); print_digest(ctx->digest);
@ -300,7 +313,7 @@ static enum state loading_commands(const struct frame_header *hdr,
memcpy_s(&rsp[1], CMDSIZE - 1, &ctx->digest, 32); memcpy_s(&rsp[1], CMDSIZE - 1, &ctx->digest, 32);
fwreply(*hdr, FW_RSP_LOAD_APP_DATA_READY, rsp); fwreply(*hdr, FW_RSP_LOAD_APP_DATA_READY, rsp);
state = FW_STATE_RUN; state = FW_STATE_START;
break; break;
} }
@ -320,13 +333,11 @@ static enum state loading_commands(const struct frame_header *hdr,
return state; 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; *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("Flipping to app mode!\n");
debug_puts("Jumping to "); debug_puts("Jumping to ");
debug_putinthex(*app_addr); debug_putinthex(*app_addr);
@ -365,6 +376,29 @@ static void run(const struct context *ctx)
__builtin_unreachable(); __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;
}
#if !defined(SIMULATION) #if !defined(SIMULATION)
static uint32_t xorwow(uint32_t state, uint32_t acc) static uint32_t xorwow(uint32_t state, uint32_t acc)
{ {
@ -399,6 +433,62 @@ static void scramble_ram(void)
*ram_data_rand = rnd_word(); *ram_data_rand = rnd_word();
} }
/* Computes the blake2s digest of the app loaded into RAM */
static int compute_app_digest(uint8_t *digest)
{
return blake2s(digest, 32, NULL, 0, (const void *)TK1_RAM_BASE,
*app_size);
}
static enum state start_where(struct context *ctx)
{
assert(ctx != NULL);
// Where do we start? Read resetinfo 'startfrom'
switch (resetinfo->type) {
case START_DEFAULT:
// fallthrough
case START_FLASH0:
ctx->flash_slot = 0;
ctx->ver_digest = mgmt_app_allowed_digest();
return FW_STATE_LOAD_FLASH_MGMT;
case START_FLASH1:
ctx->flash_slot = 1;
ctx->ver_digest = NULL;
return FW_STATE_LOAD_FLASH;
case START_FLASH0_VER:
ctx->flash_slot = 0;
ctx->ver_digest = resetinfo->app_digest;
return FW_STATE_LOAD_FLASH;
case START_FLASH1_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) int main(void)
{ {
struct context ctx = {0}; struct context ctx = {0};
@ -406,6 +496,8 @@ int main(void)
uint8_t cmd[CMDSIZE] = {0}; uint8_t cmd[CMDSIZE] = {0};
enum state state = FW_STATE_INITIAL; enum state state = FW_STATE_INITIAL;
led_set(LED_BLUE);
print_hw_version(); print_hw_version();
/*@-mustfreeonly@*/ /*@-mustfreeonly@*/
@ -418,6 +510,11 @@ int main(void)
scramble_ram(); scramble_ram();
if (part_table_read(&part_table_storage) != 0) {
// Couldn't read or create partition table
assert(1 == 2);
}
#if defined(SIMULATION) #if defined(SIMULATION)
run(&ctx); run(&ctx);
#endif #endif
@ -425,6 +522,10 @@ int main(void)
for (;;) { for (;;) {
switch (state) { switch (state) {
case FW_STATE_INITIAL: case FW_STATE_INITIAL:
state = start_where(&ctx);
break;
case FW_STATE_WAITCOMMAND:
if (readcommand(&hdr, cmd, state) == -1) { if (readcommand(&hdr, cmd, state) == -1) {
state = FW_STATE_FAIL; state = FW_STATE_FAIL;
break; break;
@ -445,9 +546,51 @@ int main(void)
state = loading_commands(&hdr, cmd, state, &ctx); state = loading_commands(&hdr, cmd, state, &ctx);
break; break;
case FW_STATE_RUN: case FW_STATE_LOAD_FLASH:
run(&ctx); if (load_flash_app(&part_table_storage.table,
break; // This is never reached! ctx.digest, ctx.flash_slot) < 0) {
debug_puts("Couldn't load app from flash\n");
state = FW_STATE_FAIL;
break;
}
state = FW_STATE_START;
break;
case FW_STATE_LOAD_FLASH_MGMT:
if (load_flash_app(&part_table_storage.table,
ctx.digest, ctx.flash_slot) < 0) {
debug_puts("Couldn't load app from flash\n");
state = FW_STATE_FAIL;
break;
}
if (mgmt_app_init(ctx.digest) != 0) {
state = FW_STATE_FAIL;
}
state = FW_STATE_START;
break;
case FW_STATE_START:
// CDI = hash(uds, hash(app), uss)
compute_cdi(ctx.digest, ctx.use_uss, ctx.uss);
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;
}
}
(void)memset((void *)resetinfo->app_digest, 0,
sizeof(resetinfo->app_digest));
jump_to_app();
break; // Not reached
case FW_STATE_FAIL: case FW_STATE_FAIL:
// fallthrough // fallthrough
@ -462,5 +605,6 @@ int main(void)
/*@ -compdestroy @*/ /*@ -compdestroy @*/
/* We don't care about memory leaks here. */ /* We don't care about memory leaks here. */
return (int)0xcafebabe; return (int)0xcafebabe;
} }

View File

@ -0,0 +1,43 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stdint.h>
#include <tkey/io.h>
#include <tkey/lib.h>
#include "mgmt_app.h"
// Lock down what app can start from flash slot 0.
//
// To update this, compute the BLAKE2s digest of the app.bin
static const uint8_t allowed_app_digest[32] = {
0xb6, 0x86, 0x1b, 0x26, 0xef, 0x69, 0x77, 0x12, 0xed, 0x6c, 0xca,
0xe8, 0x35, 0xb4, 0x5c, 0x01, 0x07, 0x71, 0xab, 0xce, 0x3f, 0x30,
0x79, 0xda, 0xe6, 0xf9, 0xee, 0x4b, 0xe2, 0x06, 0x95, 0x33,
};
static uint8_t current_app_digest[32];
int mgmt_app_init(uint8_t app_digest[32])
{
if (app_digest == NULL) {
return -1;
}
memcpy_s(current_app_digest, sizeof(current_app_digest), app_digest,
32);
return 0;
}
/* Authenticate an management app */
bool mgmt_app_authenticate(void)
{
return memeq(current_app_digest, allowed_app_digest, 32) != 0;
}
uint8_t *mgmt_app_allowed_digest(void)
{
return (uint8_t *)allowed_app_digest;
}

View File

@ -0,0 +1,14 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef MGMT_APP_H
#define MGMT_APP_H
#include <stdbool.h>
#include <stdint.h>
int mgmt_app_init(uint8_t app_digest[32]);
bool mgmt_app_authenticate(void);
uint8_t *mgmt_app_allowed_digest(void);
#endif

View File

@ -0,0 +1,106 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/lib.h>
#include "blake2s/blake2s.h"
#include "flash.h"
#include "partition_table.h"
#include "proto.h"
static enum part_status part_status;
enum part_status part_get_status(void)
{
return part_status;
}
static void part_digest(struct partition_table *part_table, uint8_t *out_digest,
size_t out_len);
static void part_digest(struct partition_table *part_table, uint8_t *out_digest,
size_t out_len)
{
int blake2err = 0;
uint8_t key[16] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
assert(part_table != NULL);
assert(out_digest != NULL);
blake2err = blake2s(out_digest, out_len, key, sizeof(key), part_table,
sizeof(struct partition_table));
assert(blake2err == 0);
}
// part_table_read reads and verifies the partition table storage,
// first trying slot 0, then slot 1 if slot 0 does not verify.
//
// It stores the partition table in storage.
//
// Returns negative values on errors.
int part_table_read(struct partition_table_storage *storage)
{
uint32_t offset[2] = {
ADDR_PARTITION_TABLE_0,
ADDR_PARTITION_TABLE_1,
};
uint8_t check_digest[PART_DIGEST_SIZE] = {0};
if (storage == NULL) {
return -1;
}
flash_release_powerdown();
(void)memset(storage, 0x00, sizeof(*storage));
for (int i = 0; i < 2; i++) {
if (flash_read_data(offset[i], (uint8_t *)storage,
sizeof(*storage)) != 0) {
return -1;
}
part_digest(&storage->table, check_digest,
sizeof(check_digest));
if (memeq(check_digest, storage->check_digest,
sizeof(check_digest))) {
if (i == 1) {
part_status = PART_SLOT0_INVALID;
}
return 0;
}
}
return -1;
}
int part_table_write(struct partition_table_storage *storage)
{
uint32_t offset[2] = {
ADDR_PARTITION_TABLE_0,
ADDR_PARTITION_TABLE_1,
};
if (storage == NULL) {
return -1;
}
part_digest(&storage->table, storage->check_digest,
sizeof(storage->check_digest));
for (int i = 0; i < 2; i++) {
flash_sector_erase(offset[i]);
if (flash_write_data(offset[i], (uint8_t *)storage,
sizeof(*storage)) != 0) {
return -1;
}
}
return 0;
}

View File

@ -0,0 +1,109 @@
// 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 */
/* ---- ---- ---- */
/* Partition2 64KiB 0xf0000 */
/* 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_0 (ADDR_BITSTREAM + SIZE_BITSTREAM)
#define ADDR_PARTITION_TABLE_1 0xf0000
#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_0 + SIZE_PARTITION_TABLE)
#define SIZE_PRE_LOADED_APP 0x20000UL // 128KiB
#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 PART_DIGEST_SIZE 16
enum part_status {
PART_SLOT0_INVALID = 1,
};
/* Partition Table */
/*- Table header */
/* - 1 bytes Version */
/**/
/*- Pre-loaded device app 1 */
/* - 4 bytes length. */
/* - 32 bytes digest. */
/* - 64 bytes signature. */
/**/
/*- Pre-loaded device app 2 */
/* - 4 bytes length. */
/* - 32 bytes digest. */
/* - 64 bytes signature. */
/**/
/*- Device app storage area */
/* - 1 byte status. */
/* - 16 bytes random nonce. */
/* - 16 bytes authentication tag. */
struct auth_metadata {
uint8_t nonce[16];
uint8_t authentication_digest[16];
} __attribute__((packed));
struct pre_loaded_app_metadata {
uint32_t size;
uint8_t digest[32];
uint8_t signature[64];
} __attribute__((packed));
struct app_storage_area {
uint8_t status;
struct auth_metadata auth;
} __attribute__((packed));
struct table_header {
uint8_t version;
} __attribute__((packed));
struct partition_table {
struct table_header header;
struct pre_loaded_app_metadata pre_app_data[N_PRELOADED_APP];
struct app_storage_area app_storage[N_STORAGE_AREA];
} __attribute__((packed));
struct partition_table_storage {
struct partition_table table;
uint8_t check_digest[PART_DIGEST_SIZE];
} __attribute__((packed));
enum part_status part_get_status(void);
int part_table_read(struct partition_table_storage *storage);
int part_table_write(struct partition_table_storage *storage);
#endif

View File

@ -0,0 +1,198 @@
// 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;
}
/* Loads a preloaded app from flash to app RAM */
int preload_load(struct partition_table *part_table, uint8_t from_slot)
{
if (part_table == NULL) {
return -5;
}
if (from_slot >= N_PRELOADED_APP) {
return -4;
}
/*Check for a valid app in flash */
if (part_table->pre_app_data[from_slot].size == 0) {
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)
{
if (part_table == NULL || data == NULL) {
return -5;
}
if (to_slot >= N_PRELOADED_APP) {
return -4;
}
/* Check if we are allowed to store */
if (!mgmt_app_authenticate()) {
return -3;
}
/* Check for a valid app in flash, bale out if it already exists */
if (part_table->pre_app_data[to_slot].size != 0) {
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_storage *part_table_storage,
size_t app_size, uint8_t app_digest[32],
uint8_t app_signature[64], uint8_t to_slot)
{
struct partition_table *part_table = &part_table_storage->table;
if (part_table == NULL || app_digest == NULL || app_signature == NULL) {
return -5;
}
if (to_slot >= N_PRELOADED_APP) {
return -4;
}
/* Check if we are allowed to store */
if (!mgmt_app_authenticate()) {
return -3;
}
/* Check for a valid app in flash, bale out if it already exists */
if (part_table->pre_app_data[to_slot].size != 0) {
return -1;
}
if (app_size == 0 || app_size > SIZE_PRE_LOADED_APP) {
return -2;
}
part_table->pre_app_data[to_slot].size = app_size;
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();
if (part_table_write(part_table_storage) != 0) {
return -6;
}
return 0;
}
int preload_delete(struct partition_table_storage *part_table_storage,
uint8_t slot)
{
struct partition_table *part_table = &part_table_storage->table;
if (part_table_storage == NULL) {
return -5;
}
if (slot >= N_PRELOADED_APP) {
return -4;
}
/* Check if we are allowed to deleted */
if (!mgmt_app_authenticate()) {
return -3;
}
/*Check for a valid app in flash */
if (part_table->pre_app_data[slot].size == 0) {
// Nothing to do.
return 0;
}
part_table->pre_app_data[slot].size = 0;
(void)memset(part_table->pre_app_data[slot].digest, 0,
sizeof(part_table->pre_app_data[slot].digest));
(void)memset(part_table->pre_app_data[slot].signature, 0,
sizeof(part_table->pre_app_data[slot].signature));
if (part_table_write(part_table_storage) != 0) {
return -6;
}
/* 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 (part_table == NULL || app_digest == NULL || app_signature == NULL) {
return -5;
}
if (slot >= N_PRELOADED_APP) {
return -4;
}
/* Check if we are allowed to read */
if (!mgmt_app_authenticate()) {
return -3;
}
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,24 @@
// 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>
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_storage *part_table_storage,
size_t app_size, uint8_t app_digest[32],
uint8_t app_signature[64], uint8_t to_slot);
int preload_delete(struct partition_table_storage *part_table_storage,
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,28 @@
// 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_FLASH0 = 1,
START_FLASH1 = 2,
START_FLASH0_VER = 3,
START_FLASH1_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,100 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include "spi.h"
#include <tkey/assert.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
static int spi_ready(void);
static void spi_enable(void);
static void spi_disable(void);
static void spi_write(uint8_t *cmd, size_t size);
static void spi_read(uint8_t *buf, size_t size);
// 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.
static 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)
{
assert(cmd != NULL);
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)
{
assert(buf != NULL);
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,13 @@
// 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_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> #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 #include "picorv32/custom_ops.S" // PicoRV32 custom instructions
#endif
#define illegal_insn() .word 0 #define illegal_insn() .word 0
// Variables in bss // Variables in bss

View File

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

View File

@ -0,0 +1,278 @@
// 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)
{
if (part_table == NULL) {
return -4;
}
for (uint8_t i = 0; i < N_STORAGE_AREA; i++) {
if (part_table->app_storage[i].status == 0x00) {
return i;
}
}
return -1;
}
static int index_to_address(int index, uint32_t *address)
{
if (address == NULL) {
return -4;
}
if ((index < 0) || (index >= N_STORAGE_AREA)) {
return -1;
}
*address = ADDR_STORAGE_AREA + index * SIZE_STORAGE_AREA;
return 0;
}
/* 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)
{
if (part_table == NULL) {
return -4;
}
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_storage *part_table_storage)
{
if (part_table_storage == NULL) {
return -4;
}
struct partition_table *part_table = &part_table_storage->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;
}
uint32_t start_address = 0;
int err = index_to_address(index, &start_address);
if (err) {
return -3;
}
/* Allocate the empty index found */
/* Erase area first */
/* Assumes the area is 64 KiB block aligned */
flash_block_64_erase(start_address); // Erase first 64 KB block
flash_block_64_erase(start_address +
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);
if (part_table_write(part_table_storage) != 0) {
return -5;
}
return 0;
}
/* Dealloacate a previously allocated storage area. Returns zero on success, and
* non-zero on errors. */
int storage_deallocate_area(struct partition_table_storage *part_table_storage)
{
if (part_table_storage == NULL) {
return -4;
}
struct partition_table *part_table = &part_table_storage->table;
int index = storage_get_area(part_table);
if (index == -1) {
/* No area to deallocate */
return -1;
}
uint32_t start_address = 0;
int err = index_to_address(index, &start_address);
if (err) {
return -3;
}
/* Erase area first */
/* Assumes the area is 64 KiB block aligned */
flash_block_64_erase(start_address); // Erase first 64 KB block
flash_block_64_erase(start_address +
0x10000); // Erase second 64 KB block
/* Clear partition table lastly */
part_table->app_storage[index].status = 0;
(void)memset(part_table->app_storage[index].auth.nonce, 0x00,
sizeof(part_table->app_storage[index].auth.nonce));
(void)memset(
part_table->app_storage[index].auth.authentication_digest, 0x00,
sizeof(part_table->app_storage[index].auth.authentication_digest));
if (part_table_write(part_table_storage) != 0) {
return -5;
}
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)
{
if (part_table == NULL) {
return -4;
}
int index = storage_get_area(part_table);
if (index == -1) {
/* No allocated area */
return -1;
}
uint32_t start_address = 0;
int err = index_to_address(index, &start_address);
if (err) {
return -3;
}
/* Cannot only erase entire sectors */
if (offset % 4096 != 0) {
return -2;
}
/* Cannot erase less than one sector */
if (size < 4096 || size > SIZE_STORAGE_AREA || size % 4096 != 0) {
return -2;
}
if ((offset + size) >= SIZE_STORAGE_AREA) {
return -2;
}
uint32_t address = start_address + 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)
{
if (part_table == NULL || data == NULL) {
return -4;
}
int index = storage_get_area(part_table);
if (index == -1) {
/* No allocated area */
return -1;
}
uint32_t start_address = 0;
int err = index_to_address(index, &start_address);
if (err) {
return -3;
}
if ((offset + size) > SIZE_STORAGE_AREA || size > 4096) {
/* Writing outside of area */
return -2;
}
uint32_t address = start_address + 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)
{
if (part_table == NULL || data == NULL) {
return -4;
}
int index = storage_get_area(part_table);
if (index == -1) {
/* No allocated area */
return -1;
}
uint32_t start_address = 0;
int err = index_to_address(index, &start_address);
if (err) {
return -3;
}
if ((offset + size) > SIZE_STORAGE_AREA) {
/* Reading outside of area */
return -2;
}
uint32_t address = start_address + 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_deallocate_area(struct partition_table_storage *part_table_storage);
int storage_allocate_area(struct partition_table_storage *part_table_storage);
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" #include "../tk1/picorv32/custom_ops.S"
#endif
.section ".text" .section ".text"
.globl syscall_enable .globl syscall_enable

View File

@ -5,29 +5,112 @@
#include <stdint.h> #include <stdint.h>
#include <tkey/assert.h> #include <tkey/assert.h>
#include <tkey/led.h> #include <tkey/debug.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "partition_table.h"
#include "preload_app.h"
#include "storage.h"
#include "../tk1/resetinfo.h"
#include "../tk1/syscall_num.h" #include "../tk1/syscall_num.h"
// clang-format off // clang-format off
static volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_RESET; 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 uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST;
static volatile struct reset *resetinfo = (volatile struct reset *)TK1_MMIO_RESETINFO_BASE;
// clang-format on // clang-format on
int32_t syscall_handler(uint32_t number, uint32_t arg1) extern struct partition_table_storage part_table_storage;
extern uint8_t part_status;
int32_t syscall_handler(uint32_t number, uint32_t arg1, uint32_t arg2,
uint32_t arg3)
{ {
struct reset *userreset = (struct reset *)arg1;
switch (number) { switch (number) {
case TK1_SYSCALL_RESET: case TK1_SYSCALL_RESET:
if (arg2 > sizeof(resetinfo->next_app_data)) {
return -1;
}
(void)memset((void *)resetinfo, 0, sizeof(*resetinfo));
resetinfo->type = userreset->type;
memcpy((void *)resetinfo->app_digest, userreset->app_digest,
32);
memcpy((void *)resetinfo->next_app_data,
userreset->next_app_data, arg2);
*system_reset = 1; *system_reset = 1;
// Should not be reached.
assert(1 == 2);
break;
case TK1_SYSCALL_ALLOC_AREA:
if (storage_allocate_area(&part_table_storage) < 0) {
debug_puts("couldn't allocate storage area\n");
return -1;
}
return 0; return 0;
case TK1_SYSCALL_SET_LED: case TK1_SYSCALL_DEALLOC_AREA:
led_set(arg1); if (storage_deallocate_area(&part_table_storage) < 0) {
debug_puts("couldn't deallocate storage area\n");
return -1;
}
return 0;
case TK1_SYSCALL_WRITE_DATA:
if (storage_write_data(&part_table_storage.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_storage.table, arg1,
(uint8_t *)arg2, arg3) < 0) {
debug_puts("couldn't read storage area\n");
return -1;
}
return 0; return 0;
case TK1_SYSCALL_GET_VIDPID: case TK1_SYSCALL_GET_VIDPID:
// UDI is 2 words: VID/PID & serial. Return just the // UDI is 2 words: VID/PID & serial. Return just the
// first word. Serial is kept secret to the device // first word. Serial is kept secret to the device
// app. // app.
return udi[0]; return udi[0];
case TK1_SYSCALL_PRELOAD_DELETE:
return preload_delete(&part_table_storage, 1);
case TK1_SYSCALL_PRELOAD_STORE:
// arg1 offset
// arg2 data
// arg3 size
// always using slot 1
return preload_store(&part_table_storage.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_storage, arg1,
(uint8_t *)arg2, (uint8_t *)arg3,
1);
case TK1_SYSCALL_PRELOAD_GET_DIGSIG:
return preload_get_digsig(&part_table_storage.table,
(uint8_t *)arg1, (uint8_t *)arg2, 1);
case TK1_SYSCALL_STATUS:
return part_get_status();
default: default:
assert(1 == 2); assert(1 == 2);
} }

View File

@ -6,8 +6,18 @@
enum syscall_num { enum syscall_num {
TK1_SYSCALL_RESET = 1, TK1_SYSCALL_RESET = 1,
TK1_SYSCALL_SET_LED = 10, TK1_SYSCALL_ALLOC_AREA = 2,
TK1_SYSCALL_GET_VIDPID = 12, 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_STATUS = 13,
}; };
#endif #endif

View File

@ -17,7 +17,7 @@ INCLUDE=include
# either of them. You don't need to recompile tkey-libs. # either of them. You don't need to recompile tkey-libs.
CFLAGS = -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 \ CFLAGS = -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 \
-mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common \ -mcmodel=medany -static -std=gnu99 -Os -ffast-math -fno-common \
-fno-builtin-printf -fno-builtin-putchar -nostdlib -mno-relax -flto \ -fno-builtin-printf -fno-builtin-putchar -nostdlib -mno-relax -flto \
-Wall -Werror=implicit-function-declaration \ -Wall -Werror=implicit-function-declaration \
-I $(INCLUDE) -I . -I $(INCLUDE) -I .
@ -31,7 +31,7 @@ LDFLAGS=-T app.lds -L libcommon/ -lcommon -L libcrt0/ -lcrt0
.PHONY: all .PHONY: all
all: libcrt0.a libcommon.a libmonocypher.a all: libcrt0.a libcommon.a libmonocypher.a libblake2s.a
IMAGE=ghcr.io/tillitis/tkey-builder:4 IMAGE=ghcr.io/tillitis/tkey-builder:4
@ -48,12 +48,12 @@ libcrt0.a: libcrt0/crt0.o
$(AR) -qc $@ libcrt0/crt0.o $(AR) -qc $@ libcrt0/crt0.o
# Common C functions # Common C functions
LIBOBJS=libcommon/assert.o libcommon/blake2s.o libcommon/led.o libcommon/lib.o \ LIBOBJS=libcommon/assert.o libcommon/led.o libcommon/lib.o \
libcommon/proto.o libcommon/touch.o libcommon/io.o libcommon/proto.o libcommon/touch.o libcommon/io.o
libcommon.a: $(LIBOBJS) libcommon.a: $(LIBOBJS)
$(AR) -qc $@ $(LIBOBJS) $(AR) -qc $@ $(LIBOBJS)
$(LIBOBJS): include/tkey/assert.h include/tkey/blake2s.h include/tkey/led.h \ $(LIBOBJS): include/tkey/assert.h include/tkey/led.h \
include/tkey/lib.h include/tkey/proto.h include/tkey/tk1_mem.h \ include/tkey/lib.h include/tkey/proto.h include/tkey/tk1_mem.h \
include/tkey/touch.h include/tkey/debug.h include/tkey/touch.h include/tkey/debug.h
@ -63,12 +63,19 @@ libmonocypher.a: $(MONOOBJS)
$(AR) -qc $@ $(MONOOBJS) $(AR) -qc $@ $(MONOOBJS)
$MONOOBJS: monocypher/monocypher-ed25519.h monocypher/monocypher.h $MONOOBJS: monocypher/monocypher-ed25519.h monocypher/monocypher.h
# blake2s
B2OBJS=blake2s/blake2s.o
libblake2s.a: $(B2OBJS)
$(AR) -qc $@ $(B2OBJS)
$B2OBJS: blake2s/blake2s.h
LIBS=libcrt0.a libcommon.a LIBS=libcrt0.a libcommon.a
.PHONY: clean .PHONY: clean
clean: clean:
rm -f $(LIBS) $(LIBOBJS) libcrt0/crt0.o rm -f $(LIBS) $(LIBOBJS) libcrt0/crt0.o
rm -f libmonocypher.a $(MONOOBJS) rm -f libmonocypher.a $(MONOOBJS)
rm -f libblake2s.a $(B2OBJS)
# Create compile_commands.json for clangd and LSP # Create compile_commands.json for clangd and LSP
.PHONY: clangd .PHONY: clangd

View File

@ -25,5 +25,9 @@ modify it under the terms of the BSD-2-Clause license.
See LICENSE for the full BSD-2-Clause license text. See LICENSE for the full BSD-2-Clause license text.
Note that Monocypher is Copyright Loup Vaillant and released under CC0 Note that:
1.0 Universal, see monocypher/LICENSE.
- Monocypher is Copyright Loup Vaillant and released under CC0
1.0 Universal, see monocypher/LICENSE.
- blake2s is Copyright Markku-Juhani O. Saarinen and released under CC0
1.0 Universal, see blake2s/LICENSE.

View File

@ -4,13 +4,15 @@
- C runtime: libcrt0. - C runtime: libcrt0.
- Common C functions including protocol calls: libcommon. - Common C functions including protocol calls: libcommon.
- Cryptographic functions: libmonocypher. - Cryptographic functions: libmonocypher. Based on
Based on monocypher version 4.0.2 [Monocypher](https://github.com/LoupVaillant/Monocypher) version
https://github.com/LoupVaillant/Monocypher 4.0.2
- BLAKE2s hash function: libblake2s.
Release notes in [RELEASE.md](RELEASE.md). Release notes in [RELEASE.md](RELEASE.md).
## Licenses and SPDX tags ## Licenses
Unless otherwise noted, the project sources are copyright Tillitis AB, Unless otherwise noted, the project sources are copyright Tillitis AB,
licensed under the terms and conditions of the "BSD-2-Clause" license. licensed under the terms and conditions of the "BSD-2-Clause" license.
See [LICENSE](LICENSE) for the full license text. See [LICENSE](LICENSE) for the full license text.
@ -22,6 +24,21 @@ directories. They may be released under other licenses. This is noted
with a similar `LICENSE` file in every directory containing imported with a similar `LICENSE` file in every directory containing imported
sources. sources.
Imported sources:
- [Monocypher](https://github.com/LoupVaillant/Monocypher) (BSD-2) by
Loup Vaillant.
- blake2s (CC-0), originally based on the reference implementation in
[RFC 7693](https://www.rfc-editor.org/rfc/rfc7693.html) written by
Markku-Juhani O. Saarinen ([original
repository](https://github.com/mjosaarinen/blake2_mjosref). Imported
from [Joachim Strömbergson's
fork](https://github.com/secworks/blake2s/) used as a model for a
hardware implementation.
### SPDX tags
The project uses single-line references to Unique License Identifiers The project uses single-line references to Unique License Identifiers
as defined by the Linux Foundation's [SPDX project](https://spdx.org/) as defined by the Linux Foundation's [SPDX project](https://spdx.org/)
on its own source files, but not necessarily imported files. The line on its own source files, but not necessarily imported files. The line
@ -40,13 +57,18 @@ specification](https://reuse.software/).
### Bellatrix and earlier ### Bellatrix and earlier
Please note that you need to use `uart_write()` and `uart_read()` for Please note that:
I/O.
If you want debug prints in QEMU you can still use `write(IO_QEMU, - For reading, only use the blocking `uart_read()`.
...)`. Avoid using `write()` in other cases.
- Only `IO_UART` and `IO_QEMU` destinations are useful for writing as
in `write(IO_UART, ...)`, `puts(IO_UART, ...)`, and so on.
- Defining `QEMU_DEBUG` works with all the `debug_*` functions, but
`TKEY_DEBUG` does not.
## Building ## Building
In order to build, you must have the `make`, `clang`, `llvm`, and In order to build, you must have the `make`, `clang`, `llvm`, and
`lld` packages installed. `lld` packages installed.

View File

@ -2,15 +2,37 @@
## Upcoming release ## Upcoming release
NOTE WELL! Rewritten I/O functions with new semantics! - NOTE WELL! Rewritten I/O functions with new signatures and
semantics!
- `blake2s()` with new signature.
### BLAKE2s hash function
The `blake2s()` function no longer call the firmware.
- The `blake2s.h` header file has moved to `blake2s/blake2s.h`.
- The `blake2s()` hash function has changed signature. It's now defined
as:
```
// All-in-one convenience function.
int blake2s(void *out, size_t outlen, // return buffer for digest
const void *key, size_t keylen, // optional secret key
const void *in, size_t inlen); // data to be hashed
```
- The component functions `blake2s_init()`, `blake2s_update()`, and
`blake2s_final()` are now available.
### I/O ### I/O
The Castor TKey hardware supports more USB endpoints: The Castor TKey hardware supports more USB endpoints:
- CDC - the same thing as older versions. - CDC - the same thing as older versions.
- HID security token, for FIDO-like apps. - FIDO security token, for FIDO-like apps.
- CTRL, a HID debug port. - DEBUG, a HID debug port.
The communication is still over a single UART. To differ between the The communication is still over a single UART. To differ between the
endpoints we use an internal USB Mode Protocol between programs endpoints we use an internal USB Mode Protocol between programs
@ -18,7 +40,7 @@ running on the PicoRV32 and the CH552 USB Controller.
The I/O functions has changed accordingly. Please use: The I/O functions has changed accordingly. Please use:
- `readselect()` with appropriate bitmask (e.g. `IO_CDC|IO_HID`) to - `readselect()` with appropriate bitmask (e.g. `IO_CDC|IO_FIDO`) to
see if there's anything to read in the endpoints you are interested see if there's anything to read in the endpoints you are interested
in. Data from endpoints not mentioned in the bitmask will be in. Data from endpoints not mentioned in the bitmask will be
discarded. discarded.
@ -48,7 +70,7 @@ The optionally built debug prints have changed. You now use
You define the debug output endpoint when you compile your program by You define the debug output endpoint when you compile your program by
including `debug.h` and defining `QEMU_DEBUG` for the qemu debug port including `debug.h` and defining `QEMU_DEBUG` for the qemu debug port
or `TKEY_DEBUG` for output on the CTRL HID endpoint. If you don't or `TKEY_DEBUG` for output on the DEBUG HID endpoint. If you don't
define either, they won't appear in your code. define either, they won't appear in your code.
Similiarly, `assert()` now also follows `QEMU_DEBUG` or `TKEY_DEBUG`, Similiarly, `assert()` now also follows `QEMU_DEBUG` or `TKEY_DEBUG`,

View File

@ -21,3 +21,19 @@ path = [
] ]
SPDX-FileCopyrightText = "2022 Tillitis AB <tillitis.se>" SPDX-FileCopyrightText = "2022 Tillitis AB <tillitis.se>"
SPDX-License-Identifier = "BSD-2-Clause" SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = [
"blake2s/*",
]
SPDX-FileCopyrightText = "Markku-Juhani O. Saarinen"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = [
"blake2s/Makefile",
]
SPDX-FileCopyrightText = "2014 Secworks Sweden AB"
SPDX-License-Identifier = "BSD-2-Clause"

View File

@ -0,0 +1,52 @@
#===================================================================
#
# Makefile
# --------
# Makefile for building the blake2s model.
#
#
# Author: Joachim Strombergson
# Copyright (c) 2014, Secworks Sweden AB
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#===================================================================
SRC = blake2s_test.c blake2s.c
INC = blake2s.h
CC = clang
CC_FLAGS = -Wall
blake2s_test: $(SRC) $(INC)
$(CC) $(CC_FLAGS) -o $@ $(SRC) -I $(INC)
clean:
rm -f ./blake2s_test
rm -f *.log
rm -f *.txt

View File

@ -3,22 +3,22 @@
// blake2s.c // blake2s.c
// --------- // ---------
// //
// A simple blake2s Reference Implementation. // A simple BLAKE2s reference implementation.
//
// See LICENSE for license terms.
// See README.md in the repo root for info about source code origin.
//====================================================================== //======================================================================
#include <stdint.h> #include <stdint.h>
#include "blake2s.h" #include "blake2s.h"
// Dummy printf() for verbose mode
static void printf(const char *format, ...)
{
}
#define VERBOSE 0 #define VERBOSE 0
#define SHOW_V 0 #define SHOW_V 0
#define SHOW_M_WORDS 0 #define SHOW_M_WORDS 0
#if VERBOSE || SHOW_V || SHOW_M_WORDS
#include <stdio.h>
#endif
// Cyclic right rotation. // Cyclic right rotation.
#ifndef ROTR32 #ifndef ROTR32
@ -41,6 +41,7 @@ static const uint32_t blake2s_iv[8] = {
}; };
#if VERBOSE || SHOW_V
//------------------------------------------------------------------ //------------------------------------------------------------------
//------------------------------------------------------------------ //------------------------------------------------------------------
void print_v(uint32_t *v) { void print_v(uint32_t *v) {
@ -71,24 +72,25 @@ void print_ctx(blake2s_ctx *ctx) {
printf("\n"); printf("\n");
} }
#endif
//------------------------------------------------------------------ //------------------------------------------------------------------
// B2S_G macro redefined as a G function. // B2S_G macro redefined as a G function.
// Allows us to output intermediate values for debugging. // Allows us to output intermediate values for debugging.
//------------------------------------------------------------------ //------------------------------------------------------------------
void G(uint32_t *v, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t y) { void G(uint32_t *v, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t y) {
if (VERBOSE) { #if VERBOSE
printf("G started.\n"); printf("G started.\n");
} #endif
if (SHOW_V) { #if SHOW_V
printf("v before processing:\n"); printf("v before processing:\n");
print_v(&v[0]); print_v(&v[0]);
} #endif
if (SHOW_M_WORDS) { #if SHOW_M_WORDS
printf("x: 0x%08x, y: 0x%08x\n", x, y); printf("x: 0x%08x, y: 0x%08x\n", x, y);
} #endif
v[a] = v[a] + v[b] + x; v[a] = v[a] + v[b] + x;
v[d] = ROTR32(v[d] ^ v[a], 16); v[d] = ROTR32(v[d] ^ v[a], 16);
@ -99,14 +101,14 @@ void G(uint32_t *v, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t x,
v[c] = v[c] + v[d]; v[c] = v[c] + v[d];
v[b] = ROTR32(v[b] ^ v[c], 7); v[b] = ROTR32(v[b] ^ v[c], 7);
if (SHOW_V) { #if SHOW_V
printf("v after processing:\n"); printf("v after processing:\n");
print_v(&v[0]); print_v(&v[0]);
} #endif
if (VERBOSE) { #if VERBOSE
printf("G completed.\n\n"); printf("G completed.\n\n");
} #endif
} }
@ -131,9 +133,9 @@ static void blake2s_compress(blake2s_ctx *ctx, int last)
int i; int i;
uint32_t v[16], m[16]; uint32_t v[16], m[16];
if (VERBOSE) { #if VERBOSE
printf("blake2s_compress started.\n"); printf("blake2s_compress started.\n");
} #endif
// init work variables // init work variables
for (i = 0; i < 8; i++) { for (i = 0; i < 8; i++) {
@ -143,9 +145,9 @@ static void blake2s_compress(blake2s_ctx *ctx, int last)
// low 32 bits of offset // low 32 bits of offset
// high 32 bits // high 32 bits
if (VERBOSE) { #if VERBOSE
printf("t[0]: 0x%08x, t[1]: 0x%08x\n", ctx->t[0], ctx->t[1]); printf("t[0]: 0x%08x, t[1]: 0x%08x\n", ctx->t[0], ctx->t[1]);
} #endif
v[12] ^= ctx->t[0]; v[12] ^= ctx->t[0];
v[13] ^= ctx->t[1]; v[13] ^= ctx->t[1];
@ -159,52 +161,52 @@ static void blake2s_compress(blake2s_ctx *ctx, int last)
m[i] = B2S_GET32(&ctx->b[4 * i]); m[i] = B2S_GET32(&ctx->b[4 * i]);
} }
if (VERBOSE) { #if VERBOSE
printf("v before G processing:\n"); printf("v before G processing:\n");
print_v(&v[0]); print_v(&v[0]);
} #endif
// Ten rounds of the G function applied on rows, diagonal. // Ten rounds of the G function applied on rows, diagonal.
for (i = 0; i < 10; i++) { for (i = 0; i < 10; i++) {
if (VERBOSE) { #if VERBOSE
printf("Round %02d:\n", (i + 1)); printf("Round %02d:\n", (i + 1));
printf("Row processing started.\n"); printf("Row processing started.\n");
} #endif
G(&v[0], 0, 4, 8, 12, m[sigma[i][ 0]], m[sigma[i][ 1]]); G(&v[0], 0, 4, 8, 12, m[sigma[i][ 0]], m[sigma[i][ 1]]);
G(&v[0], 1, 5, 9, 13, m[sigma[i][ 2]], m[sigma[i][ 3]]); G(&v[0], 1, 5, 9, 13, m[sigma[i][ 2]], m[sigma[i][ 3]]);
G(&v[0], 2, 6, 10, 14, m[sigma[i][ 4]], m[sigma[i][ 5]]); G(&v[0], 2, 6, 10, 14, m[sigma[i][ 4]], m[sigma[i][ 5]]);
G(&v[0], 3, 7, 11, 15, m[sigma[i][ 6]], m[sigma[i][ 7]]); G(&v[0], 3, 7, 11, 15, m[sigma[i][ 6]], m[sigma[i][ 7]]);
if (VERBOSE) { #if VERBOSE
printf("Row processing completed.\n"); printf("Row processing completed.\n");
printf("Diagonal processing started.\n"); printf("Diagonal processing started.\n");
} #endif
G(&v[0], 0, 5, 10, 15, m[sigma[i][ 8]], m[sigma[i][ 9]]); G(&v[0], 0, 5, 10, 15, m[sigma[i][ 8]], m[sigma[i][ 9]]);
G(&v[0], 1, 6, 11, 12, m[sigma[i][10]], m[sigma[i][11]]); G(&v[0], 1, 6, 11, 12, m[sigma[i][10]], m[sigma[i][11]]);
G(&v[0], 2, 7, 8, 13, m[sigma[i][12]], m[sigma[i][13]]); G(&v[0], 2, 7, 8, 13, m[sigma[i][12]], m[sigma[i][13]]);
G(&v[0], 3, 4, 9, 14, m[sigma[i][14]], m[sigma[i][15]]); G(&v[0], 3, 4, 9, 14, m[sigma[i][14]], m[sigma[i][15]]);
if (VERBOSE) { #if VERBOSE
printf("Diagonal processing completed.\n"); printf("Diagonal processing completed.\n");
printf("\n"); printf("\n");
} #endif
} }
if (VERBOSE) { #if VERBOSE
printf("v after G processing:\n"); printf("v after G processing:\n");
print_v(&v[0]); print_v(&v[0]);
} #endif
// Update the hash state. // Update the hash state.
for (i = 0; i < 8; ++i) { for (i = 0; i < 8; ++i) {
ctx->h[i] ^= v[i] ^ v[i + 8]; ctx->h[i] ^= v[i] ^ v[i + 8];
} }
if (VERBOSE) { #if VERBOSE
printf("blake2s_compress completed.\n"); printf("blake2s_compress completed.\n");
} #endif
} }
@ -218,11 +220,11 @@ int blake2s_init(blake2s_ctx *ctx, size_t outlen,
{ {
size_t i; size_t i;
if (VERBOSE) { #if VERBOSE
printf("blake2s_init started.\n"); printf("blake2s_init started.\n");
printf("Context before blake2s_init processing:\n"); printf("Context before blake2s_init processing:\n");
print_ctx(ctx); print_ctx(ctx);
} #endif
if (outlen == 0 || outlen > 32 || keylen > 32) if (outlen == 0 || outlen > 32 || keylen > 32)
return -1; // illegal parameters return -1; // illegal parameters
@ -243,11 +245,11 @@ int blake2s_init(blake2s_ctx *ctx, size_t outlen,
ctx->c = 64; // at the end ctx->c = 64; // at the end
} }
if (VERBOSE) { #if VERBOSE
printf("Context after blake2s_init processing:\n"); printf("Context after blake2s_init processing:\n");
print_ctx(ctx); print_ctx(ctx);
printf("blake2s_init completed.\n"); printf("blake2s_init completed.\n");
} #endif
return 0; return 0;
} }
@ -261,11 +263,11 @@ void blake2s_update(blake2s_ctx *ctx,
{ {
size_t i; size_t i;
if (VERBOSE) { #if VERBOSE
printf("blake2s_update started.\n"); printf("blake2s_update started.\n");
printf("Context before blake2s_update processing:\n"); printf("Context before blake2s_update processing:\n");
print_ctx(ctx); print_ctx(ctx);
} #endif
for (i = 0; i < inlen; i++) { for (i = 0; i < inlen; i++) {
if (ctx->c == 64) { // buffer full ? if (ctx->c == 64) { // buffer full ?
@ -278,11 +280,11 @@ void blake2s_update(blake2s_ctx *ctx,
ctx->b[ctx->c++] = ((const uint8_t *) in)[i]; ctx->b[ctx->c++] = ((const uint8_t *) in)[i];
} }
if (VERBOSE) { #if VERBOSE
printf("Context after blake2s_update processing:\n"); printf("Context after blake2s_update processing:\n");
print_ctx(ctx); print_ctx(ctx);
printf("blake2s_update completed.\n"); printf("blake2s_update completed.\n");
} #endif
} }
@ -294,11 +296,11 @@ void blake2s_final(blake2s_ctx *ctx, void *out)
{ {
size_t i; size_t i;
if (VERBOSE) { #if VERBOSE
printf("blake2s_final started.\n"); printf("blake2s_final started.\n");
printf("Context before blake2s_final processing:\n"); printf("Context before blake2s_final processing:\n");
print_ctx(ctx); print_ctx(ctx);
} #endif
ctx->t[0] += ctx->c; // mark last block offset ctx->t[0] += ctx->c; // mark last block offset
@ -321,11 +323,11 @@ void blake2s_final(blake2s_ctx *ctx, void *out)
(ctx->h[i >> 2] >> (8 * (i & 3))) & 0xFF; (ctx->h[i >> 2] >> (8 * (i & 3))) & 0xFF;
} }
if (VERBOSE) { #if VERBOSE
printf("Context after blake2s_final processing:\n"); printf("Context after blake2s_final processing:\n");
print_ctx(ctx); print_ctx(ctx);
printf("blake2s_final completed.\n"); printf("blake2s_final completed.\n");
} #endif
} }
@ -334,15 +336,16 @@ void blake2s_final(blake2s_ctx *ctx, void *out)
//------------------------------------------------------------------ //------------------------------------------------------------------
int blake2s(void *out, size_t outlen, int blake2s(void *out, size_t outlen,
const void *key, size_t keylen, const void *key, size_t keylen,
const void *in, size_t inlen, const void *in, size_t inlen)
blake2s_ctx *ctx)
{ {
if (blake2s_init(ctx, outlen, key, keylen)) blake2s_ctx ctx;
if (blake2s_init(&ctx, outlen, key, keylen))
return -1; return -1;
blake2s_update(ctx, in, inlen); blake2s_update(&ctx, in, inlen);
blake2s_final(ctx, out); blake2s_final(&ctx, out);
return 0; return 0;
} }

View File

@ -1,5 +1,12 @@
//======================================================================
//
// blake2s.h // blake2s.h
// ---------
// BLAKE2s Hashing Context and API Prototypes // BLAKE2s Hashing Context and API Prototypes
//
// See LICENSE for license terms.
// See README.md in the repo root for info about source code origin.
//======================================================================
#ifndef BLAKE2S_H #ifndef BLAKE2S_H
#define BLAKE2S_H #define BLAKE2S_H
@ -33,8 +40,6 @@ void blake2s_final(blake2s_ctx *ctx, void *out);
// All-in-one convenience function. // All-in-one convenience function.
int blake2s(void *out, size_t outlen, // return buffer for digest int blake2s(void *out, size_t outlen, // return buffer for digest
const void *key, size_t keylen, // optional secret key const void *key, size_t keylen, // optional secret key
const void *in, size_t inlen, // data to be hashed const void *in, size_t inlen); // data to be hashed
blake2s_ctx *ctx);
#endif #endif

View File

@ -0,0 +1,138 @@
//======================================================================
//
// blake2s_test.c
// --------------
//
//======================================================================
#include <stdio.h>
#include "blake2s.h"
//------------------------------------------------------------------
//------------------------------------------------------------------
void print_message(uint8_t *m, int mlen) {
printf("The message:\n");
for (int i = 1 ; i <= mlen ; i++) {
printf("0x%02x ", m[(i - 1)]);
if (i % 8 == 0) {
printf("\n");
}
}
printf("\n");
}
//------------------------------------------------------------------
//------------------------------------------------------------------
void print_digest(uint8_t *md) {
printf("The digest:\n");
for (int j = 0 ; j < 4 ; j++) {
for (int i = 0 ; i < 8 ; i++) {
printf("0x%02x ", md[i + 8 * j]);
}
printf("\n");
}
printf("\n");
}
//------------------------------------------------------------------
// test_zero_length()
// Test with a zero length mwssage.
//------------------------------------------------------------------
void test_zero_length() {
uint8_t md[32];
printf("Testing zero byte message.\n");
blake2s(md, 32, NULL, 0, NULL, 0);
print_digest(md);
printf("\n");
}
//------------------------------------------------------------------
// test_abc_message()
// Test with a zero length mwssage.
//------------------------------------------------------------------
void test_abc_message() {
uint8_t md[32];
uint8_t msg[64] = {'a', 'b', 'c'};
printf("Testing with RFC 7693 three byte 'abc' message.\n");
print_message(msg, 3);
blake2s(md, 32, NULL, 0, msg, 3);
print_digest(md);
printf("\n");
}
//------------------------------------------------------------------
// test_one_block_message()
// Test with a 64 byte message, filling one block.
//------------------------------------------------------------------
void test_one_block_message() {
uint8_t md[32];
uint8_t msg[64];
for (uint8_t i = 0 ; i < 64 ; i++) {
msg[i] = i;
}
printf("Testing with 64 byte message.\n");
print_message(msg, 64);
blake2s(md, 32, NULL, 0, msg, 64);
print_digest(md);
printf("\n");
}
//------------------------------------------------------------------
// test_one_block_one_byte_message()
// Test with a 65 byte message, filling one block and a single
// byte in the next block.
//------------------------------------------------------------------
void test_one_block_one_byte_message() {
uint8_t md[32];
uint8_t msg[65];
for (uint8_t i = 0 ; i < 65 ; i++) {
msg[i] = i;
}
printf("Testing with 65 byte message.\n");
print_message(msg, 65);
blake2s(md, 32, NULL, 0, msg, 65);
print_digest(md);
printf("\n");
}
//------------------------------------------------------------------
//------------------------------------------------------------------
int main(void) {
printf("\n");
printf("BLAKE2s reference model started. Performing a set of tests..\n");
printf("Performing a set of tests.\n");
test_zero_length();
test_abc_message();
test_one_block_message();
test_one_block_one_byte_message();
printf("BLAKE2s reference model completed.\n");
printf("\n");
return 0;
}
//======================================================================
/// EOF blake2s_test.c
//======================================================================

View File

@ -14,9 +14,8 @@
#elif defined(TKEY_DEBUG) #elif defined(TKEY_DEBUG)
#define assert(expr) \ #define assert(expr) \
((expr) \ ((expr) ? (void)(0) \
? (void)(0) \ : assert_fail(IO_DEBUG, #expr, __FILE__, __LINE__, __func__))
: assert_fail(IO_TKEYCTRL, #expr, __FILE__, __LINE__, __func__))
#else #else

View File

@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: 2023 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#ifndef TKEY_BLAKE2S_H
#define TKEY_BLAKE2S_H
#include <stddef.h>
#include <stdint.h>
// blake2s state context
typedef struct {
uint8_t b[64]; // input buffer
uint32_t h[8]; // chained state
uint32_t t[2]; // total number of bytes
size_t c; // pointer for b[]
size_t outlen; // digest size
} blake2s_ctx;
int blake2s(void *out, unsigned long outlen, const void *key,
unsigned long keylen, const void *in, unsigned long inlen,
blake2s_ctx *ctx);
#endif

View File

@ -18,12 +18,12 @@
#elif defined(TKEY_DEBUG) #elif defined(TKEY_DEBUG)
#define debug_putchar(ch) putchar(IO_TKEYCTRL, ch) #define debug_putchar(ch) putchar(IO_DEBUG, ch)
#define debug_lf() putchar(IO_TKEYCTRL, '\n') #define debug_lf() putchar(IO_DEBUG, '\n')
#define debug_putinthex(ch) putinthex(IO_TKEYCTRL, ch) #define debug_putinthex(ch) putinthex(IO_DEBUG, ch)
#define debug_puts(s) puts(IO_TKEYCTRL, s) #define debug_puts(s) puts(IO_DEBUG, s)
#define debug_puthex(ch) puthex(IO_TKEYCTRL, ch) #define debug_puthex(ch) puthex(IO_DEBUG, ch)
#define debug_hexdump(buf, len) hexdump(IO_TKEYCTRL, buf, len) #define debug_hexdump(buf, len) hexdump(IO_DEBUG, buf, len)
#else #else

View File

@ -10,15 +10,20 @@
// I/O endpoints. Keep it as bits possible to use in a bitmask in // I/O endpoints. Keep it as bits possible to use in a bitmask in
// readselect(). // readselect().
// //
// Note that the the TKEYCTRL, CDC, and HID should be kept the same on // Note that the DEBUG, CDC, and FIDO should be kept the same on
// the CH552 side. // the CH552 side.
enum ioend { enum ioend {
IO_NONE = 0x00, // No endpoint IO_NONE = 0x00, // No endpoint
IO_UART = 0x01, // Only destination, raw UART access IO_UART = 0x01, // Only destination, raw UART access
IO_QEMU = 0x10, // Only destination, QEMU debug port IO_QEMU = 0x02, // Only destination, QEMU debug port
IO_TKEYCTRL = 0x20, // HID debug port IO_CH552 = 0x10, // Internal CH552 control port
IO_CDC = 0x40, // CDC "serial port" IO_DEBUG = 0x20, // HID debug port
IO_HID = 0x80, // HID security token IO_CDC = 0x40, // CDC "serial port"
IO_FIDO = 0x80, // FIDO security token port
};
enum ch552cmd {
SET_ENDPOINTS = 0x01, // Config USB endpoints on the CH552
}; };
void write(enum ioend dest, const uint8_t *buf, size_t nbytes); void write(enum ioend dest, const uint8_t *buf, size_t nbytes);
@ -30,4 +35,6 @@ void puthex(enum ioend dest, const uint8_t ch);
void putinthex(enum ioend dest, const uint32_t n); void putinthex(enum ioend dest, const uint32_t n);
void puts(enum ioend dest, const char *s); void puts(enum ioend dest, const char *s);
void hexdump(enum ioend dest, void *buf, int len); void hexdump(enum ioend dest, void *buf, int len);
void config_endpoints(enum ioend endpoints);
#endif #endif

View File

@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2023 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include <stdint.h>
#include <tkey/blake2s.h>
#include <tkey/tk1_mem.h>
int blake2s(void *out, unsigned long outlen, const void *key,
unsigned long keylen, const void *in, unsigned long inlen,
blake2s_ctx *ctx)
{
// Not implemented.
return -1;
}

View File

@ -80,9 +80,9 @@ static void write_with_header(enum ioend dest, const uint8_t *buf,
// //
// - IO_CDC: Through the UART for the CDC endpoint, with header. // - IO_CDC: Through the UART for the CDC endpoint, with header.
// //
// - IO_HID: Through the UART for the HID endpoint, with header. // - IO_FIDO: Through the UART for the FIDO endpoint, with header.
// //
// - IO_TKEYCTRL: Through the UART for the debug HID endpoint, with // - IO_DEBUG: Through the UART for the DEBUG HID endpoint, with
// header. // header.
void write(enum ioend dest, const uint8_t *buf, size_t nbytes) void write(enum ioend dest, const uint8_t *buf, size_t nbytes)
{ {
@ -194,18 +194,18 @@ static int discard(size_t nbytes)
// //
// Use like this: // Use like this:
// //
// readselect(IO_CDC|IO_HID, &endpoint, &len) // readselect(IO_CDC|IO_FIDO, &endpoint, &len)
// //
// to wait for some data from either the CDC or the HID endpoint. // to wait for some data from either the CDC or the FIDO endpoint.
// //
// NOTE WELL: You need to call readselect() first, before doing any // NOTE WELL: You need to call readselect() first, before doing any
// calls to read(). // calls to read().
// //
// Only endpoints available for read are: // Only endpoints available for read are:
// //
// - IO_TKEYCTRL // - IO_DEBUG
// - IO_CDC // - IO_CDC
// - IO_HID // - IO_FIDO
// //
// If you need blocking low-level UART reads, use uart_read() instead. // If you need blocking low-level UART reads, use uart_read() instead.
// //
@ -215,7 +215,7 @@ static int discard(size_t nbytes)
// Returns non-zero on error. // Returns non-zero on error.
int readselect(int bitmask, enum ioend *endpoint, uint8_t *len) int readselect(int bitmask, enum ioend *endpoint, uint8_t *len)
{ {
if (bitmask & IO_UART || bitmask & IO_QEMU) { if ((bitmask & IO_UART) || (bitmask & IO_QEMU)) {
// Not possible to use readselect() on these // Not possible to use readselect() on these
// endpoints. // endpoints.
return -1; return -1;
@ -348,3 +348,27 @@ void hexdump(enum ioend dest, void *buf, int len)
write(dest, rowbuf, rowpos); write(dest, rowbuf, rowpos);
} }
} }
// Configure USB endpoints that should be enabled/disabled
//
// Allowed options are:
// - IO_FIDO
// - IO_DEBUG
//
// The following are always enabled:
// - IO_CDC
// - IO_CH552
//
// Use like this:
//
// config_endpoints(IO_FIDO|IO_DEBUG)
//
void config_endpoints(enum ioend endpoints)
{
uint8_t cmdbuf[2] = {0};
cmdbuf[0] = SET_ENDPOINTS;
cmdbuf[1] = endpoints;
write(IO_CH552, cmdbuf, 2);
}

View File

@ -29,6 +29,7 @@ RELEASE.md
example-app/Makefile example-app/Makefile
monocypher/LICENSE monocypher/LICENSE
monocypher/README.md monocypher/README.md
blake2s/*
) )
is_missingok() { is_missingok() {

View File

@ -0,0 +1,29 @@
# b2s
The firmware included a BLAKE2s digest of the expected device app in
the first app slot. The firmware refuses to start the app if the
computed digest differs from the constant.
To simplify computing the digest, use this tool with the `-c` flag for
including the digest in a C program:
## Building
`go build`
## Running
```
./b2s -m b2s -c
// BLAKE2s digest of b2s
uint8_t digest[32] = {
0x17, 0x36, 0xe9, 0x4e, 0xeb, 0x1b, 0xa2, 0x30, 0x89, 0xa9, 0xaa, 0xe, 0xf2, 0x6f, 0x35, 0xb2, 0xa9, 0x89, 0xac, 0x64, 0x63, 0xde, 0x38, 0x60, 0x47, 0x40, 0x91, 0x4e, 0xd7, 0x72, 0xa0, 0x58,
};
```
To print the digest in a more user friendly way, leave out the `-c`:
```
./b2s -m b2s
1736e94eeb1ba23089a9aa0ef26f35b2a989ac6463de38604740914ed772a058 b2s
```

View File

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2025 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
package main
import (
"flag"
"fmt"
"os"
"golang.org/x/crypto/blake2s"
)
func usage() {
fmt.Printf("Usage: %s -m filename [-c]\n", os.Args[0])
}
func printCDigest(digest [blake2s.Size]byte, fileName string) {
fmt.Printf("// BLAKE2s digest of %v\n", fileName)
fmt.Printf("const uint8_t digest[32] = {\n")
for _, n := range digest {
fmt.Printf("0x%x, ", n)
}
fmt.Printf("\n}; \n")
}
func main() {
var messageFile string
var forC bool
flag.StringVar(&messageFile, "m", "", "Specify file containing message.")
flag.BoolVar(&forC, "c", false, "Print digest for inclusion in C program.")
flag.Usage = usage
flag.Parse()
if messageFile == "" {
usage()
os.Exit(0)
}
message, err := os.ReadFile(messageFile)
if err != nil {
fmt.Printf("%v\n", err)
os.Exit(1)
}
digest := blake2s.Sum256(message)
if forC {
printCDigest(digest, messageFile)
} else {
fmt.Printf("%x %s\n", digest, messageFile)
}
os.Exit(0)
}

View File

@ -0,0 +1,9 @@
module b2s
go 1.23.0
toolchain go1.23.7
require golang.org/x/crypto v0.36.0
require golang.org/x/sys v0.31.0 // indirect

View File

@ -0,0 +1,4 @@
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

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)

Binary file not shown.

View File

@ -0,0 +1,34 @@
#!/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 install default partition table."
read -p "Press CTRL-C to abort. Press key to continue." -n1 -s
# Write both copies of the partition table
tillitis-iceprog -o 128k default_partition.bin
tillitis-iceprog -o 0xf0000 default_partition.bin
# Erase existing pre loaded app
tillitis-iceprog -o "$START_ADDRESS" -e 128k
# Write pre loaded app
tillitis-iceprog -o "$START_ADDRESS" "$APP"

View File

@ -0,0 +1,7 @@
module partition_table
go 1.23.0
require golang.org/x/crypto v0.36.0
require golang.org/x/sys v0.31.0 // indirect

View File

@ -0,0 +1,4 @@
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

View File

@ -0,0 +1,172 @@
package main
import (
"encoding/binary"
"flag"
"fmt"
"io"
"os"
"golang.org/x/crypto/blake2s"
)
type PreLoadedAppData struct {
Size uint32
Digest [32]uint8
Signature [64]uint8
}
type Auth struct {
Nonce [16]uint8
AuthDigest [16]uint8
}
type AppStorage struct {
Status uint8
Auth Auth
}
type PartTable struct {
Version uint8
PreLoadedAppData [2]PreLoadedAppData
AppStorage [4]AppStorage
}
type PartTableStorage struct {
PartTable PartTable
Digest [16]uint8
}
type Flash struct {
Bitstream [0x20000]uint8
PartitionTableStorage PartTableStorage
PartitionTablePadding [64*1024 - 349]uint8
PreLoadedApp0 [0x20000]uint8
PreLoadedApp1 [0x20000]uint8
AppStorage [4][0x20000]uint8
EndPadding [0x10000]uint8
}
func readStruct[T PartTableStorage | Flash](filename string) T {
var s T
file, err := os.Open(filename)
if err != nil {
panic(err)
}
if err := binary.Read(file, binary.LittleEndian, &s); err != nil {
panic(err)
}
sLen, err := file.Seek(0, io.SeekCurrent)
if err != nil {
panic(err)
}
fmt.Fprintf(os.Stderr, "INFO: %T struct is %d byte long\n", *new(T), sLen)
return s
}
func printPartTableStorageCondensed(storage PartTableStorage) {
fmt.Printf("Partition Table Storage\n")
fmt.Printf(" Partition Table\n")
fmt.Printf(" Header\n")
fmt.Printf(" Version : %d\n", storage.PartTable.Version)
for i, appData := range storage.PartTable.PreLoadedAppData {
fmt.Printf(" Preloaded App %d\n", i)
fmt.Printf(" Size : %d\n", appData.Size)
fmt.Printf(" Digest : %x\n", appData.Digest[:16])
fmt.Printf(" %x\n", appData.Digest[16:])
fmt.Printf(" Signature : %x\n", appData.Signature[:16])
fmt.Printf(" %x\n", appData.Signature[16:32])
fmt.Printf(" %x\n", appData.Signature[32:48])
fmt.Printf(" %x\n", appData.Signature[48:])
}
fmt.Printf(" Digest : %x\n", storage.Digest)
}
func calculateStorageDigest(data []byte) []byte {
key := [16]byte{}
hash, err := blake2s.New128(key[:])
if err != nil {
panic(err)
}
hash.Write(data)
digest := hash.Sum([]byte{})
return digest
}
func generatePartTableStorage(outputFilename string, app0Filename string) {
storage := PartTableStorage{
PartTable: PartTable{
Version: 1,
PreLoadedAppData: [2]PreLoadedAppData{},
AppStorage: [4]AppStorage{},
},
}
if app0Filename != "" {
stat, err := os.Stat(app0Filename)
if err != nil {
panic(err)
}
storage.PartTable.PreLoadedAppData[0].Size = uint32(stat.Size())
}
buf := make([]byte, 4096, 4096)
len, err := binary.Encode(buf, binary.LittleEndian, storage.PartTable)
if err != nil {
panic(err)
}
fmt.Printf("buf - len: %d, data: %x\n", len, buf[:len])
digest := calculateStorageDigest(buf[:len])
copy(storage.Digest[:], digest)
fmt.Printf("digest: %x\n", digest)
storageFile, err := os.Create(outputFilename)
if err != nil {
panic(err)
}
if err := binary.Write(storageFile, binary.LittleEndian, storage); err != nil {
panic(err)
}
}
func main() {
input := ""
output := ""
app0 := ""
flash := false
flag.StringVar(&input, "i", "", "Input binary dump file. Cannot be used with -o.")
flag.StringVar(&output, "o", "", "Output binary dump file. Cannot be used with -i.")
flag.StringVar(&app0, "app0", "", "Binary in pre loaded app slot 0. Can be used with -o.")
flag.BoolVar(&flash, "f", false, "Treat input file as a dump of the entire flash memory.")
flag.Parse()
if (input == "" && output == "") || (input != "" && output != "") {
flag.Usage()
os.Exit(1)
}
if input != "" {
var storage PartTableStorage
if flash {
storage = readStruct[Flash](input).PartitionTableStorage
} else {
storage = readStruct[PartTableStorage](input)
}
printPartTableStorageCondensed(storage)
} else if output != "" {
generatePartTableStorage(output, app0)
}
}