mirror of
https://github.com/tillitis/tillitis-key1.git
synced 2025-04-25 01:19:20 -04:00
Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
353d7e9f50 | ||
![]() |
f75620720f | ||
![]() |
fb1269b06e | ||
![]() |
770acc9b38 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -28,12 +28,10 @@
|
||||
/testbench_verilator*
|
||||
/check.smt2
|
||||
/check.vcd
|
||||
/hw/application_fpga/tkey-libs/libblake2s.a
|
||||
/hw/application_fpga/tkey-libs/libcommon.a
|
||||
/hw/application_fpga/tkey-libs/libcrt0.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.txt
|
||||
synth.v
|
||||
@ -46,7 +44,6 @@ verilated/
|
||||
*.o
|
||||
*.asc
|
||||
*.bin
|
||||
!/hw/application_fpga/tools/default_partition.bin
|
||||
*.elf
|
||||
*.map
|
||||
*.tmp
|
||||
|
12
README.md
12
README.md
@ -122,17 +122,7 @@ official version tag.
|
||||
|
||||
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
|
||||
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`.
|
||||
like `.git`, `.github`, et cetera.
|
||||
|
||||
## 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.
|
||||
|
||||
- Add SPI main controller mainly to access the flash chip.
|
||||
- Add SPI main controller mainly to access flash.
|
||||
|
||||
- Add system reset API. Device apps can reset the system and restart
|
||||
the firmware. The FPGA is not reset.
|
||||
@ -76,16 +76,8 @@ For full change log [see](https://github.com/tillitis/tillitis-key1/compare/TK1-
|
||||
- Add support for the new USB Mode Protocol to communicate with
|
||||
different endpoints.
|
||||
|
||||
- Support a filesystem on flash.
|
||||
|
||||
- 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.
|
||||
- Introduce a system call mechanism and the first syscalls: RESET,
|
||||
SET\_LED, GET\_VIDPID.
|
||||
|
||||
### CH552
|
||||
|
||||
|
@ -60,12 +60,10 @@ CFLAGS = \
|
||||
-Wall \
|
||||
-Wpedantic \
|
||||
-Wno-language-extension-token \
|
||||
-Wextra \
|
||||
-flto \
|
||||
-g \
|
||||
-I $(LIBDIR)/include \
|
||||
-I $(LIBDIR) \
|
||||
-I $(LIBDIR)/blake2s
|
||||
-I $(LIBDIR)
|
||||
|
||||
AS = clang
|
||||
|
||||
@ -126,19 +124,15 @@ FIRMWARE_OBJS = \
|
||||
$(P)/fw/tk1/main.o \
|
||||
$(P)/fw/tk1/start.o \
|
||||
$(P)/fw/tk1/proto.o \
|
||||
$(P)/fw/tk1/blake2s/blake2s.o \
|
||||
$(P)/fw/tk1/syscall_enable.o \
|
||||
$(P)/fw/tk1/syscall_handler.o \
|
||||
$(P)/fw/tk1/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
|
||||
$(P)/fw/tk1/syscall_handler.o
|
||||
|
||||
CHECK_SOURCES = \
|
||||
$(P)/fw/tk1/*.[ch]
|
||||
FIRMWARE_SOURCES = \
|
||||
$(P)/fw/tk1/main.c \
|
||||
$(P)/fw/tk1/proto.c \
|
||||
$(P)/fw/tk1/blake2s/blake2s.c \
|
||||
$(P)/fw/tk1/syscall_handler.c
|
||||
|
||||
TESTFW_OBJS = \
|
||||
$(P)/fw/testfw/main.o \
|
||||
@ -182,7 +176,7 @@ secret:
|
||||
LDFLAGS = \
|
||||
-T $(P)/fw/tk1/firmware.lds \
|
||||
-Wl,--cref,-M \
|
||||
-L $(LIBDIR) -lcommon -lblake2s
|
||||
-L $(LIBDIR) -lcommon
|
||||
|
||||
# Common libraries the firmware and testfw depend on. See
|
||||
# https://github.com/tillitis/tkey-libs/
|
||||
@ -202,9 +196,6 @@ simfirmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds
|
||||
$(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map
|
||||
|
||||
qemu_firmware.elf: CFLAGS += -DQEMU_DEBUG
|
||||
qemu_firmware.elf: ASFLAGS += -DQEMU_DEBUG
|
||||
qemu_firmware.elf: CFLAGS += -DQEMU_SYSCALL
|
||||
qemu_firmware.elf: ASFLAGS += -DQEMU_SYSCALL
|
||||
qemu_firmware.elf: firmware.elf
|
||||
mv firmware.elf qemu_firmware.elf
|
||||
|
||||
@ -217,12 +208,12 @@ compile_commands.json:
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
clang-tidy -header-filter=.* -checks=cert-* $(CHECK_SOURCES) -- $(CFLAGS)
|
||||
clang-tidy -header-filter=.* -checks=cert-* $(FIRMWARE_SOURCES) -- $(CFLAGS)
|
||||
|
||||
.PHONY: splint
|
||||
splint:
|
||||
splint \
|
||||
+unixlib \
|
||||
-nolib \
|
||||
-predboolint \
|
||||
+boolint \
|
||||
-nullpass \
|
||||
@ -233,13 +224,7 @@ splint:
|
||||
-unreachable \
|
||||
-unqualifiedtrans \
|
||||
-fullinitblock \
|
||||
+gnuextensions \
|
||||
-fixedformalarray \
|
||||
-mustfreeonly \
|
||||
-I $(LIBDIR)/include \
|
||||
-I $(LIBDIR) \
|
||||
-I $(LIBDIR)/blake2s \
|
||||
$(CHECK_SOURCES)
|
||||
$(FIRMWARE_SOURCES)
|
||||
|
||||
testfw.elf: tkey-libs $(TESTFW_OBJS) $(P)/fw/tk1/firmware.lds
|
||||
$(CC) $(CFLAGS) $(TESTFW_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map
|
||||
|
@ -1 +1 @@
|
||||
c770fe25034655241d9e0152b03fcf691c548bc50d30b574a5213abc5b36fe25 application_fpga.bin
|
||||
3d88184d4d636878d7d891e0360413b47f288eea2227435f0bfa2aad6e956f24 application_fpga.bin
|
||||
|
@ -1 +1 @@
|
||||
e72557c38bee1e16114f550b16fc04412bfba09274d7b1fe971ab6b2ef4f06421d976276175ea4df3b7d590599eb052b85a812b689d728be5031ae412b2f8d24 firmware.bin
|
||||
77e6c9e519bce27f7be1a38c10839875ac8c75c71bef540f11569e821638af85a3e724089702a1c17af4c9caa06461ef38a3b8ad5bc5cb01ecf1f3107771708f firmware.bin
|
||||
|
@ -8,14 +8,13 @@ see [the TKey Developer Handbook](https://dev.tillitis.se/).
|
||||
|
||||
## Definitions
|
||||
|
||||
- Firmware: Software in ROM responsible for loading, measuring,
|
||||
starting applications, and providing system calls. The firmware is
|
||||
included as part of the FPGA bitstream and not replacable on a usual
|
||||
consumer TKey.
|
||||
- Firmware: Software in ROM responsible for loading, measuring, and
|
||||
starting applications. The firmware is 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
|
||||
inserted into.
|
||||
- Device application or app: Software supplied by the client or from
|
||||
flash that runs on the TKey.
|
||||
- Device application or app: Software supplied by the client that runs
|
||||
on the TKey.
|
||||
|
||||
## CPU modes and firmware
|
||||
|
||||
@ -65,8 +64,9 @@ The different endpoints:
|
||||
|
||||
| *Name* | *Value* | *Comment* |
|
||||
|--------|---------|----------------------------------------------------------------------|
|
||||
| DEBUG | 0x20 | A USB HID special debug pipe. Useful for debug prints. |
|
||||
| CCID | 0x08 | USB CCID, a port for emulating a smart card |
|
||||
| CH552 | 0x10 | USB controller control |
|
||||
| DEBUG | 0x20 | A USB HID special debug pipe. Useful for debug prints. |
|
||||
| CDC | 0x40 | USB CDC-ACM, a serial port on the client. |
|
||||
| FIDO | 0x80 | A USB FIDO security token device, useful for FIDO-type applications. |
|
||||
|
||||
@ -75,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
|
||||
command to `CH552` in this form:
|
||||
|
||||
| *Name* | *Size* | *Comment* |
|
||||
|----------|--------|-------------------------------|
|
||||
| Command | 1B | Command to the CH552 firmware |
|
||||
| Argument | 1B | Data for the command |
|
||||
| *Name* | *Size* | *Comment* |
|
||||
|---------|--------|-------------------------------|
|
||||
| Command | 1B | Command to the CH552 firmware |
|
||||
| Payload | 1B | Data for the command |
|
||||
|
||||
Commands:
|
||||
|
||||
@ -91,7 +91,7 @@ Protocol](https://dev.tillitis.se/protocol/) which is described in the
|
||||
Developer Handbook.
|
||||
|
||||
The firmware uses a protocol on top of this framing layer which is
|
||||
used to load a device application. All commands are initiated by the
|
||||
used to bootstrap an application. All commands are initiated by the
|
||||
client. All commands receive a reply. See [Firmware
|
||||
protocol](http://dev.tillitis.se/protocol/#firmware-protocol) in the
|
||||
Dev Handbook for specific details.
|
||||
@ -106,191 +106,95 @@ Dev Handbook for specific details.
|
||||
|
||||
* FW\_RAM is divided into the following areas:
|
||||
|
||||
- fw stack: 3000 bytes.
|
||||
- fw stack: 3824 bytes.
|
||||
- resetinfo: 256 bytes.
|
||||
- .data and .bss: 840 bytes.
|
||||
- rest is available for .data and .bss.
|
||||
|
||||
## Firmware behaviour
|
||||
|
||||
The purpose of the firmware is to:
|
||||
|
||||
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 purpose of the firmware is to load, measure, and start an
|
||||
application received from the client over the USB/UART.
|
||||
|
||||
The firmware binary is part of the FPGA bitstream as the initial
|
||||
values of the Block RAMs used to construct the ROM. The ROM is located
|
||||
at `0x0000_0000`. This 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`.
|
||||
values of the Block RAMs used to construct the `FW_ROM`. The `FW_ROM`
|
||||
start address is located at `0x0000_0000` in the CPU memory map, which
|
||||
is also the CPU reset vector.
|
||||
|
||||
### Firmware state machine
|
||||
|
||||
This is the state diagram of the firmware. There are only four states.
|
||||
Change of state occur when we receive specific I/O or a fatal error
|
||||
occurs.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
S0: INITIAL
|
||||
S1: WAITCOMMAND
|
||||
S2: LOADING
|
||||
S3: LOAD_FLASH
|
||||
S4: LOAD_FLASH_MGMT
|
||||
S5: START
|
||||
SE: FAIL
|
||||
S1: initial
|
||||
S2: loading
|
||||
S3: running
|
||||
SE: failed
|
||||
|
||||
[*] --> S0
|
||||
[*] --> S1
|
||||
|
||||
S0 --> S1
|
||||
S0 --> S4: Default
|
||||
S0 --> S3
|
||||
S1 --> S1: Commands
|
||||
S1 --> S2: LOAD_APP
|
||||
S1 --> SE: Error
|
||||
|
||||
S1 --> S1: Other commands
|
||||
S1 --> S2: LOAD_APP
|
||||
S1 --> SE: Error
|
||||
S2 --> S2: LOAD_APP_DATA
|
||||
S2 --> S3: Last block received
|
||||
S2 --> SE: Error
|
||||
|
||||
S2 --> S2: LOAD_APP_DATA
|
||||
S2 --> S5: Last block received
|
||||
S2 --> SE: Error
|
||||
|
||||
S3 --> S5
|
||||
S3 --> SE
|
||||
|
||||
S4 --> S5
|
||||
S4 --> SE
|
||||
|
||||
SE --> [*]
|
||||
S5 --> [*]
|
||||
S3 --> [*]
|
||||
```
|
||||
|
||||
States:
|
||||
|
||||
- *INITIAL*: Transitions to next state through reset type left in
|
||||
`FW_RAM`.
|
||||
- *WAITCOMMAND*: Waiting for initial commands from client. Allows the
|
||||
commands `NAME_VERSION`, `GET_UDI`, `LOAD_APP`.
|
||||
- *LOADING*: Expecting application data from client. Allows only the
|
||||
command `LOAD_APP_DATA` to continue loading the device app.
|
||||
- *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.
|
||||
- `initial` - At start. Allows the commands `NAME_VERSION`, `GET_UDI`,
|
||||
`LOAD_APP`.
|
||||
- `loading` - Expect application data. Allows only the command
|
||||
`LOAD_APP_DATA`.
|
||||
- `run` - Computes CDI and starts the application. Allows no commands.
|
||||
- `fail` - Stops waiting for commands, flashes LED forever. Allows no
|
||||
commands.
|
||||
|
||||
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`:
|
||||
Commands in state `initial`:
|
||||
|
||||
| *command* | *next state* |
|
||||
|-----------------------|--------------|
|
||||
| `FW_CMD_NAME_VERSION` | unchanged |
|
||||
| `FW_CMD_GET_UDI` | unchanged |
|
||||
| `FW_CMD_LOAD_APP` | *LOADING* |
|
||||
| `FW_CMD_LOAD_APP` | `loading` |
|
||||
| | |
|
||||
|
||||
Commands in state `loading`:
|
||||
|
||||
| *command* | *next state* |
|
||||
|------------------------|------------------------------------|
|
||||
| `FW_CMD_LOAD_APP_DATA` | unchanged or *START* on last chunk |
|
||||
|
||||
No other states allows commands.
|
||||
| *command* | *next state* |
|
||||
|------------------------|----------------------------------|
|
||||
| `FW_CMD_LOAD_APP_DATA` | unchanged or `run` on last chunk |
|
||||
|
||||
See [Firmware protocol in the Dev
|
||||
Handbook](http://dev.tillitis.se/protocol/#firmware-protocol) for the
|
||||
definition of the specific commands and their responses.
|
||||
|
||||
Plain text explanation of the states:
|
||||
State changes from "initial" to "loading" when receiving `LOAD_APP`,
|
||||
which also sets the size of the number of data blocks to expect. After
|
||||
that we expect several `LOAD_APP_DATA` commands until the last block
|
||||
is received, when state is changed to "running".
|
||||
|
||||
- *INITIAL*: Start here. Check the `FW_RAM` for the `resetinfo` type
|
||||
for what to do next.
|
||||
In "running", the loaded device app is measured, the Compound Device
|
||||
Identifier (CDI) is computed, we do some cleanup of firmware data
|
||||
structures, enable the system calls, and finally start the app, which
|
||||
ends the firmware state machine. Hardware guarantees that we leave
|
||||
firmware mode automatically when the program counter leaves ROM.
|
||||
|
||||
For all types which begins with `FLASH_*`, set next state to
|
||||
*LOAD_FLASH*, otherwise set next state to *WAITCOMMAND*.
|
||||
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.
|
||||
|
||||
- *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*.
|
||||
### Golden path
|
||||
|
||||
- *LOAD_FLASH_MGMT*: Load device app from flash into RAM, app slot
|
||||
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.
|
||||
Firmware loads the application at the start of RAM (`0x4000_0000`). It
|
||||
use a part of the special FW\_RAM for its own stack.
|
||||
|
||||
When reset is released, the CPU starts executing the firmware. It
|
||||
begins in `start.S` by clearing all CPU registers, clears all FW\_RAM,
|
||||
@ -300,167 +204,60 @@ the system calls, but the handler is not yet enabled.
|
||||
|
||||
Beginning at `main()` it fills the entire RAM with pseudo random data
|
||||
and setting up the RAM address and data hardware scrambling with
|
||||
values from the True Random Number Generator (TRNG).
|
||||
values from the True Random Number Generator (TRNG). It then waits for
|
||||
data coming in through the UART.
|
||||
|
||||
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.
|
||||
Typical expected use scenario:
|
||||
|
||||
2. Load app data from flash slot 0 into RAM.
|
||||
1. The client sends the `FW_CMD_LOAD_APP` command with the size of
|
||||
the device app and the optional 32 byte hash of the user-supplied
|
||||
secret as arguments and gets a `FW_RSP_LOAD_APP` back. After
|
||||
using this it's not possible to restart the loading of an
|
||||
application.
|
||||
|
||||
3. Compute a BLAKE2s digest of the loaded app.
|
||||
2. If the the client receive a sucessful response, it will send
|
||||
multiple `FW_CMD_LOAD_APP_DATA` commands, together containing the
|
||||
full application.
|
||||
|
||||
4. Compare the computed digest against the allowed app digest
|
||||
hardcoded in the firmware. If it's not equal, halt CPU.
|
||||
3. On receiving`FW_CMD_LOAD_APP_DATA` commands the firmware places
|
||||
the data into `0x4000_0000` and upwards. The firmware replies
|
||||
with a `FW_RSP_LOAD_APP_DATA` response to the client for each
|
||||
received block except the last data block.
|
||||
|
||||
7. [Start the device app](#start-the-device-app).
|
||||
4. When the final block of the application image is received with a
|
||||
`FW_CMD_LOAD_APP_DATA`, the firmware measure the application by
|
||||
computing a BLAKE2s digest over the entire application. Then
|
||||
firmware send back the `FW_RSP_LOAD_APP_DATA_READY` response
|
||||
containing the digest.
|
||||
|
||||
### Start the device app
|
||||
5. The Compound Device Identifier
|
||||
([CDI]((#compound-device-identifier-computation))) is then
|
||||
computed by doing a new BLAKE2s using the Unique Device Secret
|
||||
(UDS), the application digest, and any User Supplied Secret
|
||||
(USS) digest already received.
|
||||
|
||||
1. Check if there is a verification digest left from the previous app
|
||||
in the resetinfo. If it is, compare with the loaded app's already
|
||||
computed digest. Halt CPU if different.
|
||||
6. The start address of the device app, currently `0x4000_0000`, is
|
||||
written to `APP_ADDR` and the size of the binary to `APP_SIZE` to
|
||||
let the device application know where it is loaded and how large
|
||||
it is, if it wants to relocate in RAM.
|
||||
|
||||
2. Compute the Compound Device Identifier
|
||||
([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.
|
||||
7. The firmware now clears the part of the special `FW_RAM` where it
|
||||
keeps it stack.
|
||||
|
||||
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.
|
||||
8. The interrupt handler for system calls is enabled.
|
||||
|
||||
4. Clear the stack part of `FW_RAM`.
|
||||
9. Firmware starts the application by jumping to the contents of
|
||||
`APP_ADDR`. Hardware automatically switches from firmware mode to
|
||||
application mode. In this mode some memory access is restricted,
|
||||
e.g. some addresses are inaccessible (`UDS`), and some are
|
||||
switched from read/write to read-only (see [the memory
|
||||
map](https://dev.tillitis.se/memory/)).
|
||||
|
||||
5. Enable system call interrupt handler.
|
||||
|
||||
6. Start the application by jumping to the contents of `APP_ADDR`.
|
||||
Hardware automatically switch 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/)).
|
||||
|
||||
### 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).
|
||||
If during this whole time any commands are received which are not
|
||||
allowed in the current state, or any errors occur, we enter the
|
||||
"failed" state and execute an illegal instruction. An illegal
|
||||
instruction traps the CPU and hardware blinks the status LED red until
|
||||
a power cycle. No further instructions are executed.
|
||||
|
||||
### User-supplied Secret (USS)
|
||||
|
||||
@ -480,7 +277,7 @@ CDI = blake2s(UDS, blake2s(app), USS)
|
||||
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
|
||||
thing able to read the UDS. Unfortunately, we couldn't fit a BLAKE2s
|
||||
implementation in the FPGA.
|
||||
implementation in the FPGA at this time.
|
||||
|
||||
The firmware instead does the CDI computation using the special
|
||||
firmware-only `FW_RAM` which is invisible after switching to app mode.
|
||||
@ -498,7 +295,7 @@ Then we continue with the CDI computation by updating with an optional
|
||||
USS digest and finalizing the hash, storing the resulting digest in
|
||||
`CDI`.
|
||||
|
||||
### System calls
|
||||
### Firmware system calls
|
||||
|
||||
The firmware provides a system call mechanism through the use of the
|
||||
PicoRV32 interrupt handler. They are triggered by writing to the
|
||||
@ -506,11 +303,10 @@ trigger address: 0xe1000000. It's typically done with a function
|
||||
signature like this:
|
||||
|
||||
```
|
||||
int syscall(uint32_t number, uint32_t arg1, uint32_t arg2,
|
||||
uint32_t arg3);
|
||||
int syscall(uint32_t number, uint32_t arg1);
|
||||
```
|
||||
|
||||
Arguments are system call number and up to 6 generic arguments passed
|
||||
Arguments are system call number and upto 6 generic arguments passed
|
||||
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
|
||||
according to the RISC-V calling convention. The caller is responsible
|
||||
@ -520,160 +316,16 @@ The syscall handler returns execution on the next instruction after
|
||||
the store instruction to the trigger address. The return value from
|
||||
the syscall is now available in x10 (a0).
|
||||
|
||||
The syscall numbers are kept in `syscall_num.h`. The syscalls are
|
||||
handled in `syscall_handler()` in `syscall_handler.c`.
|
||||
To add or change syscalls, see the `syscall_handler()` in
|
||||
`syscall_handler.c`.
|
||||
|
||||
#### `RESET`
|
||||
Currently supported syscalls:
|
||||
|
||||
```
|
||||
struct reset {
|
||||
uint32_t type; // Reset type
|
||||
uint8_t app_digest[32]; // Digest of next app in chain to verify
|
||||
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.
|
||||
| *Name* | *Number* | *Argument* | *Description* |
|
||||
|-------------|----------|------------|----------------------------------|
|
||||
| RESET | 1 | Unused | Reset the TKey |
|
||||
| SET\_LED | 10 | Colour | Set the colour of the status LED |
|
||||
| GET\_VIDPID | 12 | Unused | Get Vendor and Product ID |
|
||||
|
||||
## Developing firmware
|
||||
|
||||
@ -694,10 +346,7 @@ you might need to `make clean` before building, if you have already
|
||||
built before.
|
||||
|
||||
If you want debug prints to show up on the special TKey HID 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.
|
||||
endpoint instead, define `-DTKEY_DEBUG`.
|
||||
|
||||
Note that if you use `TKEY_DEBUG` you *must* have something listening
|
||||
on the corresponding HID device. It's usually the last HID device
|
||||
@ -714,45 +363,6 @@ 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
|
||||
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
|
||||
|
||||
The test firmware is in `testfw`. It's currently a bit of a hack and
|
||||
@ -764,23 +374,5 @@ 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.
|
||||
|
||||
It needs to be compiled with `-Os` instead of `-O2` in `CFLAGS` in the
|
||||
ordinary `application_fpga/Makefile` to be able to fit in 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.
|
||||
ordinary `application_fpga/Makefile` to be able to fit in the 6 kByte
|
||||
ROM.
|
||||
|
@ -1,75 +0,0 @@
|
||||
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)
|
@ -1,64 +0,0 @@
|
||||
/*
|
||||
* 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 */
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
// 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
|
@ -1,161 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
// 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,7 +50,9 @@ all: testapp.bin
|
||||
tkey-libs:
|
||||
make -C $(LIBDIR)
|
||||
|
||||
TESTAPP_FMTFILES = *.[ch]
|
||||
TESTAPP_FMTFILES = \
|
||||
$(P)/main.c \
|
||||
$(P)/syscall.h
|
||||
|
||||
TESTAPP_OBJS = \
|
||||
$(P)/main.o \
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include <tkey/tk1_mem.h>
|
||||
|
||||
#include "../tk1/proto.h"
|
||||
#include "../tk1/resetinfo.h"
|
||||
#include "../tk1/syscall_num.h"
|
||||
#include "syscall.h"
|
||||
|
||||
@ -122,49 +121,13 @@ int main(void)
|
||||
}
|
||||
|
||||
// But a syscall to get parts of UDI should be able to run
|
||||
int vidpid = syscall(TK1_SYSCALL_GET_VIDPID, 0, 0, 0);
|
||||
int vidpid = syscall(TK1_SYSCALL_GET_VIDPID, 0);
|
||||
|
||||
if (vidpid != 0x00010203) {
|
||||
failmsg("Expected VID/PID to be 0x00010203");
|
||||
anyfailed = 1;
|
||||
}
|
||||
|
||||
puts(IO_CDC, "\r\nAllocating storage area...");
|
||||
|
||||
if (syscall(TK1_SYSCALL_ALLOC_AREA, 0, 0, 0) != 0) {
|
||||
failmsg("Failed to allocate storage area");
|
||||
}
|
||||
puts(IO_CDC, "done.\r\n");
|
||||
|
||||
puts(IO_CDC, "\r\nWriting to storage area...");
|
||||
|
||||
uint8_t out_data[14] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
|
||||
if (syscall(TK1_SYSCALL_WRITE_DATA, 0, (uint32_t)out_data,
|
||||
sizeof(out_data)) != 0) {
|
||||
failmsg("Failed to write to storage area");
|
||||
}
|
||||
puts(IO_CDC, "done.\r\n");
|
||||
|
||||
puts(IO_CDC, "\r\nReading from storage area...");
|
||||
|
||||
uint8_t in_data[14] = {0};
|
||||
if (syscall(TK1_SYSCALL_READ_DATA, 0, (uint32_t)in_data,
|
||||
sizeof(in_data)) != 0) {
|
||||
failmsg("Failed to write to storage area");
|
||||
}
|
||||
if (!memeq(in_data, out_data, sizeof(in_data))) {
|
||||
failmsg("Failed to read back data from storage area");
|
||||
anyfailed = 1;
|
||||
}
|
||||
puts(IO_CDC, "done.\r\n");
|
||||
|
||||
puts(IO_CDC, "\r\nDeallocating storage area...");
|
||||
|
||||
if (syscall(TK1_SYSCALL_DEALLOC_AREA, 0, 0, 0) != 0) {
|
||||
failmsg("Failed to deallocate storage area");
|
||||
}
|
||||
puts(IO_CDC, "done.\r\n");
|
||||
|
||||
uint32_t cdi_local[CDI_WORDS];
|
||||
uint32_t cdi_local2[CDI_WORDS];
|
||||
wordcpy_s(cdi_local, CDI_WORDS, (void *)cdi, CDI_WORDS);
|
||||
@ -260,10 +223,7 @@ int main(void)
|
||||
}
|
||||
|
||||
if (in == '+') {
|
||||
struct reset rst;
|
||||
memset(&rst, 0, sizeof(rst));
|
||||
rst.type = START_DEFAULT;
|
||||
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
|
||||
syscall(TK1_SYSCALL_RESET, 0);
|
||||
}
|
||||
|
||||
write(IO_CDC, &in, 1);
|
||||
|
@ -6,6 +6,6 @@
|
||||
#ifndef TKEY_APP_SYSCALL_H
|
||||
#define TKEY_APP_SYSCALL_H
|
||||
|
||||
int syscall(uint32_t number, uint32_t arg1, uint32_t arg2, uint32_t arg3);
|
||||
int syscall(uint32_t number, uint32_t arg1);
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Uses ../.clang-format
|
||||
FMTFILES=*.[ch]
|
||||
FMTFILES=main.c
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
clang-format --dry-run --ferror-limit=0 $(FMTFILES)
|
||||
|
@ -1,73 +0,0 @@
|
||||
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)
|
@ -1,28 +0,0 @@
|
||||
#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
|
@ -1,218 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// 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
|
116
hw/application_fpga/fw/tk1/blake2s/LICENSE
Normal file
116
hw/application_fpga/fw/tk1/blake2s/LICENSE
Normal file
@ -0,0 +1,116 @@
|
||||
CC0 1.0 Universal
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||
subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||
purpose of contributing to a commons of creative, cultural and scientific
|
||||
works ("Commons") that the public can reliably and without fear of later
|
||||
claims of infringement build upon, modify, incorporate in other works, reuse
|
||||
and redistribute as freely as possible in any form whatsoever and for any
|
||||
purposes, including without limitation commercial purposes. These owners may
|
||||
contribute to the Commons to promote the ideal of a free culture and the
|
||||
further production of creative, cultural and scientific works, or to gain
|
||||
reputation or greater distribution for their Work in part through the use and
|
||||
efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation
|
||||
of additional consideration or compensation, the person associating CC0 with a
|
||||
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
||||
and publicly distribute the Work under its terms, with knowledge of his or her
|
||||
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||
effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not limited
|
||||
to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
||||
and translate a Work;
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||
depicted in a Work;
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data in
|
||||
a Work;
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation thereof,
|
||||
including any amended or successor version of such directive); and
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world
|
||||
based on applicable law or treaty, and any national implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||
and Related Rights and associated claims and causes of action, whether now
|
||||
known or unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||
duration provided by applicable law or treaty (including future time
|
||||
extensions), (iii) in any current or future medium and for any number of
|
||||
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
|
||||
the Waiver for the benefit of each member of the public at large and to the
|
||||
detriment of Affirmer's heirs and successors, fully intending that such Waiver
|
||||
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||
by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||
shall be preserved to the maximum extent permitted taking into account
|
||||
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
|
||||
is so judged Affirmer hereby grants to each affected person a royalty-free,
|
||||
non transferable, non sublicensable, non exclusive, irrevocable and
|
||||
unconditional license to exercise Affirmer's Copyright and Related Rights in
|
||||
the Work (i) in all territories worldwide, (ii) for the maximum duration
|
||||
provided by applicable law or treaty (including future time extensions), (iii)
|
||||
in any current or future medium and for any number of copies, and (iv) for any
|
||||
purpose whatsoever, including without limitation commercial, advertising or
|
||||
promotional purposes (the "License"). The License shall be deemed effective as
|
||||
of the date CC0 was applied by Affirmer to the Work. Should any part of the
|
||||
License for any reason be judged legally invalid or ineffective under
|
||||
applicable law, such partial invalidity or ineffectiveness shall not
|
||||
invalidate the remainder of the License, and in such case Affirmer hereby
|
||||
affirms that he or she will not (i) exercise any of his or her remaining
|
||||
Copyright and Related Rights in the Work or (ii) assert any associated claims
|
||||
and causes of action with respect to the Work, in either case contrary to
|
||||
Affirmer's express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or warranties
|
||||
of any kind concerning the Work, express, implied, statutory or otherwise,
|
||||
including without limitation warranties of title, merchantability, fitness
|
||||
for a particular purpose, non infringement, or the absence of latent or
|
||||
other defects, accuracy, or the present or absence of errors, whether or not
|
||||
discoverable, all to the greatest extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without limitation
|
||||
any person's Copyright and Related Rights in the Work. Further, Affirmer
|
||||
disclaims responsibility for obtaining any necessary consents, permissions
|
||||
or other rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to this
|
||||
CC0 or use of the Work.
|
||||
|
||||
For more information, please see
|
||||
<http://creativecommons.org/publicdomain/zero/1.0/>
|
11
hw/application_fpga/fw/tk1/blake2s/README.md
Normal file
11
hw/application_fpga/fw/tk1/blake2s/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# 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.
|
351
hw/application_fpga/fw/tk1/blake2s/blake2s.c
Normal file
351
hw/application_fpga/fw/tk1/blake2s/blake2s.c
Normal file
@ -0,0 +1,351 @@
|
||||
//======================================================================
|
||||
//
|
||||
// blake2s.c
|
||||
// ---------
|
||||
//
|
||||
// A simple blake2s Reference Implementation.
|
||||
//======================================================================
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "blake2s.h"
|
||||
|
||||
// Dummy printf() for verbose mode
|
||||
static void printf(const char *format, ...)
|
||||
{
|
||||
}
|
||||
|
||||
#define VERBOSE 0
|
||||
#define SHOW_V 0
|
||||
#define SHOW_M_WORDS 0
|
||||
|
||||
|
||||
// Cyclic right rotation.
|
||||
#ifndef ROTR32
|
||||
#define ROTR32(x, y) (((x) >> (y)) ^ ((x) << (32 - (y))))
|
||||
#endif
|
||||
|
||||
|
||||
// Little-endian byte access.
|
||||
#define B2S_GET32(p) \
|
||||
(((uint32_t) ((uint8_t *) (p))[0]) ^ \
|
||||
(((uint32_t) ((uint8_t *) (p))[1]) << 8) ^ \
|
||||
(((uint32_t) ((uint8_t *) (p))[2]) << 16) ^ \
|
||||
(((uint32_t) ((uint8_t *) (p))[3]) << 24))
|
||||
|
||||
|
||||
// Initialization Vector.
|
||||
static const uint32_t blake2s_iv[8] = {
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------
|
||||
//------------------------------------------------------------------
|
||||
void print_v(uint32_t *v) {
|
||||
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x\n", v[0], v[1], v[2], v[3]);
|
||||
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x\n", v[4], v[5], v[6], v[7]);
|
||||
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x\n", v[8], v[9], v[10], v[11]);
|
||||
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x\n", v[12], v[13], v[14], v[15]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// print_ctx()
|
||||
// Print the contents of the context data structure.
|
||||
//------------------------------------------------------------------
|
||||
void print_ctx(blake2s_ctx *ctx) {
|
||||
printf("Chained state (h):\n");
|
||||
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x, ",
|
||||
ctx->h[0], ctx->h[1], ctx->h[2], ctx->h[3]);
|
||||
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x",
|
||||
ctx->h[4], ctx->h[5], ctx->h[6], ctx->h[7]);
|
||||
printf("\n");
|
||||
|
||||
printf("Byte counter (t):\n");
|
||||
printf("0x%08x, 0x%08x", ctx->t[0], ctx->t[1]);
|
||||
printf("\n");
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// B2S_G macro redefined as a G function.
|
||||
// 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) {
|
||||
if (VERBOSE) {
|
||||
printf("G started.\n");
|
||||
}
|
||||
|
||||
if (SHOW_V) {
|
||||
printf("v before processing:\n");
|
||||
print_v(&v[0]);
|
||||
}
|
||||
|
||||
if (SHOW_M_WORDS) {
|
||||
printf("x: 0x%08x, y: 0x%08x\n", x, y);
|
||||
}
|
||||
|
||||
v[a] = v[a] + v[b] + x;
|
||||
v[d] = ROTR32(v[d] ^ v[a], 16);
|
||||
v[c] = v[c] + v[d];
|
||||
v[b] = ROTR32(v[b] ^ v[c], 12);
|
||||
v[a] = v[a] + v[b] + y;
|
||||
v[d] = ROTR32(v[d] ^ v[a], 8);
|
||||
v[c] = v[c] + v[d];
|
||||
v[b] = ROTR32(v[b] ^ v[c], 7);
|
||||
|
||||
if (SHOW_V) {
|
||||
printf("v after processing:\n");
|
||||
print_v(&v[0]);
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("G completed.\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Compression function. "last" flag indicates last block.
|
||||
//------------------------------------------------------------------
|
||||
static void blake2s_compress(blake2s_ctx *ctx, int last)
|
||||
{
|
||||
const uint8_t sigma[10][16] = {
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
|
||||
{11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
|
||||
{7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
|
||||
{9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
|
||||
{2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
|
||||
{12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
|
||||
{13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
|
||||
{6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
|
||||
{10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}
|
||||
};
|
||||
|
||||
int i;
|
||||
uint32_t v[16], m[16];
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("blake2s_compress started.\n");
|
||||
}
|
||||
|
||||
// init work variables
|
||||
for (i = 0; i < 8; i++) {
|
||||
v[i] = ctx->h[i];
|
||||
v[i + 8] = blake2s_iv[i];
|
||||
}
|
||||
|
||||
// low 32 bits of offset
|
||||
// high 32 bits
|
||||
if (VERBOSE) {
|
||||
printf("t[0]: 0x%08x, t[1]: 0x%08x\n", ctx->t[0], ctx->t[1]);
|
||||
}
|
||||
v[12] ^= ctx->t[0];
|
||||
v[13] ^= ctx->t[1];
|
||||
|
||||
// last block flag set ?
|
||||
if (last) {
|
||||
v[14] = ~v[14];
|
||||
}
|
||||
|
||||
// get little-endian words
|
||||
for (i = 0; i < 16; i++) {
|
||||
m[i] = B2S_GET32(&ctx->b[4 * i]);
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("v before G processing:\n");
|
||||
print_v(&v[0]);
|
||||
}
|
||||
|
||||
// Ten rounds of the G function applied on rows, diagonal.
|
||||
for (i = 0; i < 10; i++) {
|
||||
if (VERBOSE) {
|
||||
printf("Round %02d:\n", (i + 1));
|
||||
printf("Row processing started.\n");
|
||||
}
|
||||
|
||||
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], 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]]);
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("Row processing completed.\n");
|
||||
printf("Diagonal processing started.\n");
|
||||
}
|
||||
|
||||
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], 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]]);
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("Diagonal processing completed.\n");
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("v after G processing:\n");
|
||||
print_v(&v[0]);
|
||||
}
|
||||
|
||||
// Update the hash state.
|
||||
for (i = 0; i < 8; ++i) {
|
||||
ctx->h[i] ^= v[i] ^ v[i + 8];
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("blake2s_compress completed.\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Initialize the hashing context "ctx" with optional key "key".
|
||||
// 1 <= outlen <= 32 gives the digest size in bytes.
|
||||
// Secret key (also <= 32 bytes) is optional (keylen = 0).
|
||||
//------------------------------------------------------------------
|
||||
int blake2s_init(blake2s_ctx *ctx, size_t outlen,
|
||||
const void *key, size_t keylen) // (keylen=0: no key)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("blake2s_init started.\n");
|
||||
printf("Context before blake2s_init processing:\n");
|
||||
print_ctx(ctx);
|
||||
}
|
||||
|
||||
if (outlen == 0 || outlen > 32 || keylen > 32)
|
||||
return -1; // illegal parameters
|
||||
|
||||
for (i = 0; i < 8; i++) // state, "param block"
|
||||
ctx->h[i] = blake2s_iv[i];
|
||||
ctx->h[0] ^= 0x01010000 ^ (keylen << 8) ^ outlen;
|
||||
|
||||
ctx->t[0] = 0; // input count low word
|
||||
ctx->t[1] = 0; // input count high word
|
||||
ctx->c = 0; // pointer within buffer
|
||||
ctx->outlen = outlen;
|
||||
|
||||
for (i = keylen; i < 64; i++) // zero input block
|
||||
ctx->b[i] = 0;
|
||||
if (keylen > 0) {
|
||||
blake2s_update(ctx, key, keylen);
|
||||
ctx->c = 64; // at the end
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("Context after blake2s_init processing:\n");
|
||||
print_ctx(ctx);
|
||||
printf("blake2s_init completed.\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Add "inlen" bytes from "in" into the hash.
|
||||
//------------------------------------------------------------------
|
||||
void blake2s_update(blake2s_ctx *ctx,
|
||||
const void *in, size_t inlen) // data bytes
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("blake2s_update started.\n");
|
||||
printf("Context before blake2s_update processing:\n");
|
||||
print_ctx(ctx);
|
||||
}
|
||||
|
||||
for (i = 0; i < inlen; i++) {
|
||||
if (ctx->c == 64) { // buffer full ?
|
||||
ctx->t[0] += ctx->c; // add counters
|
||||
if (ctx->t[0] < ctx->c) // carry overflow ?
|
||||
ctx->t[1]++; // high word
|
||||
blake2s_compress(ctx, 0); // compress (not last)
|
||||
ctx->c = 0; // counter to zero
|
||||
}
|
||||
ctx->b[ctx->c++] = ((const uint8_t *) in)[i];
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("Context after blake2s_update processing:\n");
|
||||
print_ctx(ctx);
|
||||
printf("blake2s_update completed.\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Generate the message digest (size given in init).
|
||||
// Result placed in "out".
|
||||
//------------------------------------------------------------------
|
||||
void blake2s_final(blake2s_ctx *ctx, void *out)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("blake2s_final started.\n");
|
||||
printf("Context before blake2s_final processing:\n");
|
||||
print_ctx(ctx);
|
||||
}
|
||||
|
||||
ctx->t[0] += ctx->c; // mark last block offset
|
||||
|
||||
// carry overflow
|
||||
// high word
|
||||
if (ctx->t[0] < ctx->c) {
|
||||
ctx->t[1]++;
|
||||
}
|
||||
|
||||
// fill up with zeros
|
||||
// final block flag = 1
|
||||
while (ctx->c < 64) {
|
||||
ctx->b[ctx->c++] = 0;
|
||||
}
|
||||
blake2s_compress(ctx, 1);
|
||||
|
||||
// little endian convert and store
|
||||
for (i = 0; i < ctx->outlen; i++) {
|
||||
((uint8_t *) out)[i] =
|
||||
(ctx->h[i >> 2] >> (8 * (i & 3))) & 0xFF;
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
printf("Context after blake2s_final processing:\n");
|
||||
print_ctx(ctx);
|
||||
printf("blake2s_final completed.\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Convenience function for all-in-one computation.
|
||||
//------------------------------------------------------------------
|
||||
int blake2s(void *out, size_t outlen,
|
||||
const void *key, size_t keylen,
|
||||
const void *in, size_t inlen,
|
||||
blake2s_ctx *ctx)
|
||||
{
|
||||
if (blake2s_init(ctx, outlen, key, keylen))
|
||||
return -1;
|
||||
|
||||
blake2s_update(ctx, in, inlen);
|
||||
|
||||
blake2s_final(ctx, out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
//======================================================================
|
40
hw/application_fpga/fw/tk1/blake2s/blake2s.h
Normal file
40
hw/application_fpga/fw/tk1/blake2s/blake2s.h
Normal file
@ -0,0 +1,40 @@
|
||||
// blake2s.h
|
||||
// BLAKE2s Hashing Context and API Prototypes
|
||||
|
||||
#ifndef BLAKE2S_H
|
||||
#define BLAKE2S_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// 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;
|
||||
|
||||
// Initialize the hashing context "ctx" with optional key "key".
|
||||
// 1 <= outlen <= 32 gives the digest size in bytes.
|
||||
// Secret key (also <= 32 bytes) is optional (keylen = 0).
|
||||
int blake2s_init(blake2s_ctx *ctx, size_t outlen,
|
||||
const void *key, size_t keylen); // secret key
|
||||
|
||||
// Add "inlen" bytes from "in" into the hash.
|
||||
void blake2s_update(blake2s_ctx *ctx, // context
|
||||
const void *in, size_t inlen); // data to be hashed
|
||||
|
||||
// Generate the message digest (size given in init).
|
||||
// Result placed in "out".
|
||||
void blake2s_final(blake2s_ctx *ctx, void *out);
|
||||
|
||||
// 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
|
||||
blake2s_ctx *ctx);
|
||||
|
||||
#endif
|
||||
|
@ -7,7 +7,7 @@ OUTPUT_ARCH("riscv")
|
||||
ENTRY(_start)
|
||||
|
||||
/* Define stack size */
|
||||
STACK_SIZE = 3000;
|
||||
STACK_SIZE = 0xEF0; /* 3824 B */
|
||||
|
||||
MEMORY
|
||||
{
|
||||
|
@ -1,244 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
// 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,21 +3,16 @@
|
||||
* SPDX-License-Identifier: GPL-2.0-only
|
||||
*/
|
||||
|
||||
#include <blake2s/blake2s.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <tkey/assert.h>
|
||||
#include <tkey/debug.h>
|
||||
#include <tkey/led.h>
|
||||
#include <tkey/lib.h>
|
||||
#include <tkey/tk1_mem.h>
|
||||
|
||||
#include "mgmt_app.h"
|
||||
#include "partition_table.h"
|
||||
#include "preload_app.h"
|
||||
#include "blake2s/blake2s.h"
|
||||
#include "proto.h"
|
||||
#include "resetinfo.h"
|
||||
#include "state.h"
|
||||
#include "syscall_enable.h"
|
||||
|
||||
@ -38,11 +33,8 @@ static volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER
|
||||
static volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL;
|
||||
static volatile uint32_t *ram_addr_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_ADDR_RAND;
|
||||
static volatile uint32_t *ram_data_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_DATA_RAND;
|
||||
static volatile struct reset *resetinfo = (volatile struct reset *)TK1_MMIO_RESETINFO_BASE;
|
||||
// clang-format on
|
||||
|
||||
struct partition_table_storage part_table_storage;
|
||||
|
||||
// Context for the loading of a TKey program
|
||||
struct context {
|
||||
uint32_t left; // Bytes left to receive
|
||||
@ -50,9 +42,6 @@ struct context {
|
||||
uint8_t *loadaddr; // Where we are currently loading a TKey program
|
||||
bool use_uss; // Use USS?
|
||||
uint8_t uss[32]; // User Supplied Secret, if any
|
||||
uint8_t flash_slot; // App is loaded from flash slot number
|
||||
/*@null@*/ volatile uint8_t
|
||||
*ver_digest; // Verify loaded app against this digest
|
||||
};
|
||||
|
||||
static void print_hw_version(void);
|
||||
@ -67,14 +56,11 @@ static enum state initial_commands(const struct frame_header *hdr,
|
||||
static enum state loading_commands(const struct frame_header *hdr,
|
||||
const uint8_t *cmd, enum state state,
|
||||
struct context *ctx);
|
||||
static void run(const struct context *ctx);
|
||||
#if !defined(SIMULATION)
|
||||
static uint32_t xorwow(uint32_t state, uint32_t acc);
|
||||
#endif
|
||||
static void scramble_ram(void);
|
||||
static int compute_app_digest(uint8_t *digest);
|
||||
static int load_flash_app(struct partition_table *part_table,
|
||||
uint8_t digest[32], uint8_t slot);
|
||||
static enum state start_where(struct context *ctx);
|
||||
|
||||
static void print_hw_version(void)
|
||||
{
|
||||
@ -94,7 +80,6 @@ static void print_digest(uint8_t *md)
|
||||
for (int j = 0; j < 4; j++) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
debug_puthex(md[i + 8 * j]);
|
||||
(void)md;
|
||||
}
|
||||
debug_lf();
|
||||
}
|
||||
@ -159,7 +144,6 @@ 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)
|
||||
{
|
||||
assert(bufsiz >= 4);
|
||||
assert(buf != NULL);
|
||||
|
||||
buf[0] = word >> 24;
|
||||
buf[1] = word >> 16;
|
||||
@ -295,6 +279,7 @@ static enum state loading_commands(const struct frame_header *hdr,
|
||||
ctx->left -= nbytes;
|
||||
|
||||
if (ctx->left == 0) {
|
||||
blake2s_ctx b2s_ctx = {0};
|
||||
int blake2err = 0;
|
||||
|
||||
debug_puts("Fully loaded ");
|
||||
@ -303,7 +288,9 @@ static enum state loading_commands(const struct frame_header *hdr,
|
||||
|
||||
// Compute Blake2S digest of the app,
|
||||
// storing it for FW_STATE_RUN
|
||||
blake2err = compute_app_digest(ctx->digest);
|
||||
blake2err = blake2s(&ctx->digest, 32, NULL, 0,
|
||||
(const void *)TK1_RAM_BASE,
|
||||
*app_size, &b2s_ctx);
|
||||
assert(blake2err == 0);
|
||||
print_digest(ctx->digest);
|
||||
|
||||
@ -313,7 +300,7 @@ static enum state loading_commands(const struct frame_header *hdr,
|
||||
memcpy_s(&rsp[1], CMDSIZE - 1, &ctx->digest, 32);
|
||||
fwreply(*hdr, FW_RSP_LOAD_APP_DATA_READY, rsp);
|
||||
|
||||
state = FW_STATE_START;
|
||||
state = FW_STATE_RUN;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -333,11 +320,13 @@ static enum state loading_commands(const struct frame_header *hdr,
|
||||
return state;
|
||||
}
|
||||
|
||||
static void jump_to_app(void)
|
||||
static void run(const struct context *ctx)
|
||||
{
|
||||
/* Start of app is always at the beginning of RAM */
|
||||
*app_addr = TK1_RAM_BASE;
|
||||
|
||||
// CDI = hash(uds, hash(app), uss)
|
||||
compute_cdi(ctx->digest, ctx->use_uss, ctx->uss);
|
||||
|
||||
debug_puts("Flipping to app mode!\n");
|
||||
debug_puts("Jumping to ");
|
||||
debug_putinthex(*app_addr);
|
||||
@ -376,29 +365,6 @@ static void jump_to_app(void)
|
||||
__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)
|
||||
static uint32_t xorwow(uint32_t state, uint32_t acc)
|
||||
{
|
||||
@ -433,62 +399,6 @@ static void scramble_ram(void)
|
||||
*ram_data_rand = rnd_word();
|
||||
}
|
||||
|
||||
/* Computes the blake2s digest of the app loaded into RAM */
|
||||
static int compute_app_digest(uint8_t *digest)
|
||||
{
|
||||
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)
|
||||
{
|
||||
struct context ctx = {0};
|
||||
@ -496,8 +406,6 @@ int main(void)
|
||||
uint8_t cmd[CMDSIZE] = {0};
|
||||
enum state state = FW_STATE_INITIAL;
|
||||
|
||||
led_set(LED_BLUE);
|
||||
|
||||
print_hw_version();
|
||||
|
||||
/*@-mustfreeonly@*/
|
||||
@ -510,11 +418,6 @@ int main(void)
|
||||
|
||||
scramble_ram();
|
||||
|
||||
if (part_table_read(&part_table_storage) != 0) {
|
||||
// Couldn't read or create partition table
|
||||
assert(1 == 2);
|
||||
}
|
||||
|
||||
#if defined(SIMULATION)
|
||||
run(&ctx);
|
||||
#endif
|
||||
@ -522,10 +425,6 @@ int main(void)
|
||||
for (;;) {
|
||||
switch (state) {
|
||||
case FW_STATE_INITIAL:
|
||||
state = start_where(&ctx);
|
||||
break;
|
||||
|
||||
case FW_STATE_WAITCOMMAND:
|
||||
if (readcommand(&hdr, cmd, state) == -1) {
|
||||
state = FW_STATE_FAIL;
|
||||
break;
|
||||
@ -546,51 +445,9 @@ int main(void)
|
||||
state = loading_commands(&hdr, cmd, state, &ctx);
|
||||
break;
|
||||
|
||||
case FW_STATE_LOAD_FLASH:
|
||||
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;
|
||||
}
|
||||
|
||||
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_RUN:
|
||||
run(&ctx);
|
||||
break; // This is never reached!
|
||||
|
||||
case FW_STATE_FAIL:
|
||||
// fallthrough
|
||||
@ -605,6 +462,5 @@ int main(void)
|
||||
|
||||
/*@ -compdestroy @*/
|
||||
/* We don't care about memory leaks here. */
|
||||
|
||||
return (int)0xcafebabe;
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// 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
|
@ -1,106 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
// 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
|
@ -1,198 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
// 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
|
@ -1,28 +0,0 @@
|
||||
// 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
|
@ -1,29 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
// 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
|
@ -1,100 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
// 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,19 +4,8 @@
|
||||
*/
|
||||
|
||||
#include <tkey/tk1_mem.h>
|
||||
|
||||
#ifdef QEMU_SYSCALL
|
||||
|
||||
#define picorv32_retirq_insn(...) \
|
||||
mv ra, x3; \
|
||||
ret
|
||||
|
||||
#else
|
||||
|
||||
#include "picorv32/custom_ops.S" // PicoRV32 custom instructions
|
||||
|
||||
#endif
|
||||
|
||||
#define illegal_insn() .word 0
|
||||
|
||||
// Variables in bss
|
||||
|
@ -8,11 +8,8 @@
|
||||
|
||||
enum state {
|
||||
FW_STATE_INITIAL,
|
||||
FW_STATE_WAITCOMMAND,
|
||||
FW_STATE_LOADING,
|
||||
FW_STATE_LOAD_FLASH,
|
||||
FW_STATE_LOAD_FLASH_MGMT,
|
||||
FW_STATE_START,
|
||||
FW_STATE_RUN,
|
||||
FW_STATE_FAIL,
|
||||
FW_STATE_MAX,
|
||||
};
|
||||
|
@ -1,278 +0,0 @@
|
||||
// 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);
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
// 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,13 +1,5 @@
|
||||
#ifdef QEMU_SYSCALL
|
||||
|
||||
#define picorv32_maskirq_insn(...)
|
||||
|
||||
#else
|
||||
|
||||
#include "../tk1/picorv32/custom_ops.S"
|
||||
|
||||
#endif
|
||||
|
||||
.section ".text"
|
||||
.globl syscall_enable
|
||||
|
||||
|
@ -5,112 +5,29 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <tkey/assert.h>
|
||||
#include <tkey/debug.h>
|
||||
#include <tkey/lib.h>
|
||||
#include <tkey/tk1_mem.h>
|
||||
#include <tkey/led.h>
|
||||
|
||||
#include "partition_table.h"
|
||||
#include "preload_app.h"
|
||||
#include "storage.h"
|
||||
|
||||
#include "../tk1/resetinfo.h"
|
||||
#include "../tk1/syscall_num.h"
|
||||
|
||||
// clang-format off
|
||||
static volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_RESET;
|
||||
static volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST;
|
||||
static volatile struct reset *resetinfo = (volatile struct reset *)TK1_MMIO_RESETINFO_BASE;
|
||||
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;
|
||||
// clang-format on
|
||||
|
||||
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)
|
||||
int32_t syscall_handler(uint32_t number, uint32_t arg1)
|
||||
{
|
||||
struct reset *userreset = (struct reset *)arg1;
|
||||
|
||||
switch (number) {
|
||||
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;
|
||||
|
||||
// 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;
|
||||
case TK1_SYSCALL_DEALLOC_AREA:
|
||||
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;
|
||||
}
|
||||
|
||||
case TK1_SYSCALL_SET_LED:
|
||||
led_set(arg1);
|
||||
return 0;
|
||||
case TK1_SYSCALL_GET_VIDPID:
|
||||
// UDI is 2 words: VID/PID & serial. Return just the
|
||||
// first word. Serial is kept secret to the device
|
||||
// app.
|
||||
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:
|
||||
assert(1 == 2);
|
||||
}
|
||||
|
@ -6,18 +6,8 @@
|
||||
|
||||
enum syscall_num {
|
||||
TK1_SYSCALL_RESET = 1,
|
||||
TK1_SYSCALL_ALLOC_AREA = 2,
|
||||
TK1_SYSCALL_DEALLOC_AREA = 3,
|
||||
TK1_SYSCALL_WRITE_DATA = 4,
|
||||
TK1_SYSCALL_READ_DATA = 5,
|
||||
TK1_SYSCALL_ERASE_DATA = 6,
|
||||
TK1_SYSCALL_GET_VIDPID = 7,
|
||||
TK1_SYSCALL_PRELOAD_STORE = 8,
|
||||
TK1_SYSCALL_PRELOAD_STORE_FIN = 9,
|
||||
TK1_SYSCALL_PRELOAD_DELETE = 10,
|
||||
TK1_SYSCALL_PRELOAD_GET_DIGSIG = 11,
|
||||
TK1_SYSCALL_REG_MGMT = 12,
|
||||
TK1_SYSCALL_STATUS = 13,
|
||||
TK1_SYSCALL_SET_LED = 10,
|
||||
TK1_SYSCALL_GET_VIDPID = 12,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -17,7 +17,7 @@ INCLUDE=include
|
||||
# either of them. You don't need to recompile tkey-libs.
|
||||
|
||||
CFLAGS = -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 \
|
||||
-mcmodel=medany -static -std=gnu99 -Os -ffast-math -fno-common \
|
||||
-mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common \
|
||||
-fno-builtin-printf -fno-builtin-putchar -nostdlib -mno-relax -flto \
|
||||
-Wall -Werror=implicit-function-declaration \
|
||||
-I $(INCLUDE) -I .
|
||||
|
@ -32,6 +32,7 @@ The Castor TKey hardware supports more USB endpoints:
|
||||
|
||||
- CDC - the same thing as older versions.
|
||||
- FIDO security token, for FIDO-like apps.
|
||||
- CCID, smart card interface.
|
||||
- DEBUG, a HID debug port.
|
||||
|
||||
The communication is still over a single UART. To differ between the
|
||||
|
@ -10,20 +10,22 @@
|
||||
// I/O endpoints. Keep it as bits possible to use in a bitmask in
|
||||
// readselect().
|
||||
//
|
||||
// Note that the DEBUG, CDC, and FIDO should be kept the same on
|
||||
// the CH552 side.
|
||||
// Note that the values for IO_CH552, IO_CDC, IO_FIDO, IO_CCID and IO_DEBUG
|
||||
// should be kept the same in the code for the CH552 side.
|
||||
enum ioend {
|
||||
IO_NONE = 0x00, // No endpoint
|
||||
IO_UART = 0x01, // Only destination, raw UART access
|
||||
IO_QEMU = 0x02, // Only destination, QEMU debug port
|
||||
IO_CH552 = 0x10, // Internal CH552 control port
|
||||
IO_DEBUG = 0x20, // HID debug port
|
||||
IO_CDC = 0x40, // CDC "serial port"
|
||||
IO_FIDO = 0x80, // FIDO security token port
|
||||
IO_CH552 = 0x04, // Internal CH552 control port
|
||||
IO_CDC = 0x08, // CDC "serial" port
|
||||
IO_FIDO = 0x10, // FIDO security token port
|
||||
IO_CCID = 0x20, // CCID "smart card" port
|
||||
IO_DEBUG = 0x40, // Debug port over USB HID
|
||||
};
|
||||
|
||||
enum ch552cmd {
|
||||
SET_ENDPOINTS = 0x01, // Config USB endpoints on the CH552
|
||||
CH552_CMD_MAX,
|
||||
};
|
||||
|
||||
void write(enum ioend dest, const uint8_t *buf, size_t nbytes);
|
||||
|
@ -74,15 +74,20 @@ static void write_with_header(enum ioend dest, const uint8_t *buf,
|
||||
// write blockingly writes nbytes bytes of data from buf to dest which
|
||||
// is either:
|
||||
//
|
||||
// - IO_UART: Low-level UART access, no USB Mode Header added.
|
||||
//
|
||||
// - IO_QEMU: QEMU debug port
|
||||
//
|
||||
// - IO_UART: Low-level UART access, no USB Mode Header added.
|
||||
// - IO_CH552: Internal communication between the FPGA and the
|
||||
// CH552, with header.
|
||||
//
|
||||
// - IO_CDC: Through the UART for the CDC endpoint, with header.
|
||||
//
|
||||
// - IO_FIDO: Through the UART for the FIDO endpoint, with header.
|
||||
//
|
||||
// - IO_DEBUG: Through the UART for the DEBUG HID endpoint, with
|
||||
// - IO_CCID: Through the UART for the CCID endpoint, with header.
|
||||
//
|
||||
// - IO_DEBUG: Through the UART for the DEBUG endpoint (USB HID), with
|
||||
// header.
|
||||
void write(enum ioend dest, const uint8_t *buf, size_t nbytes)
|
||||
{
|
||||
@ -203,9 +208,11 @@ static int discard(size_t nbytes)
|
||||
//
|
||||
// Only endpoints available for read are:
|
||||
//
|
||||
// - IO_DEBUG
|
||||
// - IO_CH552
|
||||
// - IO_CDC
|
||||
// - IO_FIDO
|
||||
// - IO_CCID
|
||||
// - IO_DEBUG
|
||||
//
|
||||
// If you need blocking low-level UART reads, use uart_read() instead.
|
||||
//
|
||||
@ -352,7 +359,8 @@ void hexdump(enum ioend dest, void *buf, int len)
|
||||
// Configure USB endpoints that should be enabled/disabled
|
||||
//
|
||||
// Allowed options are:
|
||||
// - IO_FIDO
|
||||
// - IO_FIDO (can't be used used together with IO_CCID)
|
||||
// - IO_CCID (can't be used used together with IO_FIDO)
|
||||
// - IO_DEBUG
|
||||
//
|
||||
// The following are always enabled:
|
||||
|
@ -1,29 +0,0 @@
|
||||
# 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
|
||||
```
|
@ -1,59 +0,0 @@
|
||||
// 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)
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
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
|
@ -1,4 +0,0 @@
|
||||
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=
|
@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
|
||||
FLASH_SIZE_BYTES = 1 * 1024 * 1024
|
||||
PRELOADED_APP_0_START = 0x30000
|
||||
|
||||
|
||||
def run(output_path, preloaded_app_0_path):
|
||||
with (
|
||||
open(output_path, "wb") as output_file,
|
||||
open(preloaded_app_0_path, "rb") as preloaded_app_0_file,
|
||||
):
|
||||
content = bytearray(b"\xFF") * FLASH_SIZE_BYTES
|
||||
preloaded_app = preloaded_app_0_file.read()
|
||||
content[
|
||||
PRELOADED_APP_0_START : PRELOADED_APP_0_START + len(preloaded_app)
|
||||
] = preloaded_app
|
||||
output_file.write(content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
arg_parser = argparse.ArgumentParser()
|
||||
arg_parser.add_argument("OUTPUT_PATH")
|
||||
arg_parser.add_argument("PRELOADED_APP_0_PATH")
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
run(args.OUTPUT_PATH, args.PRELOADED_APP_0_PATH)
|
Binary file not shown.
@ -1,34 +0,0 @@
|
||||
#!/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"
|
@ -1,7 +0,0 @@
|
||||
module partition_table
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require golang.org/x/crypto v0.36.0
|
||||
|
||||
require golang.org/x/sys v0.31.0 // indirect
|
@ -1,4 +0,0 @@
|
||||
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=
|
@ -1,172 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -57,6 +57,7 @@ if __name__ == "__main__":
|
||||
"CdcCtrlInterfaceDesc": "CDC-Ctrl",
|
||||
"CdcDataInterfaceDesc": "CDC-Data",
|
||||
"FidoInterfaceDesc": "FIDO",
|
||||
"CcidInterfaceDesc": "CCID",
|
||||
"DebugInterfaceDesc": "DEBUG"
|
||||
}
|
||||
|
||||
|
@ -94,6 +94,13 @@ Header file for CH554 microcontrollers.
|
||||
#define USB_CDC_REQ_TYPE_SET_CONTROL_LINE_STATE 0x22
|
||||
#endif
|
||||
|
||||
/* USB CCID (Smart Card) class request code */
|
||||
#ifndef USB_CCID_REQ_TYPE
|
||||
#define USB_CCID_REQ_TYPE_ABORT 0x01
|
||||
#define USB_CCID_REQ_TYPE_GET_CLOCK_FREQUENCIES 0x02
|
||||
#define USB_CCID_REQ_TYPE_GET_DATA_RATES 0x03
|
||||
#endif
|
||||
|
||||
/* USB request type for hub class request */
|
||||
#ifndef HUB_GET_HUB_DESCRIPTOR
|
||||
#define HUB_CLEAR_HUB_FEATURE 0x20
|
||||
@ -200,7 +207,8 @@ Header file for CH554 microcontrollers.
|
||||
#define USB_IDX_INTERFACE_CDC_CTRL_STR 0x04
|
||||
#define USB_IDX_INTERFACE_CDC_DATA_STR 0x05
|
||||
#define USB_IDX_INTERFACE_FIDO_STR 0x06
|
||||
#define USB_IDX_INTERFACE_DEBUG_STR 0x07
|
||||
#define USB_IDX_INTERFACE_CCID_STR 0x07
|
||||
#define USB_IDX_INTERFACE_DEBUG_STR 0x08
|
||||
#endif
|
||||
|
||||
#ifndef USB_DEVICE_ADDR
|
||||
|
@ -8,14 +8,16 @@ enum ioend {
|
||||
IO_NONE = 0x00, // No endpoint
|
||||
IO_UART = 0x01, // Only destination, raw UART access
|
||||
IO_QEMU = 0x02, // Only destination, QEMU debug port
|
||||
IO_CH552 = 0x10, // Internal CH552 control port
|
||||
IO_DEBUG = 0x20, // HID debug port
|
||||
IO_CDC = 0x40, // CDC "serial port"
|
||||
IO_FIDO = 0x80, // FIDO security token port
|
||||
IO_CH552 = 0x04, // Internal CH552 control port
|
||||
IO_CDC = 0x08, // CDC "serial" port
|
||||
IO_FIDO = 0x10, // FIDO security token port
|
||||
IO_CCID = 0x20, // CCID "smart card" port
|
||||
IO_DEBUG = 0x40, // Debug port over USB HID
|
||||
};
|
||||
|
||||
enum ch552cmd {
|
||||
SET_ENDPOINTS = 0x01, // Config enabled/disabled USB endpoints on the CH552
|
||||
SET_ENDPOINTS = 0x01, // Config USB endpoints on the CH552
|
||||
CH552_CMD_MAX,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -55,6 +55,12 @@ unsigned char FLASH FidoInterfaceDesc[] = { // "FIDO"
|
||||
'F', 0, 'I', 0, 'D', 0, 'O', 0,
|
||||
};
|
||||
|
||||
unsigned char FLASH CcidInterfaceDesc[] = { // "CCID"
|
||||
10, // Length of this descriptor (in bytes)
|
||||
0x03, // Descriptor type (String)
|
||||
'C', 0, 'C', 0, 'I', 0, 'D', 0,
|
||||
};
|
||||
|
||||
unsigned char FLASH DebugInterfaceDesc[] = { // "DEBUG"
|
||||
12, // Length of this descriptor (in bytes)
|
||||
0x03, // Descriptor type (String)
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user