Compare commits

..

4 Commits

Author SHA1 Message Date
Mikael Ågren
353d7e9f50
tkey-libs: Import tag fw-4 of tkey-libs
- Use tag fw-4 from https://github.com/tillitis/tkey-libs/
2025-04-23 15:13:28 +02:00
Jonas Thörnblad
f75620720f
ch552: Clean up debugging of USB stack 2025-04-17 10:27:57 +02:00
Jonas Thörnblad
fb1269b06e
ch552: Fix various USB stack things, add check for IO_CH552 command range.
- Move handling of returning data from USB_GET_DESCRIPTOR request.
- Fix correct size of ActiveCfgDesc descriptor.
- Add missing check for limiting total length in USB_CDC_REQ_TYPE_GET_LINE_CODING.
- Add missing break in DeviceInterrupt to follow coding style.
- Check command range for IO_CH552.
- Add some comments.
2025-04-17 10:27:57 +02:00
Jonas Thörnblad
770acc9b38
ch552: Add CCID (Smart Card) support 2025-04-17 10:27:56 +02:00
66 changed files with 1299 additions and 3580 deletions

5
.gitignore vendored
View File

@ -28,12 +28,10 @@
/testbench_verilator* /testbench_verilator*
/check.smt2 /check.smt2
/check.vcd /check.vcd
/hw/application_fpga/tkey-libs/libblake2s.a
/hw/application_fpga/tkey-libs/libcommon.a /hw/application_fpga/tkey-libs/libcommon.a
/hw/application_fpga/tkey-libs/libcrt0.a /hw/application_fpga/tkey-libs/libcrt0.a
/hw/application_fpga/tkey-libs/libmonocypher.a /hw/application_fpga/tkey-libs/libmonocypher.a
/hw/application_fpga/tkey-libs/libblake2s.a
/hw/application_fpga/tools/partition_table/partition_table
/hw/application_fpga/tools/b2s/b2s
synth.json synth.json
synth.txt synth.txt
synth.v synth.v
@ -46,7 +44,6 @@ verilated/
*.o *.o
*.asc *.asc
*.bin *.bin
!/hw/application_fpga/tools/default_partition.bin
*.elf *.elf
*.map *.map
*.tmp *.tmp

View File

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

View File

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

View File

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

View File

@ -1 +1 @@
c770fe25034655241d9e0152b03fcf691c548bc50d30b574a5213abc5b36fe25 application_fpga.bin 3d88184d4d636878d7d891e0360413b47f288eea2227435f0bfa2aad6e956f24 application_fpga.bin

View File

@ -1 +1 @@
e72557c38bee1e16114f550b16fc04412bfba09274d7b1fe971ab6b2ef4f06421d976276175ea4df3b7d590599eb052b85a812b689d728be5031ae412b2f8d24 firmware.bin 77e6c9e519bce27f7be1a38c10839875ac8c75c71bef540f11569e821638af85a3e724089702a1c17af4c9caa06461ef38a3b8ad5bc5cb01ecf1f3107771708f firmware.bin

View File

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

View File

@ -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)

View File

@ -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 */
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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

View 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/>

View 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.

View 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;
}
//======================================================================
//======================================================================

View 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

View File

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

View File

@ -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;
}

View File

@ -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

View File

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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

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

View File

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

View File

@ -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);
}

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ INCLUDE=include
# either of them. You don't need to recompile tkey-libs. # either of them. You don't need to recompile tkey-libs.
CFLAGS = -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 \ CFLAGS = -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 \
-mcmodel=medany -static -std=gnu99 -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 \ -fno-builtin-printf -fno-builtin-putchar -nostdlib -mno-relax -flto \
-Wall -Werror=implicit-function-declaration \ -Wall -Werror=implicit-function-declaration \
-I $(INCLUDE) -I . -I $(INCLUDE) -I .

View File

@ -32,6 +32,7 @@ The Castor TKey hardware supports more USB endpoints:
- CDC - the same thing as older versions. - CDC - the same thing as older versions.
- FIDO security token, for FIDO-like apps. - FIDO security token, for FIDO-like apps.
- CCID, smart card interface.
- DEBUG, a HID debug port. - DEBUG, a HID debug port.
The communication is still over a single UART. To differ between the The communication is still over a single UART. To differ between the

View File

@ -10,20 +10,22 @@
// I/O endpoints. Keep it as bits possible to use in a bitmask in // I/O endpoints. Keep it as bits possible to use in a bitmask in
// readselect(). // readselect().
// //
// Note that the DEBUG, CDC, and FIDO should be kept the same on // Note that the values for IO_CH552, IO_CDC, IO_FIDO, IO_CCID and IO_DEBUG
// the CH552 side. // should be kept the same in the code for the CH552 side.
enum ioend { enum ioend {
IO_NONE = 0x00, // No endpoint IO_NONE = 0x00, // No endpoint
IO_UART = 0x01, // Only destination, raw UART access IO_UART = 0x01, // Only destination, raw UART access
IO_QEMU = 0x02, // Only destination, QEMU debug port IO_QEMU = 0x02, // Only destination, QEMU debug port
IO_CH552 = 0x10, // Internal CH552 control port IO_CH552 = 0x04, // Internal CH552 control port
IO_DEBUG = 0x20, // HID debug port IO_CDC = 0x08, // CDC "serial" port
IO_CDC = 0x40, // CDC "serial port" IO_FIDO = 0x10, // FIDO security token port
IO_FIDO = 0x80, // FIDO security token port IO_CCID = 0x20, // CCID "smart card" port
IO_DEBUG = 0x40, // Debug port over USB HID
}; };
enum ch552cmd { enum ch552cmd {
SET_ENDPOINTS = 0x01, // Config USB endpoints on the CH552 SET_ENDPOINTS = 0x01, // Config USB endpoints on the CH552
CH552_CMD_MAX,
}; };
void write(enum ioend dest, const uint8_t *buf, size_t nbytes); void write(enum ioend dest, const uint8_t *buf, size_t nbytes);

View File

@ -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 // write blockingly writes nbytes bytes of data from buf to dest which
// is either: // is either:
// //
// - IO_UART: Low-level UART access, no USB Mode Header added.
//
// - IO_QEMU: QEMU debug port // - 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_CDC: Through the UART for the CDC endpoint, with header.
// //
// - IO_FIDO: Through the UART for the FIDO 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. // header.
void write(enum ioend dest, const uint8_t *buf, size_t nbytes) void write(enum ioend dest, const uint8_t *buf, size_t nbytes)
{ {
@ -203,9 +208,11 @@ static int discard(size_t nbytes)
// //
// Only endpoints available for read are: // Only endpoints available for read are:
// //
// - IO_DEBUG // - IO_CH552
// - IO_CDC // - IO_CDC
// - IO_FIDO // - IO_FIDO
// - IO_CCID
// - IO_DEBUG
// //
// If you need blocking low-level UART reads, use uart_read() instead. // 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 // Configure USB endpoints that should be enabled/disabled
// //
// Allowed options are: // 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 // - IO_DEBUG
// //
// The following are always enabled: // The following are always enabled:

View File

@ -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
```

View File

@ -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)
}

View File

@ -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

View File

@ -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=

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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=

View File

@ -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)
}
}

View File

@ -57,6 +57,7 @@ if __name__ == "__main__":
"CdcCtrlInterfaceDesc": "CDC-Ctrl", "CdcCtrlInterfaceDesc": "CDC-Ctrl",
"CdcDataInterfaceDesc": "CDC-Data", "CdcDataInterfaceDesc": "CDC-Data",
"FidoInterfaceDesc": "FIDO", "FidoInterfaceDesc": "FIDO",
"CcidInterfaceDesc": "CCID",
"DebugInterfaceDesc": "DEBUG" "DebugInterfaceDesc": "DEBUG"
} }

View File

@ -94,6 +94,13 @@ Header file for CH554 microcontrollers.
#define USB_CDC_REQ_TYPE_SET_CONTROL_LINE_STATE 0x22 #define USB_CDC_REQ_TYPE_SET_CONTROL_LINE_STATE 0x22
#endif #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 */ /* USB request type for hub class request */
#ifndef HUB_GET_HUB_DESCRIPTOR #ifndef HUB_GET_HUB_DESCRIPTOR
#define HUB_CLEAR_HUB_FEATURE 0x20 #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_CTRL_STR 0x04
#define USB_IDX_INTERFACE_CDC_DATA_STR 0x05 #define USB_IDX_INTERFACE_CDC_DATA_STR 0x05
#define USB_IDX_INTERFACE_FIDO_STR 0x06 #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 #endif
#ifndef USB_DEVICE_ADDR #ifndef USB_DEVICE_ADDR

View File

@ -8,14 +8,16 @@ enum ioend {
IO_NONE = 0x00, // No endpoint IO_NONE = 0x00, // No endpoint
IO_UART = 0x01, // Only destination, raw UART access IO_UART = 0x01, // Only destination, raw UART access
IO_QEMU = 0x02, // Only destination, QEMU debug port IO_QEMU = 0x02, // Only destination, QEMU debug port
IO_CH552 = 0x10, // Internal CH552 control port IO_CH552 = 0x04, // Internal CH552 control port
IO_DEBUG = 0x20, // HID debug port IO_CDC = 0x08, // CDC "serial" port
IO_CDC = 0x40, // CDC "serial port" IO_FIDO = 0x10, // FIDO security token port
IO_FIDO = 0x80, // FIDO security token port IO_CCID = 0x20, // CCID "smart card" port
IO_DEBUG = 0x40, // Debug port over USB HID
}; };
enum ch552cmd { 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 #endif

View File

@ -55,6 +55,12 @@ unsigned char FLASH FidoInterfaceDesc[] = { // "FIDO"
'F', 0, 'I', 0, 'D', 0, 'O', 0, '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" unsigned char FLASH DebugInterfaceDesc[] = { // "DEBUG"
12, // Length of this descriptor (in bytes) 12, // Length of this descriptor (in bytes)
0x03, // Descriptor type (String) 0x03, // Descriptor type (String)

File diff suppressed because it is too large Load Diff