Tillitis Security Bulletin 240115-1


Severity: Low.

A vulnerability has been found in tkey-device-signer and verisigner that makes it possible to disclose portions of the TKey’s data in RAM over the USB interface. To exploit the vulnerability an attacker needs to use a custom client application and to touch the TKey.

The threat model defines a set of assets, attack types and threat actors.
This vulnerability analysis shows that the attack is of software type and,

  • a threat actor can read portions of RAM content, but no sensitive assets, from a device that runs tkey-device-signer or verisigner that have been compiled by Tillitis.
  • a skilled threat actor* could potentially read out asset information from a device that runs tkey-device-signer or verisigner that have been compiled by other parties and generates a different SHA512 digest, see reproducible builds below.

*type 1, 2 or 3 as defined in the threat model.

User action

Tillitis recommends:

  • Update any client application* that uses verisigner to tkey-device-signer version 1.0.0. verisigner is being deprecated.
  • Update any client application* that uses tkey-device-signer to version 1.0.0.
  • If you are building tkey-device-signer from source code, Tillitis recommends using the provided OCI image.

*Custom client applications using tkey-device-signer are also recommended to be updated.

Note: Updating the device application changes the identity, meaning that the Ed25519 key pair also changes. All services using your current identity need to be updated with the new identity. See this guide.

Affected applications

All client applications running tkey-device-signer version 0.0.7 and 0.0.8.
From Tillitis this includes:

  • tkey-ssh-agent v0.0.6 and earlier
    embedding tkey-device-signer v0.0.7
  • tkey-sign-cli v0.0.8
    embedding tkey-device-signer v0.0.8

All client applications running verisigner v0.0.3 and earlier.
From Tillitis this includes:

  • tkey-verification v0.0.2.
    embedding verisigner v0.0.3.


The following release contain security updates that address the vulnerability described in this bulletin:

The following client applications are updated to embed tkey-device-signer v1.0.0:

For user compiled applications, see section Reproducible builds below.

Note: verisigner is going to be deprecated in favor of using tkey-device-signer v1.0.0 in the forthcoming tkey-verification v1.0.0.

Technical details

The vulnerability is of type CWE-367: Time-of-check Time-of-use (TOCTOU). If this is exploited it allows for a CWE-125: Out-of-bounds Read.

An exploit is made by setting an increasingly larger size of the message to sign (using CMD_SET_SIZE) and relying on the TOCTOU bug to get additional data in the message.

First the message size is set to 4096 bytes (maximum size), then immediately a new message size of 4097 bytes is set. The device app will return an error but still use the new size (4097). Another variable that keeps track of what is left to be sent by the client keeps the old value (4096).

When 4096 bytes of data is sent from client to TKey, this creates a signature of the sent data plus the extra byte in memory according to the new message size (4097). Repeating this method again, but using a size of 4098 bytes, will include yet another byte of RAM in the signature. This is continued with 4099, 4100, …

The signature returned is computed over an unknown byte every time. One can iterate over every possible value of the last byte (brute forcing) and do a signature verification. When the signature verifies correctly the unknown byte is revealed.

Eventually the signing operation’s own memory is part of the message to sign. The message itself will change during the signing operation, making it much harder and probably impossible to produce verifiable signatures over a known message. This stops the exploit.

The signing context stops the exploit

There are a few interesting assets in RAM:

  1. The message, since this is the start of the data included in the signature.
  2. The private key.
  3. The context of the signing operation.

The locations in memory are:

Versionlocation of messagelocation of private keylocation of signing context
0.0.7, verisigner-v0.0.3message = 0x4001ec20local_cdi = 0x4001ebc0ctx = 0x4001fd40
0.0.8message =
secret_key = 0x4001edf8r = 0x4001ebe0*
*location of the variable r , which is part of the signing context.

The memory visualized for v0.0.7 and verisigner-0.0.3:

--------------------------- <--- 0x40020000
| STACK                   |
| ..                      |
| Signing context         | <--- An exploit stops here
| ..                      |
| message ↑               | <--- start of exploit, grows
| ..                      |       towards higher addresses
|                         |
| Private key             |
| ..                      |   
| - - - - - - - - - - - - |
| ..                      |
|                         |
| Unused area,            |
| randomized at power up  |
|                         |
| ..                      |
| - - - - - - - - - - - - |
| APP                     |
--------------------------- <--- 0x40000000

The memory visualized for v0.0.8:

--------------------------- <--- 0x40020000
| STACK                   |
| ..                      |
| message ↑               | <--- start of exploit, grows
| ..                      |       towards higher addresses                                        |                         |
| Private key             |
| ..                      |
| Signing context         | <--- An exploit stops here
| ..                      |   
| - - - - - - - - - - - - |
| ..                      |
|                         |
| Unused area,            |
| randomized at power up  |
|                         |
| ..                      |
| - - - - - - - - - - - - |
| APP                     | <--- Wraps and continue to read upwards
--------------------------- <--- 0x40000000

The message buffer contains the data to be signed and grows towards higher addresses, so the out-of-bounds read occur on addresses above the message buffer’s end. In order to include the private key in the signing operation, all the previous content in RAM needs to be leaked first.

The signing context is located in between the message and the private key for all versions, v0.0.7, v0.0.8, and verisigner-v0.0.3. The difference in v0.0.8 is the need to wrap around and continue to read from the start of RAM. The possibility to wrap around and continue to read from the beginning of RAM is an unwanted behavior in the FPGA design. This is addressed in release TK1-2024.03.

The location of the signing context on the stack is crucial for stopping an exploit. The signing context is used by the Ed25519 signing operation to store intermediate calculations. In order to leak it you have to include the signing context as part of the message being signed. However, during the signing the message is used in two separate hash operations. Since the execution is in series this means that the context, which is included in the message to be signed, will change between these two function calls. This makes it impossible to create a valid signature over a known message.

The implementations differ, but the same operations are performed. The details below are from v0.0.8, but the same general idea applies for v0.0.7 and verisigner-v0.0.3.

When generating a signature the function call looks like this:

static void ed25519_dom_sign(u8 signature [64], 
                             const u8 secret_key[32],
                             const u8 *dom, size_t dom_size,
                             const u8 *message, 
                                          size_t message_size)
	u8 a[64];  // secret scalar and prefix
	u8 r[32];  // secret deterministic "random" nonce
	u8 h[32];  // publically verifiable hash of the                                           
                      message (not wiped)
	u8 R[32];  // first half of the signature (allows 
                      overlapping inputs)
	const u8 *pk = secret_key + 32;

	crypto_sha512(a, secret_key, 32);
	crypto_eddsa_trim_scalar(a, a);
	hash_reduce(r, dom, dom_size, a + 32, 32, message,                     message_size, 0, 0);
	crypto_eddsa_scalarbase(R, r);
	hash_reduce(h, dom, dom_size, R, 32, pk, 32, message,                     message_size);
	COPY(signature, R, 32);
	crypto_eddsa_mul_add(signature + 32, h, a, r);


The interesting section in this function is:

hash_reduce(r, dom, dom_size, a + 32, 32, message, 
            message_size, 0, 0);
crypto_eddsa_scalarbase(R, r);
hash_reduce(h, dom, dom_size, R, 32, pk, 32, message, 

The message, which is a pointer to a location on the stack, is used twice in order to calculate the signature. The variable r, which is of type u8 r[32], stores the return value of hash_reduce() on the stack. This gives that, when the context is included in the message to be signed, the content of message will change before the next function call of hash_reduce(). Also note that a, which is used in the first hash_reduce(), is derived from the secret key so r cannot be considered guessable or known.

Reproducible builds

The exploit is effectively stopped by the order of the data on the stack. C compilers don’t provide any guarantee for how the data is ordered, and can be vastly different depending on compiler, compiler version, and compiler optimization level.

Tillitis have reproducible builds and verification of both tkey-device-signer and verisigner in place, so the analysis above covers binaries built with other compiler versions as long as they have the same SHA512 digests. The digests are included in the repositories. The recommended way of building is to use the OCI image, tkey-builder, with the right version for the device application’s tag. See the device applications’ documentation in the repositories for more information.


  • January 15, 2024 – Tillitis receives the report through
  • January 17, 2024 – Confirmed that an out-of-bounds read is possible.
  • January 29, 2024 – Confirmed that this also affects verisigner in tkey-verification.
  • March 25, 2024 – Released new version of hardware design with hardware RAM wraparound protection, TK1-24.03.
  • April 16, 2024 – Released new versions of tkey-device-signer, including the TOCTOU fix and hardening with explicit protocol state machine, as well as official client apps using tkey-device-signer.


We would like to thank the security researcher Sergei Volokitin of Hexplot, who discovered the vulnerability and reported it through our bug bounty program.

Change log

2024-04-16 17:00 CEST: Initial publication