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