This repository contains applications to run on the TKey USB security stick. For testing and development purposes the apps can also be run in QEMU, this is also explained in detail below.
Current list of apps:
- The Ed25519 signer app. Used as root of trust and SSH authentication
- The RNG stream app. Providing arbitrary high quality random numbers
- blink. A minimalistic example application
- nx. Test program for our execution monitor.
For more information about the apps, see subsections below.
The documentation for the Go module and packages (along with this README) can also be read over at https://pkg.go.dev/github.com/tillitis/tillitis-key1-apps
Note that development is ongoing. For example, changes might be made to the signer app, causing the public/private keys it provides to change. To avoid unexpected changes, please use a tagged release.
See Release notes.
Unless otherwise noted, the project sources are licensed under the terms and conditions of the "GNU General Public License v2.0 only":
Copyright Tillitis AB.
These programs are free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 only.
These programs are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see:
See LICENSE for the full GPLv2-only license text.
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 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:
All contributors must adhere to the Developer Certificate of Origin.
To build you need the clang
, llvm
, lld
, golang
packages
installed. Version 15 or later of LLVM/Clang is required (with riscv32
support and Zmmul extension). Ubuntu 22.10 (Kinetic) is known to have
this and work. Please see
toolchain_setup.md
(in the tillitis-key1 repository) for detailed information on the
currently supported build and development environment.
Build everything:
$ make
If your available objcopy
is anything other than the default
llvm-objcopy
, then define OBJCOPY
to whatever they're called on
your system.
The apps can be run both on the hardware TKey, and on a QEMU machine
that emulates the platform. In both cases, the host program (the
program that runs on your computer, for example tkey-ssh-agent
) will
talk to the app over a serial port, virtual or real. There is a
separate section below which explains running in QEMU.
Plug the USB stick into your computer. If the LED in one of the outer corners of the USB stick is a steady white, then it has been programmed with the standard FPGA bitstream (including the firmware). If it is not then please refer to quickstart.md (in the tillitis-key1 repository) for instructions on initial programming of the USB stick.
Running lsusb
should list the USB stick as 1207:8887 Tillitis MTA1-USB-V1
. On Linux, the TKey's serial port device path is
typically /dev/ttyACM0
(but it may end with another digit, if you
have other devices plugged in). The host programs tries to auto-detect
serial ports of TKey USB sticks, but if more than one is found you'll
need to choose one using the --port
flag.
However, you should make sure that you can read and write to the serial port as your regular user.
One way to accomplish this is by installing the provided
system/60-tkey.rules
in /etc/udev/rules.d/
and running udevadm control --reload
. Now when a TKey is plugged in, its device path
(like /dev/ttyACM0
) should be read/writable by you who are logged in
locally (see loginctl
).
Another way is becoming a member of the group that owns the serial
port. On Ubuntu that group is dialout
, and you can do it like this:
$ id -un
exampleuser
$ ls -l /dev/ttyACM0
crw-rw---- 1 root dialout 166, 0 Sep 16 08:20 /dev/ttyACM0
$ sudo usermod -a -G dialout exampleuser
For the change to take effect everywhere you need to logout from your
system, and then log back in again. Then logout from your system and
log back in again. You can also (following the above example) run
newgrp dialout
in the terminal that you're working in.
Your TKey is now running the firmware. Its LED is a steady white, indicating that it is ready to receive an app to run.
You can check that the OS has found and enumerated the USB stick by running:
ioreg -p IOUSB -w0 -l
There should be an entry with "USB Vendor Name" = "Tillitis"
.
Looking in the /dev
directory, there should be a device named like
/dev/tty.usbmodemXYZ
. Where XYZ is a number, for example 101. This
is the device path that might need to be passed as --port
when
running the host programs.
Build our qemu. Use the tk1
branch. Please follow the
toolchain_setup.md
and install the packages listed there first.
$ git clone -b tk1 https://github.com/tillitis/qemu
$ mkdir qemu/build
$ cd qemu/build
$ ../configure --target-list=riscv32-softmmu --disable-werror
$ make -j $(nproc)
(Built with warnings-as-errors disabled, see this issue.)
You also need to build the firmware:
$ git clone https://github.com/tillitis/tillitis-key1
$ cd tillitis-key1/hw/application_fpga
$ make firmware.elf
Please refer to the mentioned toolchain_setup.md if you have any issues building.
Then run the emulator, passing using the built firmware to "-bios":
$ /path/to/qemu/build/qemu-system-riscv32 -nographic -M tk1,fifo=chrid -bios firmware.elf \
-chardev pty,id=chrid
It tells you what serial port it is using, for instance /dev/pts/1
.
This is what you need to use as --port
when running the host
programs.
The TK1 machine running on QEMU (which in turn runs the firmware, and
then the app) can output some memory access (and other) logging. You
can add -d guest_errors
to the qemu commandline To make QEMU send
these to stderr.
This is a message signer, for root of trust and SSH authentication
using ed25519. There are two host programs which can communicate with
the app. tkey-sign
just performs a complete test signing.
tkey-ssh-agent
is an SSH agent that allow using the signer for SSH
remote access.
If you're running on hardware, the LED on the USB stick is expected to be a steady white, indicating that firmware is ready to receive an app to run.
There's a script called runsign.sh
which runs tkey-runapp
to load
the signer app onto TKey and start it. The script then runs
tkey-sign
which communicates with the signer app to make it sign a
message and then verifies the signature. You can use it like this:
./runsign.sh file-with-message
The signer app can sign messages of up to 4096 bytes. If the --port
flags needs to be used, you can pass it after the message argument.
The host program tkey-runapp
only loads and starts an app. You'll
then have to switch to a different program that speaks your app's
specific protocol. For instance the tkey-sign
program provided here.
To run tkey-runapp
you need to pass it the raw app binary that
should be run (and possibly --port
, if the auto-detection is not
sufficient).
$ ./tkey-runapp apps/signer/app.bin
While the app is being loaded, the LED on the USB stick (in one of the
outer corners) will be turned off. tkey-runapp
also supports sending
a User Supplied Secret (USS) to the firmware when loading the app. By
adding the flag --uss
, you will be asked to type a phrase which will
be hashed to become the USS digest (the final newline is removed from
the phrase before hashing).
Alternatively, you may use --uss-file=filename
to make it read the
contents of a file, which is then hashed as the USS. The filename can
be -
for reading from stdin. Note that all data in file/stdin is
read and hashed without any modification.
The firmware uses the USS digest, together with a hash digest of the application binary, and the Unique Device Secret (UDS, unique per physical device) to derive secrets for use by the application.
The practical result for users of the signer app is that the ed25519 public/private keys will change along with the USS. So if you enter a different phrase (or pass a file with different contents), the derived USS will change, and so will your identity. To learn more, read the system_description.md (in the tillitis-key1 repository).
tkey-sign
assumes that tkey-runapp
has been used to load the
signer app and can be used like this (again, --port
is optional):
./tkey-sign file-with-message
If you're using real hardware, the LED on the USB stick is a steady green while the app is receiving data to sign. The LED then flashes green, indicating that you're required to physically touch the USB stick for the signing to complete. The touch sensor is located next to the flashing LED -- touch it and release. If running on QEMU, the virtual device is always touched automatically.
The program should eventually output a signature and say that it was verified.
When all is done, the LED on the hardware USB stick will be steady blue, indicating that it is ready to make (another) signature.
Note that to load a new app, the USB stick needs to be unplugged and
plugged in again. Similarly, QEMU would need to be restarted (Ctrl-a x
to quit). If you're using the setup with the USB stick sitting in
the programming jig and at the same time plugged into the computer,
then you need to unplug both the USB stick and the programmer. Or
alternatively run the reset-tk1
script (in the tillitis-key1 repo).
That was fun, now let's try the SSH agent!
This host program for the signer app is a complete, alternative SSH
agent with practical use. The signer app binary gets built into the
tkey-ssh-agent, which will load it onto USB stick when started. Like
the other host programs, tkey-ssh-agent tries to auto-detect serial
ports of TKey USB sticks. If more than one is found, or if you're
running on QEMU, then you'll need to use the --port
flag. An example
of that:
$ ./tkey-ssh-agent -a ./agent.sock --port /dev/pts/1
This will start the SSH agent and tell it to listen on the specified
socket ./agent.sock
.
It will also output the SSH ed25519 public key for this instance of the app on this specific TKey USB stick. So again; if the signer app binary, the USS, or the UDS in the physical USB stick change, then the private key will also change -- and thus the derived public key, your public identity in the world of SSH.
If you copy-paste the public key into your ~/.ssh/authorized_keys
you can try to log onto your local computer (if sshd is running
there). The socket path set/output above is also needed by SSH in
SSH_AUTH_SOCK
:
$ SSH_AUTH_SOCK=/path/to/agent.sock ssh -F /dev/null localhost
-F /dev/null
is used to ignore your ~/.ssh/config which could
interfere with this test.
The tkey-ssh-agent also supports the --uss
and --uss-file
flags,
as described for tkey-runapp
above.
You can use --show-pubkey
(short flag: -p
) to only output the
pubkey. The pubkey is printed to stdout for easy redirection, but some
messages are still present on stderr.
The Makefile
has an install
target that installs
tkey-ssh-agent and the above mentioned 60-tkey.rules
. First make
then sudo make install
, then sudo make reload-rules
to apply the
rules to the running system. This also installs a man page which
contains some useful information, try man ./system/tkey-ssh-agent.1
to read it before installing.
There is also a Work In Progress Debian/Ubuntu package which can be
build using the script debian/build-pkg.sh
.
The signer app normally requires the USB stick 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
.
The host apps will also stop displaying this requirement. Of course this changes the signer app binary and as a consequence the derived private key and identity will change.
This app generates a continuous stream of high quality random numbers
that can be read from the TKey's serial port device. In Linux for
example like: dd bs=1 count=1024 if=/dev/ttyACM0 of=rngdata
(or just
a plain cat
).
The app can be loaded and started using the tkey-runapp
as described
above.
The RNG is a Hash_DRBG implementation using the BLAKE2s hash function as primitive. The generator will extract at most 128 bits from each hash operation, using 128 bits as exclusive evolving state. The RNG will be reseeded after 1000 hash operations. Reseeding is done by extracting 256 entropy bits from the TK1 TRNG core. Note that the reseed rate can be changed during compile time by adjusting the RESEED_TIME define in main.c.
In blink/
there is also a very, very simple app written in
assembler, blink.bin
(blink.S) that blinks the LED.
Device apps and libraries are kept under the apps
directory. A C
runtime is provided as apps/libcrt0/libcrt0.a
which you can link
your C apps with.
RAM starts at 0x4000_0000 and ends at 0x4002_0000 (128 KB). The app will be loaded by firmware at RAM start. The stack for the app is setup to start just below RAM end (see apps/libcrt0/crt0.S). A larger app comes at a compromise of it having a smaller stack.
There are no heap allocation functions, no malloc()
and friends.
Special memory areas for memory mapped hardware functions are
available at base 0xc000_0000 and an offset. See
software.md
(in the tillitis-key1 repository), and the include file tk1_mem.h
.
If you're running the app on our qemu emulator we have added a debug port on 0xfe00_1000 (TK1_MMIO_QEMU_DEBUG). Anything written there will be printed as a character by qemu on the console.
qemu_putchar()
, qemu_puts()
, qemu_putinthex()
, qemu_hexdump()
and friends (see apps/libcommon/lib.[ch]
) use this debug port to
print stuff.
libcommon
is compiled with no debug output by default. Rebuild
libcommon
without -DNODEBUG
to get the debug output.