Blog

Getting started with the Tillitis TKey security token

by Eliot Roxbergh 2023-07-03

We recently got our hands on the newly released Tillitis TKey, which we will try for the first time in this blog post. The TKey is a small, configurable, completely open source security token.

In this blog we will show how we got started using the TKey without any prior knowledge of the device; that is, we will demonstrate how we wrote a simple application for the TKey, and also - to try a more realistic use case - we will show how we used the TKey for authentication on a Linux system. The latter was accomplished using already available apps provided by Tillitis, which we were able to configure to be used for PAM authentication.

Disclaimer: Assured has a long working relationship with both Mullvad and Tillitis.

Table of Contents

Anyway, what's a TKey?

The TKey is an open source security token created by the Mullvad spin-off Tillitis AB. Both its software and its hardware (schematics, PCB, and FPGA design) are open source and available under copyleft licenses. The TKey was released in April of 2023 with a price tag of 880 SEK (incl. tax) and available from the Tillitis store.

The TKey is a small device drawing less than 100mA. It provides a few basic functions (either in firmware or by the provided apps and libs) such as Ed25519 signing, key derivation, BLAKE2 hashing, and a good random number generator (TRNG). It also has a sensor for user confirmation. With these features, the TKey can be used for various functionalities, such as authentication (signing, SSH login, passkey, etc.), serving as a root of trust (sign/encrypt), or as a source of entropy (TRNG/CSPRNG).

The firmware is locked down and user applications need to be loaded onto the TKey after each boot, as it keeps no user-state between power cycles. However, it has an internal Unique Device Secret (UDS) which can be used to derive keys that are persistent across reboots, in addition to verify that the device and application have not been modified since last boot. See the Tillitis developer documentation for more technical details.

Tillitis provides a few applications, but it is also possible to write apps for the TKey using regular C (Tillitis provides a limited standard lib).

TKey


Hello, Apps

When connected to a system, the TKey is initially empty, awaiting a binary to run. So let us first build and transfer one of the provided applications to try it out.

Ubuntu 22.04 was used in this example.

Install dependencies

sudo apt install -y \
                 build-essential clang lld llvm bison flex libreadline-dev \
                 gawk tcl-dev libffi-dev git mercurial graphviz \
                 xdot pkg-config python3 libftdi-dev \
                 python3-dev libeigen3-dev \
                 libboost-dev libboost-filesystem-dev \
                 libboost-thread-dev libboost-program-options-dev \
                 libboost-iostreams-dev cmake libusb-1.0-0-dev \
                 ninja-build libglib2.0-dev libpixman-1-dev \
                 golang clang-format

# NOTE, clang >= 15 required, might need to be set manually if not default (e.g. on Ubuntu 22.04)
#   sudo apt install -y clang-15 lldb-15 lld-15
#   sudo update-alternatives --install /usr/bin/clang      clang       /usr/bin/clang-15  200
#   sudo update-alternatives --install /usr/bin/ld.lld     ld.lld      /usr/bin/ld.lld-15  200
#   sudo update-alternatives --install /usr/bin/llvm-ar    llvm-ar     /usr/bin/llvm-ar-15 200
#   sudo update-alternatives --install /usr/bin/llvm-objcopy  llvm-objcopy /usr/bin/llvm-objcopy-15 200

Clone and build TKey libraries and apps

# build libraries (used in linking, see Makefile)
git clone https://github.com/tillitis/tkey-libs
cd tkey-libs
make all
cd ..

# build runapp (for transferring our app to tkey)
git clone https://github.com/tillitis/tillitis-key1-apps
cd tillitis-key1-apps
make apps
make tkey-runapp
chmod +x tkey-runapp
cd ..

Get access to the TKey device

# Give the active user permission to interact with the device,
#   for instance by adding them to the dialout group

#TKey is usually found here and owned by group dialout
ls -l /dev/ttyACM0

#add user to dialout
sudo usermod -a -G dialout $USER

#relogin or run this command
newgrp dialout

Transfer the app we want to run to the TKey

./tillitis-key1-apps/tkey-runapp tillitis-key1-apps/apps/blink/app.bin

The blink application should now have been transferred to the TKey, and the TKey should be happily blinking.

The code for this application would look something like this, simple enough:

#include <types.h>
#include <tk1_mem.h>

#define SLEEPTIME 100000
#define LED_RED   (1 << TK1_MMIO_TK1_LED_R_BIT)
#define LED_GREEN (1 << TK1_MMIO_TK1_LED_G_BIT)
#define LED_BLUE  (1 << TK1_MMIO_TK1_LED_B_BIT)

static volatile uint32_t *led = (volatile uint32_t *)TK1_MMIO_TK1_LED;

void sleep(uint32_t n)
{
    for (volatile int i = 0; i < n; i++);
}

int main(void)
{
    for (;;) {
        *led = LED_RED;
        sleep(SLEEPTIME);
        *led = LED_GREEN;
        sleep(SLEEPTIME);
        *led = LED_BLUE;
        sleep(SLEEPTIME);
    }
}

Check the Tillitis developer documentation for source code


Hello, Developer

The applications are currently written in C, using the provided C runtime and limited standard library. The provided apps use or provide features such as TRNG, cryptographic operations (hashing, signing, and key derivation), support for the touch sensor, and two-way communication with a client application on the host computer. Based on the provided apps (tillitis-key1-apps/apps/*.c), we made our own simple program for the TKey.

We wrote a very simple application, shown below, that uses the onboard TRNG to pick a winner among two "players" (red and blue). The score is accumulated, and the leader is continuously shown on LED as the color red, blue, or white for tie. As you notice, the code is just regular C using the TKey provided addresses for reading/writing LEDs and TRNG values.

#include <types.h>
#include <tk1_mem.h>

#define SLEEPTIME  100000
#define LED_BLACK  0
#define LED_RED    (1 << TK1_MMIO_TK1_LED_R_BIT)
#define LED_GREEN  (1 << TK1_MMIO_TK1_LED_G_BIT)
#define LED_BLUE   (1 << TK1_MMIO_TK1_LED_B_BIT)
#define LED_WHITE  (LED_RED | LED_GREEN | LED_BLUE)
#define UINT32_MAX (4294967295U)
#define MAX_SCORE  UINT32_MAX

static volatile uint32_t *led = (volatile uint32_t *)TK1_MMIO_TK1_LED;
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;

enum player {
    red = LED_RED,
    blue = LED_BLUE,
    tie = LED_WHITE,
};

void sleep(uint32_t n)
{
    for (volatile int i = 0; i < n; i++);
}

void toggle_rgb(void)
{
    *led = LED_RED;
    sleep(SLEEPTIME);
    *led = LED_GREEN;
    sleep(SLEEPTIME);
    *led = LED_BLUE;
    sleep(SLEEPTIME);
}

/*
 * Perform coin toss
 *  Who will win today?
 */
enum player get_winner(void)
{
    uint32_t rand = 0;
    sleep(SLEEPTIME*4);

    /* wait for entropy if necessary */
    while(*trng_status == 0) {
        *led = LED_GREEN;
        sleep(SLEEPTIME*10);
    }

    //todo: should use Hash_DRBG instead of raw TRNG
    rand = *trng_entropy;
    sleep(SLEEPTIME);

    /*
     * Are you readyyy?
     *  Time for coin flip!
     */

    //todo: this only checks last bit
    if (rand % 2 == 0) {
        return red;
    } else {
        return blue;
    }
}

int main(void)
{
    /*
     *  Welcome to coin race!
     */
    enum player winner;
    int32_t score_red=0, score_blue = 0;

    *led = LED_WHITE;
    sleep(SLEEPTIME);
    toggle_rgb();
    toggle_rgb();

    /* Start race, current leader is shown on LED (white for equal) */
    while (1) {
        winner = get_winner();
        if (winner == red) {
            score_red++;
        } else {
            score_blue++;
        }

        if (score_red > score_blue) {
            *led = red;
        } else if (score_blue > score_red) {
            *led = blue;
        } else {
            *led = tie;
        }

        if (score_red == MAX_SCORE || score_blue == MAX_SCORE) {
            /* Congratulations to the final winner! */
            while (1);
        }
        sleep(SLEEPTIME*10);
    }
}

The application can then be built for the TKey using the same Make command as provided in tkey-apps.

Finally, transfer the application to the TKey by running ./tillitis-key1-apps/tkey-runapp coin_race.bin.


Hello, Real World

Without developing any new applications, what can we use the TKey for today? One such thing is SSH authentication using the tkey-ssh-agent.

The tkey-ssh-agent is an application (which runs simultaneously on the TKey and the computer) that enables us to use the TKey for key-based authentication, instead of directly interacting with a private key on the local computer. That is, the TKey is taking on a role similar to that of an HSM or TPM, although technically the private keys are re-derived on each boot-up as it has no lasting user state.¹

With an ssh-agent, we can however do more than just SSH, for instance, there is a PAM module (pam_ssh_agent_auth) that allows the use of an ssh-agent for authentication. Let's see if we can use this to authenticate to our Linux device using the TKey. We could then use the TKey in conjunction with a password (2FA), or for convenience, it could provide an alternative to passwords for some applications.

Therefore, in this chapter we will:

  • first, setup the TKey for SSH connections (where signing is done on the TKey with its key), and;
  • second, by the same mechanism, we will use the TKey for authentication to PAM: replacing the need to enter a password for the sudo command.

¹ The keys are generated based on the internal Unique Device Secret, a User Supplied Secret, and the application hash; and by using the application hash in key generation, it is ensured that the application uploaded has not been tampered with since last time.

The TKey ssh-agent

The tkey-ssh-agent runs in the background, and once called upon via its socket, it transfers the application to the TKey and starts the authentication procedure. The TKey then generates an ed25519 key-pair and performs cryptographic signing as necessary.

Specifically, the TKey generates a key-pair based on its internal identifier (i.e. Unique Device Secret, UDS), the hash of the TKey app, and optional user input (i.e. User Supplied Secret, USS). For the tkey-ssh-agent the USS is optional, but if used, it functions as a passphrase; otherwise the private keys won't match.

SSH: use TKey signing via ssh-agent

  • Build the tkey-ssh-agent, and download Golang >= 1.19 if not already installed
cd tillitis-key1-apps
# ---> DL golang: https://go.dev/dl/
#   tar xvf go*.tar.gz
#   export PATH="$PWD/go/bin/:$PATH"
make all
  • Install SSH
sudo apt install -y ssh
sudo systemctl disable ssh
  • Add the TKey internal public key to the servers you'd like to access over SSH (here your local machine)
# add pubkey to ~/.ssh/authorized_keys
#   example line: ssh-ed25519 AAAA5Ns36SKds24ovMDXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX4Asdv/U My-Tillitis-TKey
./tkey-ssh-agent  --show-pubkey 1>>  ~/.ssh/authorized_keys

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
sudo systemctl start ssh
  • Start the SSH agent. When a request is made to the agent socket, the application will be transferred to the TKey and signing can take place
./tkey-ssh-agent -a ~/.ssh/agent.sock &
  • Finally, connect to your local machine using SSH (via this tkey-ssh-agent socket). Run the following command and press on the TKey to perform signing and finish the login (as prompted by the tkey-ssh-agent program).
# (add -vvv for verbose client, and if issues check "journalctl -u ssh.service")
SSH_AUTH_SOCK="$HOME/.ssh/agent.sock" ssh -F /dev/null $USER@localhost

sudo: use TKey for authentication

Once the ssh-agent is up and running, we can use it for more than just regular ssh, such as authenticating to PAM. It is thereby possible to use the TKey for different steps in the Linux authentication, here we will use the TKey as an alternative to passwords for running the 'sudo' command.

  • Enable the tkey-ssh-agent to automatically start for the current user, creating a socket at login
cd tillitis-key1-apps
make all
sudo make install
cd ..

systemctl --user start tkey-ssh-agent.service
systemctl --user enable tkey-ssh-agent.service
#socket should now be here: `/run/user/$UID/tkey-ssh-agent/sock`
  • We set the authorized keys path to /etc/ssh/sudo_authorized_keys, keeping it separate from ssh's trusted keys. Note, any keys listed in this file will be used to gain sudo privileges in this example.
#(if not already copied, you may also use `ssh-add -L` to get the public key from the ssh-agent)
sudo cp ~/.ssh/authorized_keys /etc/ssh/sudo_authorized_keys
  • Install the PAM module ssh-agent-auth
sudo apt install libpam-ssh-agent-auth
  • Add the alternative authentication method (ssh-agent-auth) for sudo by adding the PAM module as a step in its PAM config (i.e. /etc/pam.d/sudo). The tkey can then be used in conjunction to, or instead of a password, by adding a new line before the password authentication step: pam_unix.so (common-auth). To use the tkey in conjunction with a password we set the module to be "required", for instance auth required pam_ssh_agent_auth.so file=/etc/ssh/sudo_authorized_keys. On the other hand, as shown in the snippet below, we can instead use the more complicated PAM syntax (e.g. [success=2 default=ignore]), to on success skip the two next modules ignoring the password requirement, and on failure ignore the result of this module and continue with password authentication instead. It should be possible to use this authentication for any service using PAM, not only sudo - see other configs in /etc/pam.d/
    • as long as $SSH_AUTH_SOCK is set. The new configuration is applied as soon as the file is saved, and in this case, the new authentication step will be checked once 'sudo' is run.
#/etc/pam.d/sudo

# ...

#add this line before pam_unix.so and change success=2 to the number of modules to skip on success
#note: "$SSH_AUTH_SOCK" must always point to the socket file
auth [success=2 default=ignore] pam_ssh_agent_auth.so file=/etc/ssh/sudo_authorized_keys debug

# (in this example we want to skip these 2 modules on success, replacing password authentication with TKey)
# check password
auth    [success=1 default=ignore]    pam_unix.so nullok
# default deny
auth    requisite            pam_deny.so

# -> (success: either tkey or password authentication succeeded)

# ...
  • Contrary to Ubuntu manpage, you need to add the following to /etc/sudoers, by running sudo visudo /etc/sudoers. This is necessary as to properly inherit SSH_AUTH_SOCK. (The Ubuntu 22.04 system uses sudo v1.9.9 and still requires this step)
Defaults        env_keep += "SSH_AUTH_SOCK"
  • We are now ready to try to authenticate using the TKey
SSH_AUTH_SOCK=/run/user/$UID/tkey-ssh-agent/sock sudo echo SUCCESS!
  • Finally, let's permanently set $SSH_AUTH_SOCK. This variable must point to the active ssh-agent socket, created by the tkey-ssh-agent service. One alternative is to set the variable in /etc/profile or ~/.profile.
echo "export SSH_AUTH_SOCK=/run/user/$UID/tkey-ssh-agent/sock" >> ~/.profile
source ~/.profile
sudo -k #remove cached credentials
sudo echo SUCCESS!

If issues arise: check log: e.g. sudo journalctl -f or /var/log/auth.log and the status of tkey with systemctl status tkey-ssh-agent.

The most common error is "pam_ssh_agent_auth: No ssh-agent could be contacted", usually regarding the environment variable SSH_AUTH_SOCK not set or inherited properly. Make sure that it's set and that the socket indeed exists, for the sudo command as shown above the configuration option 'env_keep' is also required.

Setting SSH_AUTH_SOCK can be done in several ways depending on your system or who calls it. In addition to regular export in e.g. ~/.profile, this can also be done in a PAM configuration file¹ such as in /etc/security/pam_env.conf or /etc/environment, or by loading your own with _pam_env². It is also possible to use systemd's environment.d. See also an overview of environment variables.

¹ note: pam has a special syntax for env config, for example add the following line to the environment .conf file (e.g. /etc/security/pam_env.conf):
SSH_AUTH_SOCK DEFAULT=/home/user/.ssh/agent.sock

² Optionally, you can create your own config file instead and point to it from /etc/pam.d/sudo:
session required pam_env.so readenv=1 envfile=my-config-file.conf user_readenv=0


Conclusions

All in all, the TKey shows promise and it is exciting to see new open source security devices on the market. With this device recently released, it is still early days and more apps are being worked on. Still, after only spending a day with the TKey, we were able to write a simple application which for instance uses the internal TRNG as well as to use the provided ssh-agent app to perform SSH authentication which we configured to perform PAM authentication (2FA) on Linux.

As mentioned earlier, the TKey needs to be loaded with an application each time it is connected. However, in our use case, this did not pose an issue as the ssh-agent background service simply setups the TKey when a call is made.

We did not go into much technical detail of the inner workings of the TKey in this introductory post. The following resources from Tillitis could be of interest: