mirror of
https://github.com/tillitis/tillitis-key1.git
synced 2025-04-23 16:39:19 -04:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e10f7007a4 | ||
![]() |
93704287d5 | ||
![]() |
ba7df35064 | ||
![]() |
2746070402 | ||
![]() |
b70d42471b | ||
![]() |
8e7108f7ee | ||
![]() |
6e793bea26 | ||
![]() |
d34e8e2c12 | ||
![]() |
91471c2962 | ||
![]() |
0a0f5bcec8 | ||
![]() |
7e859bd06a | ||
![]() |
403013a0e8 | ||
![]() |
c9a7910965 | ||
![]() |
50966f010d | ||
![]() |
e9ddf29ce9 | ||
![]() |
1ff6e0262f | ||
![]() |
81f3195592 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -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
|
||||||
|
12
README.md
12
README.md
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
75
hw/application_fpga/fw/reset_test/Makefile
Normal file
75
hw/application_fpga/fw/reset_test/Makefile
Normal 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)
|
64
hw/application_fpga/fw/reset_test/app.lds
Normal file
64
hw/application_fpga/fw/reset_test/app.lds
Normal 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 */
|
||||||
|
}
|
53
hw/application_fpga/fw/reset_test/crt0.S
Normal file
53
hw/application_fpga/fw/reset_test/crt0.S
Normal 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
|
161
hw/application_fpga/fw/reset_test/main.c
Normal file
161
hw/application_fpga/fw/reset_test/main.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
hw/application_fpga/fw/reset_test/syscall.S
Normal file
85
hw/application_fpga/fw/reset_test/syscall.S
Normal 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
|
@ -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 \
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
73
hw/application_fpga/fw/testloadapp/Makefile
Normal file
73
hw/application_fpga/fw/testloadapp/Makefile
Normal 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)
|
28
hw/application_fpga/fw/testloadapp/blink.h
Normal file
28
hw/application_fpga/fw/testloadapp/blink.h
Normal 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
|
218
hw/application_fpga/fw/testloadapp/main.c
Normal file
218
hw/application_fpga/fw/testloadapp/main.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
hw/application_fpga/fw/tk1/auth_app.c
Normal file
76
hw/application_fpga/fw/tk1/auth_app.c
Normal 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;
|
||||||
|
}
|
14
hw/application_fpga/fw/tk1/auth_app.h
Normal file
14
hw/application_fpga/fw/tk1/auth_app.h
Normal 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
|
@ -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.
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
244
hw/application_fpga/fw/tk1/flash.c
Normal file
244
hw/application_fpga/fw/tk1/flash.c
Normal 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;
|
||||||
|
}
|
55
hw/application_fpga/fw/tk1/flash.h
Normal file
55
hw/application_fpga/fw/tk1/flash.h
Normal 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
|
@ -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;
|
||||||
}
|
}
|
||||||
|
43
hw/application_fpga/fw/tk1/mgmt_app.c
Normal file
43
hw/application_fpga/fw/tk1/mgmt_app.c
Normal 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;
|
||||||
|
}
|
14
hw/application_fpga/fw/tk1/mgmt_app.h
Normal file
14
hw/application_fpga/fw/tk1/mgmt_app.h
Normal 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
|
106
hw/application_fpga/fw/tk1/partition_table.c
Normal file
106
hw/application_fpga/fw/tk1/partition_table.c
Normal 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;
|
||||||
|
}
|
109
hw/application_fpga/fw/tk1/partition_table.h
Normal file
109
hw/application_fpga/fw/tk1/partition_table.h
Normal 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
|
198
hw/application_fpga/fw/tk1/preload_app.c
Normal file
198
hw/application_fpga/fw/tk1/preload_app.c
Normal 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;
|
||||||
|
}
|
24
hw/application_fpga/fw/tk1/preload_app.h
Normal file
24
hw/application_fpga/fw/tk1/preload_app.h
Normal 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
|
28
hw/application_fpga/fw/tk1/resetinfo.h
Normal file
28
hw/application_fpga/fw/tk1/resetinfo.h
Normal 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
|
29
hw/application_fpga/fw/tk1/rng.c
Normal file
29
hw/application_fpga/fw/tk1/rng.c
Normal 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;
|
||||||
|
}
|
11
hw/application_fpga/fw/tk1/rng.h
Normal file
11
hw/application_fpga/fw/tk1/rng.h
Normal 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
|
100
hw/application_fpga/fw/tk1/spi.c
Normal file
100
hw/application_fpga/fw/tk1/spi.c
Normal 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;
|
||||||
|
}
|
13
hw/application_fpga/fw/tk1/spi.h
Normal file
13
hw/application_fpga/fw/tk1/spi.h
Normal 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
|
@ -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
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
278
hw/application_fpga/fw/tk1/storage.c
Normal file
278
hw/application_fpga/fw/tk1/storage.c
Normal 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);
|
||||||
|
}
|
22
hw/application_fpga/fw/tk1/storage.h
Normal file
22
hw/application_fpga/fw/tk1/storage.h
Normal 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
|
@ -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
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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`,
|
||||||
|
@ -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"
|
||||||
|
52
hw/application_fpga/tkey-libs/blake2s/Makefile
Normal file
52
hw/application_fpga/tkey-libs/blake2s/Makefile
Normal 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
|
@ -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;
|
||||||
}
|
}
|
@ -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
|
||||||
|
|
138
hw/application_fpga/tkey-libs/blake2s/blake2s_test.c
Normal file
138
hw/application_fpga/tkey-libs/blake2s/blake2s_test.c
Normal 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
|
||||||
|
//======================================================================
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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() {
|
||||||
|
29
hw/application_fpga/tools/b2s/README.md
Normal file
29
hw/application_fpga/tools/b2s/README.md
Normal 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
|
||||||
|
```
|
59
hw/application_fpga/tools/b2s/b2s.go
Normal file
59
hw/application_fpga/tools/b2s/b2s.go
Normal 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)
|
||||||
|
}
|
9
hw/application_fpga/tools/b2s/go.mod
Normal file
9
hw/application_fpga/tools/b2s/go.mod
Normal 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
|
4
hw/application_fpga/tools/b2s/go.sum
Normal file
4
hw/application_fpga/tools/b2s/go.sum
Normal 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=
|
28
hw/application_fpga/tools/create_flash_image.py
Executable file
28
hw/application_fpga/tools/create_flash_image.py
Executable 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)
|
BIN
hw/application_fpga/tools/default_partition.bin
Normal file
BIN
hw/application_fpga/tools/default_partition.bin
Normal file
Binary file not shown.
34
hw/application_fpga/tools/load_preloaded_app.sh
Executable file
34
hw/application_fpga/tools/load_preloaded_app.sh
Executable 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"
|
7
hw/application_fpga/tools/partition_table/go.mod
Normal file
7
hw/application_fpga/tools/partition_table/go.mod
Normal 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
|
4
hw/application_fpga/tools/partition_table/go.sum
Normal file
4
hw/application_fpga/tools/partition_table/go.sum
Normal 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=
|
172
hw/application_fpga/tools/partition_table/partition_table.go
Normal file
172
hw/application_fpga/tools/partition_table/partition_table.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user