pax_global_header00006660000000000000000000000064147151112570014516gustar00rootroot0000000000000052 comment=5390ebdca1ce9bfab1a120bb18c396ca94f61da9 tillitis-tkey-device-signer-1.0.2/000077500000000000000000000000001471511125700170475ustar00rootroot00000000000000tillitis-tkey-device-signer-1.0.2/.clang-format000066400000000000000000000004171471511125700214240ustar00rootroot00000000000000BasedOnStyle: LLVM UseTab: Always IndentWidth: 8 TabWidth: 8 IncludeBlocks: Preserve BreakBeforeBraces: Custom BraceWrapping: AfterFunction: true AllowShortFunctionsOnASingleLine: false AllowShortIfStatementsOnASingleLine: Never AllowShortEnumsOnASingleLine: false tillitis-tkey-device-signer-1.0.2/.editorconfig000066400000000000000000000000341471511125700215210ustar00rootroot00000000000000[*.md] max_line_length = 70 tillitis-tkey-device-signer-1.0.2/.github/000077500000000000000000000000001471511125700204075ustar00rootroot00000000000000tillitis-tkey-device-signer-1.0.2/.github/workflows/000077500000000000000000000000001471511125700224445ustar00rootroot00000000000000tillitis-tkey-device-signer-1.0.2/.github/workflows/ci.yaml000066400000000000000000000016341471511125700237270ustar00rootroot00000000000000 name: ci on: push: branches: - 'main' pull_request: {} # allow manual runs: workflow_dispatch: {} jobs: ci: runs-on: ubuntu-latest container: image: ghcr.io/tillitis/tkey-builder:4 steps: - name: checkout uses: actions/checkout@v4 with: # fetch-depth: 0 persist-credentials: false - name: fix # https://github.com/actions/runner-images/issues/6775 run: | git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: check fmt of c code run: make checkfmt - name: check for SPDX tags run: ./tools/spdx-ensure - name: make run: ./build.sh reuse-compliance-check: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: REUSE Compliance Check uses: fsfe/reuse-action@v4 with: args: lint tillitis-tkey-device-signer-1.0.2/.gitignore000066400000000000000000000000731471511125700210370ustar00rootroot00000000000000*.a *.o *.bin *.elf # Clangd compile_commands.json .cache tillitis-tkey-device-signer-1.0.2/LICENSE000066400000000000000000000024241471511125700200560ustar00rootroot00000000000000BSD 2-Clause License Copyright 2022 Tillitis AB Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. tillitis-tkey-device-signer-1.0.2/LICENSES/000077500000000000000000000000001471511125700202545ustar00rootroot00000000000000tillitis-tkey-device-signer-1.0.2/LICENSES/BSD-2-Clause.txt000066400000000000000000000023761471511125700230060ustar00rootroot00000000000000Copyright 2022 Tillitis AB Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. tillitis-tkey-device-signer-1.0.2/Makefile000066400000000000000000000046701471511125700205160ustar00rootroot00000000000000# Check for OS, if not macos assume linux UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) shasum = shasum -a 512 else shasum = sha512sum endif IMAGE=ghcr.io/tillitis/tkey-builder:4 OBJCOPY ?= llvm-objcopy P := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) LIBDIR ?= $(P)/../tkey-libs CC = clang INCLUDE = $(LIBDIR)/include # If you want libcommon's qemu_puts() et cetera to output something on our QEMU # debug port, use -DQEMU_DEBUG below 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 -nostdlib -mno-relax -flto -g \ -Wall -Werror=implicit-function-declaration \ -I $(INCLUDE) -I $(LIBDIR) #-DQEMU_DEBUG ifneq ($(TKEY_SIGNER_APP_NO_TOUCH),) CFLAGS := $(CFLAGS) -DTKEY_SIGNER_APP_NO_TOUCH endif AS = clang ASFLAGS = -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 -mcmodel=medany -mno-relax LDFLAGS=-T $(LIBDIR)/app.lds -L $(LIBDIR) -lcommon -lcrt0 .PHONY: all all: signer/app.bin check-signer-hash # Create compile_commands.json for clangd and LSP .PHONY: clangd clangd: compile_commands.json compile_commands.json: $(MAKE) clean bear -- make signer/app.bin # Turn elf into bin for device %.bin: %.elf $(OBJCOPY) --input-target=elf32-littleriscv --output-target=binary $^ $@ chmod a-x $@ show-%-hash: %/app.bin @echo "Device app digest:" @$(shasum) $$(dirname $^)/app.bin check-signer-hash: signer/app.bin show-signer-hash @echo "Expected device app digest: " @cat signer/app.bin.sha512 $(shasum) -c signer/app.bin.sha512 .PHONY: check check: clang-tidy -header-filter=.* -checks=cert-* signer/*.[ch] -- $(CFLAGS) # Simple ed25519 signer app SIGNEROBJS=signer/main.o signer/app_proto.o signer/app.elf: $(SIGNEROBJS) $(CC) $(CFLAGS) $(SIGNEROBJS) $(LDFLAGS) -L $(LIBDIR)/monocypher -lmonocypher -I $(LIBDIR) -o $@ $(SIGNEROBJS): $(INCLUDE)/tkey/tk1_mem.h signer/app_proto.h .PHONY: clean clean: rm -f signer/app.bin signer/app.elf $(SIGNEROBJS) # Uses ../.clang-format FMTFILES=signer/*.[ch] .PHONY: fmt fmt: clang-format --dry-run --ferror-limit=0 $(FMTFILES) clang-format --verbose -i $(FMTFILES) .PHONY: checkfmt checkfmt: clang-format --dry-run --ferror-limit=0 --Werror $(FMTFILES) .PHONY: podman podman: podman run --arch=amd64 --rm --mount type=bind,source=$(CURDIR),target=/src --mount type=bind,source=$(LIBDIR),target=/tkey-libs -w /src -it $(IMAGE) make -j tillitis-tkey-device-signer-1.0.2/README.md000066400000000000000000000202021471511125700203220ustar00rootroot00000000000000[![ci](https://github.com/tillitis/tkey-device-signer/actions/workflows/ci.yaml/badge.svg?branch=main&event=push)](https://github.com/tillitis/tkey-device-signer/actions/workflows/ci.yaml) # Tillitis TKey Signer The TKey `signer` device application is an ed25519 signing tool. It can sign messages up to 4 kByte. It is, for instance, used by the [tkey-ssh-agent](https://github.com/tillitis/tkey-ssh-agent) for SSH authentication and by [tkey-sign](https://github.com/tillitis/tkey-sign-cli) for doing digital signatures of files. See [Release notes](RELEASE.md). ## Client Go package We provide a Go package to use with `signer`: - https://github.com/tillitis/tkeysign [![Go Reference](https://pkg.go.dev/badge/github.com/tillitis/tkeysign.svg)](https://pkg.go.dev/github.com/tillitis/tkeysign) ## Signer application protocol `signer` has a simple application protocol on top of the [TKey Framing Protocol](https://dev.tillitis.se/protocol/#framing-protocol). The protocol state machine handling this protocol is documented in [the implementation notes](docs/implementation-notes.md). The protocol has the following requests and responses: | *command* | *Function* | *FP length* | *code* | *data* | *response* | |-------------------------|----------------------------------|-------------|--------|-------------------|-------------------------| | `CMD_GET_PUBKEY` | Get the public key | 1 B | 0x01 | none | `RSP_GET_PUBKEY` | | `CMD_SET_SIZE` | Set size of message to be signed | 32 B | 0x03 | size as 32 bit LE | `RSP_SET_SIZE` | | `CMD_LOAD_DATA` | Load a chunk of message | 128 B | 0x05 | 127 B null-padded | `RSP_LOAD_DATA` | | `CMD_GET_SIG` | Sign and get signature | 1 B | 0x07 | none | `RSP_GET_SIG` | | `CMD_GET_NAMEVERSION` | Identify version of app | 1 B | 0x09 | none | `RSP_GET_NAMEVERSION` | | `CMD_GET_FIRMWARE_HASH` | Ask for digest of firmware | 32 B | 0x0b | size as 32 bit LE | `RSP_GET_FIRMWARE_HASH` | | *response* | *FP length* | *code* | *data* | |-------------------------|-------------|--------|------------------------------------| | `RSP_GET_PUBKEY` | 128 B | 0x02 | 32 byte ed25519 public key | | `RSP_SET_SIZE` | 4 B | 0x04 | 1 byte status | | `RSP_LOAD_DATA` | 4 B | 0x06 | 1 byte status | | `RSP_GET_SIG` | 128 B | 0x08 | 64 byte signature | | `RSP_GET_NAMEVERSION` | 32 B | 0x0a | 2 * 4 byte name, version 32 bit LE | | `RSP_GET_FIRMWARE_HASH` | 128 B | 0x0c | 1 byte status + 64 bytes digest | | *status replies* | *code* | |------------------|--------| | OK | 0 | | BAD | 1 | It identifies itself with: - `name0`: "tk1 " - `name1`: "sign" Please note that `signer` also replies with a `NOK` Framing Protocol response status if the endpoint field in the FP header is meant for the firmware (endpoint = `DST_FW`). This is recommended for well-behaved device applications so the client side can probe for the firmware. Typical use by a client application: 1. Probe for firmware by sending firmware's `GET_NAME_VERSION` with FP header endpoint = `DST_FW`. 2. If firmware is found, load `signer`. 3. Upon receiving the device app digest back from firmware, switch to start talking the `signer` protocol above. 4. Send `CMD_GET_PUBKEY` to receive the `signer`'s public key. If the public key is already stored, check against it so it's the expected key. 5. Send `CMD_SET_SIZE` to set the size of the message to sign. 6. Send repeated messages with `CMD_LOAD_DATA` to send the entire message. 7. Send `CMD_GET_SIG` to get the signature over the message. **Please note**: The firmware detection mechanism is not by any means secure. If in doubt a user should always remove the TKey and insert it again before doing any operation. ## Licenses and SPDX tags Unless otherwise noted, the project sources are copyright Tillitis AB, licensed under the terms and conditions of the "BSD-2-Clause" license. See [LICENSE](LICENSE) for the full license text. Until Oct 17, 2024, the license was GPL-2.0 Only. External source code we have imported are isolated in their own directories. They may be released under other licenses. This is noted with a similar `LICENSE` file in every directory containing imported sources. The project uses single-line references to Unique License Identifiers as defined by the Linux Foundation's [SPDX project](https://spdx.org/) on its own source files, but not necessarily imported files. The line in each individual source file identifies the license applicable to that file. The current set of valid, predefined SPDX identifiers can be found on the SPDX License List at: https://spdx.org/licenses/ We attempt to follow the [REUSE specification](https://reuse.software/). ## Building You have two options for build tools: either you use our OCI image `ghcr.io/tillitis/tkey-builder` or native tools. An easy way to build is to use the provided scripts: - `build.sh` for native tools. - `build-podman.sh` for use with Podman. These scripts automatilly clone the [tkey-libs device libraries](https://github.com/tillitis/tkey-libs) in a directory next to this one. If you want to use a pre-built libraries, download the libraries tar ball from https://github.com/tillitis/tkey-libs/releases unpack it, and specify where you unpacked it in `LIBDIR` when building: ``` make LIBDIR=~/Downloads/tkey-libs-v0.1.2 ``` Note that your `lld` might complain if they were built with a different version. If so, either use the same version the release used or use podman. ### Building with Podman On Ubuntu 22.10, running ``` apt install podman rootlesskit slirp4netns ``` should be enough to get you a working Podman setup. You can then either: - Use `build-podman.sh` as described above, which clones and builds the tkey-libs libraries as well. - Download [pre-built versions of the tkey-libs libraries](https://github.com/tillitis/tkey-libs/releases) and define `LIBDIR` to where you unpacked the tkey-libs, something like: ``` make LIBDIR=$HOME/Downloads/tkey-libs-v0.1.2 podman ``` Note that `~` expansion doesn't work. ### Building with host tools To build with native tools you need at least the `clang`, `llvm`, `lld`, packages installed. Version 15 or later of LLVM/Clang is for support of our architecture (RV32\_Zmmul). Ubuntu 22.10 (Kinetic) is known to have this. Please see [toolchain_setup.md](https://github.com/tillitis/tillitis-key1/blob/main/doc/toolchain_setup.md) (in the tillitis-key1 repository) for detailed information on the currently supported build and development environment. Build everything: ``` $ make ``` If you cloned `tkey-libs` to somewhere else then the default set `LIBDIR` to the path of the directory. If your available `objcopy` is anything other than the default `llvm-objcopy`, then define `OBJCOPY` to whatever they're called on your system. ### Disabling touch requirement The `signer` normally requires the TKey to be physically touched for signing to complete. For special purposes it can be compiled with this requirement removed by setting the environment variable `TKEY_SIGNER_APP_NO_TOUCH` to some value when building. Example: ``` $ make TKEY_SIGNER_APP_NO_TOUCH=yesplease ``` Of course this changes the signer app binary and as a consequence the derived private key and identity will change. ## Running If you just want to sign a file or experiment with the signer, use the [tkey-sign](https://github.com/tillitis/tkey-sign-cli) command which you can also use as an example on how to load and run the signer device app. [tkey-ssh-agent](https://github.com/tillitis/tillitis-key1-apps) also uses this signer app. Please see the [Developer Handbook](https://dev.tillitis.se/) for [how to run with QEMU](https://dev.tillitis.se/tools/#qemu-emulator) or [how to run apps on a TKey](https://dev.tillitis.se/devapp/#running-tkey-apps). tillitis-tkey-device-signer-1.0.2/RELEASE.md000066400000000000000000000034001471511125700204460ustar00rootroot00000000000000# Release notes ## v1.0.2 - Change license to BSD-2-Clause - Follow REUSE specification, see https://reuse.software/ - Bump tkey-libs to v0.1.2, also using BSD-2-Clause - Exit early in build scripts - Added make target for creating compile_commands.json for clangd **Note:** this release does not change the CDI generated for a signer built with touch (the default). Complete [changelog](https://github.com/tillitis/tkey-device-signer/compare/v1.0.1...v1.0.2). ## v1.0.1 - Resolve a bug that prevents the signer, built without touch, to do a signature. - Add clangd files to gitignore. - Use the right shasum tool in Makefile. - Format the app.bin.sha512 file correctly. **Note:** this release does not change the CDI generated for a signer built with touch (the default). Complete [changelog](https://github.com/tillitis/tkey-device-signer/compare/v1.0.0...v1.0.1). ## v1.0.0 - Fix TOCTOU bug. - Remove prehash command. - Introduce firmware hash command. - Device app version now 3 since protocol changed. - Introduce an explicit protocol state machine, exiting early on suspicious behaviour, [see implementation notes](docs/implementation-notes.md). - Update to tkey-libs v0.1.1. - Use CDI directly instead of copying. Complete [changelog](https://github.com/tillitis/tkey-device-signer/compare/v0.0.8...v1.0.0). ## v0.0.8 - Update to use tkey-libs v0.0.2, including Monocypher 4.0.1 - Use execution monitor - Support signing pre-hashed messages. NOTE: this release changes the Tkey identity (CDI) compared to v0.0.7, i.e., you will not have the same Ed25519 keypair derived. Complete [changelog](https://github.com/tillitis/tkey-device-signer/compare/v0.0.7...v0.0.8). ## v0.0.7 Just ripped from https://github.com/tillitis/tillitis-key1-apps No semantic changes. tillitis-tkey-device-signer-1.0.2/REUSE.toml000066400000000000000000000010641471511125700206300ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2024 Tillitis AB # SPDX-License-Identifier: BSD-2-Clause version = 1 [[annotations]] path = ".github/workflows/*" SPDX-FileCopyrightText = "2022 Tillitis AB " SPDX-License-Identifier = "BSD-2-Clause" [[annotations]] path = [ ".clang-format", ".editorconfig", ".gitignore", "Makefile", "README.md", "RELEASE.md", "docs/implementation-notes.md", "signer/app.bin.sha512" ] SPDX-FileCopyrightText = "2022 Tillitis AB " SPDX-License-Identifier = "BSD-2-Clause" tillitis-tkey-device-signer-1.0.2/build-podman.sh000077500000000000000000000007021471511125700217600ustar00rootroot00000000000000#! /bin/sh # SPDX-FileCopyrightText: 2023 Tillitis AB # SPDX-License-Identifier: BSD-2-Clause set -eu tkey_libs_version="v0.1.2" printf "Building tkey-libs with version: %s\n" "$tkey_libs_version" if [ -d ../tkey-libs ] then (cd ../tkey-libs; git checkout "$tkey_libs_version") else git clone -b "$tkey_libs_version" https://github.com/tillitis/tkey-libs.git ../tkey-libs fi make -j -C ../tkey-libs podman make -j podman tillitis-tkey-device-signer-1.0.2/build.sh000077500000000000000000000006641471511125700205130ustar00rootroot00000000000000#! /bin/sh # SPDX-FileCopyrightText: 2023 Tillitis AB # SPDX-License-Identifier: BSD-2-Clause set -eu tkey_libs_version="v0.1.2" printf "Building tkey-libs with version: %s\n" "$tkey_libs_version" if [ -d ../tkey-libs ] then (cd ../tkey-libs; git checkout "$tkey_libs_version") else git clone -b "$tkey_libs_version" https://github.com/tillitis/tkey-libs.git ../tkey-libs fi make -j -C ../tkey-libs make -j tillitis-tkey-device-signer-1.0.2/docs/000077500000000000000000000000001471511125700177775ustar00rootroot00000000000000tillitis-tkey-device-signer-1.0.2/docs/implementation-notes.md000066400000000000000000000071301471511125700244750ustar00rootroot00000000000000# Implementation notes ## Protocol state machine The state machine below controls the protocol parser. Only a certain number of commands are available in each state. The reasoning behind it is to tighten the protocol and exit early on any errors. We have four states: - `started` - `loading` - `signing` - `failed` When we start the machine we start in state `started` and are ready for some initial commands. See [README.md](../README.md) for a complete description of the protocol and what we expect a well-behaved client to do. The state machine is here to catch clients who are not that well-behaved. When we enter state `failed` we trigger an unimplemented instruction and halt the CPU, blinking the status LED forever in hardware, and not executing any more instructions until powercycling the TKey. Protocol commands in the graph are marked with all caps, like this `GET_NAMEVERSION`. In the code all commands are prefixed by `CMD_` but that has been removed here to make it easier to follow. They have a corresponding reply code `RSP_` which is not part of the protocol parser and hence not mentioned here. "Error" means any kind of error, either parsing errors (wrong length, unexpected data, a firmware probe in a state that doesn't allow it) or some other kind of error. ```mermaid stateDiagram-v2 S1: started S2: loading S3: signing SE: failed [*] --> S1 S1 --> S1: GET_NAMEVERSION, GET_PUBKEY, GET_FIRMWARE_HASH S1 --> S2: SET_SIZE S1 --> SE: Error S2 --> S2: LOAD_DATA S2 --> S3: Last block received S2 --> SE: Error S3 --> SE: Error S3 --> S1: GET_SIG ``` ### State: started Commands allowed in state `started`: | *command* | *next state* | |---------------------|--------------| | `GET_NAMEVERSION` | `started` | | `GET_PUBKEY` | `started` | | `GET_FIRMWARE_HASH` | `started` | | `SET_SIZE` | `loading` | In `started` we're ready to start a transaction with a client program. It can gather some information or start sending a message to be signed by setting the message size. ### State: loading Commands allowed in state `loading`: | *command* | *next state* | |-------------|--------------------------------------| | `LOAD_DATA` | `loading` or `signing` on last chunk | We're in the process of receiving a message to be signed. All other commands result in state failed. You can't go back to `started` until you've sent the entire message and asked for a signature or powercycle the TKey. When we have received the entire message, as set by `SET_SIZE`, we move to state `signing`. One can argue that we should instead immediately sign and send back the signature but that would break the current protocol which a lot of clients are depending on. ### State: signing Commands allowed in state `signing`: | *command* | *next state* | |-----------|--------------| | `GET_SIG` | `started` | The entire message has been received. We're waiting for the client to ask for the signature. No other commands are allowed. If they are sent, even a firmware probe, we enter state `failed`. When the request comes for a signature we first wait for the user to assert presence by touching the touch sensor, then sign the message, and send the signature back to the client. Then we go back to state `started` to expect a new message to sign. ### State: failed Commands in state `failed`: None. Some error (parsing or otherwise) or wrong command has been seen. We feed the CPU an illegal instruction and the hardware hangs forever, blinking the LED red. We can only exit this state by powercycling the TKey. tillitis-tkey-device-signer-1.0.2/signer/000077500000000000000000000000001471511125700203365ustar00rootroot00000000000000tillitis-tkey-device-signer-1.0.2/signer/app.bin.sha512000066400000000000000000000002211471511125700226050ustar00rootroot00000000000000fe4458e4125966885d9b745a25422948d76e60371165b97729fce1b423f22b87929c684b4381f2220aa0c94266ba035730d5f08a6e6e0aab7d7bf15165d2fff6 signer/app.bin tillitis-tkey-device-signer-1.0.2/signer/app_proto.c000066400000000000000000000023371471511125700225120ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Tillitis AB // SPDX-License-Identifier: BSD-2-Clause #include #include "app_proto.h" // Send reply frame with response status Not OK (NOK==1), shortest length void appreply_nok(struct frame_header hdr) { writebyte(genhdr(hdr.id, hdr.endpoint, 0x1, LEN_1)); writebyte(0); } // Send app reply with frame header, response code, and LEN_X-1 bytes from buf void appreply(struct frame_header hdr, enum appcmd rspcode, void *buf) { size_t nbytes = 0; enum cmdlen len = LEN_1; switch (rspcode) { case RSP_GET_PUBKEY: len = LEN_128; nbytes = 128; break; case RSP_SET_SIZE: len = LEN_4; nbytes = 4; break; case RSP_LOAD_DATA: len = LEN_4; nbytes = 4; break; case RSP_GET_SIG: len = LEN_128; nbytes = 128; break; case RSP_GET_NAMEVERSION: len = LEN_32; nbytes = 32; break; case RSP_GET_FIRMWARE_HASH: len = LEN_128; nbytes = 128; break; default: qemu_puts("appreply(): Unknown response code: "); qemu_puthex(rspcode); qemu_lf(); return; } // Frame Protocol Header writebyte(genhdr(hdr.id, hdr.endpoint, 0x0, len)); // app protocol header is 1 byte response code writebyte(rspcode); nbytes--; write(buf, nbytes); } tillitis-tkey-device-signer-1.0.2/signer/app_proto.h000066400000000000000000000013541471511125700225150ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Tillitis AB // SPDX-License-Identifier: BSD-2-Clause #ifndef APP_PROTO_H #define APP_PROTO_H #include #include // clang-format off enum appcmd { CMD_GET_PUBKEY = 0x01, RSP_GET_PUBKEY = 0x02, CMD_SET_SIZE = 0x03, RSP_SET_SIZE = 0x04, CMD_LOAD_DATA = 0x05, RSP_LOAD_DATA = 0x06, CMD_GET_SIG = 0x07, RSP_GET_SIG = 0x08, CMD_GET_NAMEVERSION = 0x09, RSP_GET_NAMEVERSION = 0x0a, CMD_GET_FIRMWARE_HASH = 0x0b, RSP_GET_FIRMWARE_HASH = 0x0c, CMD_FW_PROBE = 0xff, }; // clang-format on void appreply_nok(struct frame_header hdr); void appreply(struct frame_header hdr, enum appcmd rspcode, void *buf); #endif tillitis-tkey-device-signer-1.0.2/signer/main.c000066400000000000000000000237431471511125700214370ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2022 Tillitis AB // SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include #include #include #include "app_proto.h" // clang-format off static volatile uint32_t *cdi = (volatile uint32_t *) TK1_MMIO_TK1_CDI_FIRST; static volatile uint32_t *cpu_mon_ctrl = (volatile uint32_t *) TK1_MMIO_TK1_CPU_MON_CTRL; static volatile uint32_t *cpu_mon_first = (volatile uint32_t *) TK1_MMIO_TK1_CPU_MON_FIRST; static volatile uint32_t *cpu_mon_last = (volatile uint32_t *) TK1_MMIO_TK1_CPU_MON_LAST; static volatile uint32_t *app_addr = (volatile uint32_t *) TK1_MMIO_TK1_APP_ADDR; static volatile uint32_t *app_size = (volatile uint32_t *) TK1_MMIO_TK1_APP_SIZE; // clang-format on // Touch timeout in seconds #define TOUCH_TIMEOUT 30 #define MAX_SIGN_SIZE 4096 const uint8_t app_name0[4] = "tk1 "; const uint8_t app_name1[4] = "sign"; const uint32_t app_version = 0x00000003; enum state { STATE_STARTED, STATE_LOADING, STATE_SIGNING, STATE_FAILED, }; // Context for the loading of a message struct context { uint8_t secret_key[64]; // Private key. Keep this here below // message in memory. uint8_t pubkey[32]; uint8_t message[MAX_SIGN_SIZE]; uint32_t left; // Bytes left to receive uint32_t message_size; uint16_t msg_idx; // Where we are currently loading a message }; // Incoming packet from client struct packet { struct frame_header hdr; // Framing Protocol header uint8_t cmd[CMDLEN_MAXBYTES]; // Application level protocol }; static enum state started_commands(enum state state, struct context *ctx, struct packet pkt); static enum state loading_commands(enum state state, struct context *ctx, struct packet pkt); static enum state signing_commands(enum state state, struct context *ctx, struct packet pkt); static int read_command(struct frame_header *hdr, uint8_t *cmd); static void wipe_context(struct context *ctx); static void wipe_context(struct context *ctx) { crypto_wipe(ctx->message, MAX_SIGN_SIZE); ctx->left = 0; ctx->message_size = 0; ctx->msg_idx = 0; } // started_commands() allows only these commands: // // - CMD_FW_PROBE // - CMD_GET_NAMEVERSION // - CMD_GET_FIRMWARE_HASH // - CMD_GET_PUBKEY // - CMD_SET_SIZE // // Anything else sent leads to state 'failed'. // // Arguments: the current state, the context and the incoming command. // Returns: The new state. static enum state started_commands(enum state state, struct context *ctx, struct packet pkt) { uint8_t rsp[CMDLEN_MAXBYTES] = {0}; // Response size_t rsp_left = CMDLEN_MAXBYTES; // How many bytes left in response buf // Smallest possible payload length (cmd) is 1 byte. switch (pkt.cmd[0]) { case CMD_FW_PROBE: // Firmware probe. Allowed in this protocol state. // State unchanged. break; case CMD_GET_NAMEVERSION: qemu_puts("CMD_GET_NAMEVERSION\n"); if (pkt.hdr.len != 1) { // Bad length state = STATE_FAILED; break; } memcpy_s(rsp, rsp_left, app_name0, sizeof(app_name0)); rsp_left -= sizeof(app_name0); memcpy_s(&rsp[4], rsp_left, app_name1, sizeof(app_name1)); rsp_left -= sizeof(app_name1); memcpy_s(&rsp[8], rsp_left, &app_version, sizeof(app_version)); appreply(pkt.hdr, RSP_GET_NAMEVERSION, rsp); // state unchanged break; case CMD_GET_FIRMWARE_HASH: { uint32_t fw_len = 0; qemu_puts("APP_CMD_GET_FIRMWARE_HASH\n"); if (pkt.hdr.len != 32) { rsp[0] = STATUS_BAD; appreply(pkt.hdr, RSP_GET_FIRMWARE_HASH, rsp); state = STATE_FAILED; break; } fw_len = pkt.cmd[1] + (pkt.cmd[2] << 8) + (pkt.cmd[3] << 16) + (pkt.cmd[4] << 24); if (fw_len == 0 || fw_len > 8192) { qemu_puts("FW size must be > 0 and <= 8192\n"); rsp[0] = STATUS_BAD; appreply(pkt.hdr, RSP_GET_FIRMWARE_HASH, rsp); state = STATE_FAILED; break; } rsp[0] = STATUS_OK; crypto_sha512(&rsp[1], (void *)TK1_ROM_BASE, fw_len); appreply(pkt.hdr, RSP_GET_FIRMWARE_HASH, rsp); // state unchanged break; } case CMD_GET_PUBKEY: qemu_puts("CMD_GET_PUBKEY\n"); if (pkt.hdr.len != 1) { // Bad length state = STATE_FAILED; break; } memcpy_s(rsp, CMDLEN_MAXBYTES, ctx->pubkey, sizeof(ctx->pubkey)); appreply(pkt.hdr, RSP_GET_PUBKEY, rsp); // state unchanged break; case CMD_SET_SIZE: { uint32_t local_message_size = 0; qemu_puts("CMD_SET_SIZE\n"); // Bad length if (pkt.hdr.len != 32) { rsp[0] = STATUS_BAD; appreply(pkt.hdr, RSP_SET_SIZE, rsp); state = STATE_FAILED; break; } // cmd[1..4] contains the size. local_message_size = pkt.cmd[1] + (pkt.cmd[2] << 8) + (pkt.cmd[3] << 16) + (pkt.cmd[4] << 24); if (local_message_size == 0 || local_message_size > MAX_SIGN_SIZE) { qemu_puts("Message size not within range!\n"); rsp[0] = STATUS_BAD; appreply(pkt.hdr, RSP_SET_SIZE, rsp); state = STATE_FAILED; break; } // Set the real message size used later and reset // where we load the data ctx->message_size = local_message_size; ctx->left = ctx->message_size; ctx->msg_idx = 0; rsp[0] = STATUS_OK; appreply(pkt.hdr, RSP_SET_SIZE, rsp); state = STATE_LOADING; break; } default: qemu_puts("Got unknown initial command: 0x"); qemu_puthex(pkt.cmd[0]); qemu_lf(); state = STATE_FAILED; break; } return state; } // loading_commands() allows only these commands: // // - CMD_LOAD_DATA // // Anything else sent leads to state 'failed'. // // Arguments: the current state, the context and the incoming command. // Returns: The new state. static enum state loading_commands(enum state state, struct context *ctx, struct packet pkt) { uint8_t rsp[CMDLEN_MAXBYTES] = {0}; // Response int nbytes = 0; // Bytes to write to memory switch (pkt.cmd[0]) { case CMD_LOAD_DATA: { qemu_puts("CMD_LOAD_DATA\n"); // Bad length if (pkt.hdr.len != CMDLEN_MAXBYTES) { rsp[0] = STATUS_BAD; appreply(pkt.hdr, RSP_LOAD_DATA, rsp); state = STATE_FAILED; break; } if (ctx->left > CMDLEN_MAXBYTES - 1) { nbytes = CMDLEN_MAXBYTES - 1; } else { nbytes = ctx->left; } memcpy_s(&ctx->message[ctx->msg_idx], MAX_SIGN_SIZE - ctx->msg_idx, pkt.cmd + 1, nbytes); ctx->msg_idx += nbytes; ctx->left -= nbytes; rsp[0] = STATUS_OK; appreply(pkt.hdr, RSP_LOAD_DATA, rsp); if (ctx->left == 0) { state = STATE_SIGNING; break; } // state unchanged break; } default: qemu_puts("Got unknown loading command: 0x"); qemu_puthex(pkt.cmd[0]); qemu_lf(); state = STATE_FAILED; break; } return state; } // signing_commands() allows only these commands: // // - CMD_GET_SIG // // Anything else sent leads to state 'failed'. // // Arguments: the current state, the context, the incoming command // packet, and the secret key. // // Returns: The new state. static enum state signing_commands(enum state state, struct context *ctx, struct packet pkt) { uint8_t rsp[CMDLEN_MAXBYTES] = {0}; // Response uint8_t signature[64] = {0}; bool touched = false; switch (pkt.cmd[0]) { case CMD_GET_SIG: qemu_puts("CMD_GET_SIG\n"); if (pkt.hdr.len != 1) { // Bad length state = STATE_FAILED; break; } #ifndef TKEY_SIGNER_APP_NO_TOUCH touched = touch_wait(LED_GREEN, TOUCH_TIMEOUT); if (!touched) { rsp[0] = STATUS_BAD; appreply(pkt.hdr, RSP_GET_SIG, rsp); state = STATE_STARTED; break; } #endif qemu_puts("Touched, now let's sign\n"); // All loaded, device touched, let's sign the message crypto_ed25519_sign(signature, ctx->secret_key, ctx->message, ctx->message_size); qemu_puts("Sending signature!\n"); memcpy_s(rsp + 1, CMDLEN_MAXBYTES, signature, sizeof(signature)); appreply(pkt.hdr, RSP_GET_SIG, rsp); // Forget signature and most of context crypto_wipe(signature, sizeof(signature)); wipe_context(ctx); state = STATE_STARTED; break; default: qemu_puts("Got unknown signing command: 0x"); qemu_puthex(pkt.cmd[0]); qemu_lf(); state = STATE_FAILED; break; } return state; } // read_command takes a frame header and a command to fill in after // parsing. It returns 0 on success. static int read_command(struct frame_header *hdr, uint8_t *cmd) { uint8_t in = 0; memset(hdr, 0, sizeof(struct frame_header)); memset(cmd, 0, CMDLEN_MAXBYTES); in = readbyte(); if (parseframe(in, hdr) == -1) { qemu_puts("Couldn't parse header\n"); return -1; } // Now we know the size of the cmd frame, read it all if (read(cmd, CMDLEN_MAXBYTES, hdr->len) != 0) { qemu_puts("read: buffer overrun\n"); return -1; } // Well-behaved apps are supposed to check for a client // attempting to probe for firmware. In that case destination // is firmware and we just reply NOK. if (hdr->endpoint == DST_FW) { appreply_nok(*hdr); qemu_puts("Responded NOK to message meant for fw\n"); cmd[0] = CMD_FW_PROBE; return 0; } // Is it for us? if (hdr->endpoint != DST_SW) { qemu_puts("Message not meant for app. endpoint was 0x"); qemu_puthex(hdr->endpoint); qemu_lf(); return -1; } return 0; } int main(void) { struct context ctx = {0}; enum state state = STATE_STARTED; struct packet pkt = {0}; // Use Execution Monitor on RAM after app *cpu_mon_first = *app_addr + *app_size; *cpu_mon_last = TK1_RAM_BASE + TK1_RAM_SIZE; *cpu_mon_ctrl = 1; led_set(LED_BLUE); // Generate a public key from CDI crypto_ed25519_key_pair(ctx.secret_key, ctx.pubkey, (uint8_t *)cdi); for (;;) { qemu_puts("parser state: "); qemu_putinthex(state); qemu_lf(); if (read_command(&pkt.hdr, pkt.cmd) != 0) { state = STATE_FAILED; } switch (state) { case STATE_STARTED: state = started_commands(state, &ctx, pkt); break; case STATE_LOADING: state = loading_commands(state, &ctx, pkt); break; case STATE_SIGNING: state = signing_commands(state, &ctx, pkt); break; case STATE_FAILED: // fallthrough default: qemu_puts("parser state 0x"); qemu_puthex(state); qemu_lf(); assert(1 == 2); break; // Not reached } } } tillitis-tkey-device-signer-1.0.2/tools/000077500000000000000000000000001471511125700202075ustar00rootroot00000000000000tillitis-tkey-device-signer-1.0.2/tools/spdx-ensure000077500000000000000000000034671471511125700224240ustar00rootroot00000000000000#!/bin/bash # SPDX-FileCopyrightText: 2023 Tillitis AB # SPDX-License-Identifier: BSD-2-Clause set -eu # Check for the SPDX tag in all files in the repo. Exit with a non-zero code if # some is missing. The missingok arrays below contain files and directories # with files where the the tag is not required. cd "${0%/*}" cd .. tag="SPDX-License-Identifier:" missingok_dirs=( .github/workflows/ LICENSES/ ) missingok_files=( .clang-format .editorconfig .gitignore LICENSE Makefile README.md RELEASE.md REUSE.toml docs/implementation-notes.md signer/app.bin.sha512 ) is_missingok() { item="$1" # ok for empty files [[ -f "$item" ]] && [[ ! -s "$item" ]] && return 0 for fileok in "${missingok_files[@]}"; do [[ "$item" = "$fileok" ]] && return 0 done for dirok in "${missingok_dirs[@]}"; do [[ "$item" =~ ^$dirok ]] && return 0 done return 1 } printf "* Checking for SPDX tags in %s\n" "$PWD" mapfile -t repofiles < <(git ls-files || true) if [[ -z "${repofiles[*]}" ]]; then printf "* No files in the repo?!\n" exit 1 fi failed=0 printed=0 for fileok in "${missingok_files[@]}"; do [[ -f "$fileok" ]] && continue if (( !printed )); then printf "* Some files in missingok_files are themselves missing:\n" printed=1 failed=1 fi printf "%s\n" "$fileok" done printed=0 for dirok in "${missingok_dirs[@]}"; do [[ -d "$dirok" ]] && continue if (( !printed )); then printf "* Some dirs in missingok_dirs are themselves missing:\n" printed=1 failed=1 fi printf "%s\n" "$dirok" done printed=0 for file in "${repofiles[@]}"; do is_missingok "$file" && continue if ! grep -q "$tag" "$file"; then if (( !printed )); then printf "* Files missing the SPDX tag:\n" printed=1 failed=1 fi printf "%s\n" "$file" fi done exit "$failed"