As a reminder, this module guides you through creating a USB security key: a USB device that can be connected to your computer and authenticate you to some service.
At this point, we have configured the Tock kernel to provide the baseline resources necessary to implement the USB security key and use it with real services. However, we still need to actually implement the security key's operational logic. This submodule will guide you through creating a userspace application that follows the HOTP protocol.
One open standard for implementing USB security keys is HMAC-based One-Time Password (HOTP). It generates the 6 to 8 digit numeric codes which are used as a second-factor for some websites.
These security keys typically do not just calculate HOTP codes, but can also enter them to your computer automatically. We will also enable that functionality by having our devices function as a USB HID keyboard device as well. This means that when plugged in through the proper USB port, it appears as an additional keyboard to your computer and is capable of entering text.
Tock applications look much closer to applications on traditional OSes than to normal embedded software. They are compiled separately from the kernel and loaded separately onto the hardware. They can be started or stopped individually and can be removed from the hardware individually. Moreover, the kernel decides which applications to run and what permissions they should be given.
Applications make requests to the OS kernel through system calls. Applications
instruct the kernel using "command" system calls, and the kernel notifies
applications with "upcalls". Importantly, upcalls never interrupt a running
application. The application must
yield to receive upcalls (i.e. callbacks).
The userspace library ("libtock") wraps system calls in easier to use functions.
Often the library functions include the call to
yield and expose a synchronous
driver interface. Application code can also call
yield directly as we will do
in this module.
This stage builds up to a full-featured HOTP key application. We'll start with a basic HOTP application which has a pre-compiled HOTP secret key. Then, each milestone will add additional functionality:
- Milestone one adds user input to reconfigure the HOTP secret.
- Milestone two adds persistent storage for the HOTP information so it is remembered across resets and power cycles.
- Milestone three adds support for multiple HOTP secrets simultaneously.
We have provided starter code as well as completed code for each of the milestones. If you're facing some bugs which are limiting your progress, you can reference or even wholesale copy a milestone in order to advance to the next parts of the tutorial.
There are two steps to check before you begin:
Make sure you have compiled and installed the Tock kernel with the USB HID, HMAC, and KV drivers on to your board.
Make sure you have no testing apps installed. To remove all apps:
We'll start with the starter code which implements a basic HOTP key.
libtock-c, navigate to
This contains the starter code for the HOTP application. It has a hardcoded HOTP secret and generates an HOTP code from it each time the Button 1 on the board is pressed.
Compile the application and load it onto your board. In the app directory, run:
make tockloader install
To see console output from the application, run
tockloader listenin a separate terminal.
TIP: You can leave the console running, even when compiling and uploading new applications. It's worth opening a second terminal and leaving
tockloader listenalways running.
Since this application creates a USB HID device to enter HOTP codes, you'll need a second USB cable which will connect directly to the microcontroller. If you are using the nRF52840dk, plug the USB cable into the port on the left-hand side of the nRF52840DK labeled "nRF USB".
After attaching the USB cable, you should restart the application by hitting the reset button (on the nRF52840DK it is labeled "IF BOOT/RESET").
To generate an HOTP code, press the first button ("Button 1" on the nRF5240DK). You should see a message printed to console output that says
Counter: 0. Typed "750359" on the USB HID the keyboard.
The HOTP code will also be written out over the USB HID device. The six-digit number should appear wherever your cursor is.
Verify the HOTP values with https://www.verifyr.com/en/otp/check#hotp. Go to section "#2 Generate HOTP Code". Once there, enter:
- "test" as the HOTP Code to auth
- The current counter value from console as the Counter
- "sha256" as the Algorithm
- 6 as the Digits
Click "Generate" and you'll see a six-digit HOTP code that should match the output of the Tock HOTP app.
The source code for this application is in the file
This is roughly 300 lines of code and includes Button handling, HMAC use and the
HOTP state machine. Execution starts at the
main() function at the bottom of
Play around with the app and take a look through the code to make sure it makes sense. Don't worry too much about the HOTP next code generation, as it already works and you won't have to modify it.
Checkpoint: You should be able to run the application and have it output HOTP codes over USB to your computer when Button 1 is pressed.
The first milestone is to modify the HOTP application to allow the user to set a
secret, rather than having a pre-compiled default secret. Completed code is
available in the
hotp_milestone_one/ folder in case you run into issues.
Modify the code in
main()to detect when a user wants to change the HOTP secret rather than get the next code.
The simplest way to do this is to sense how long the button is held for. You can delay a short period, roughly 500 ms would work well, and then read the button again and check if it's still being pressed. You can wait synchronously with the
delay_ms()function and you can read a button with the
Note that buttons are indexed from 0 in Tock. So "Button 1" on the hardware is button number 0 in the application code. All four of the buttons on the nRF52840DK are accessible, although the
initialize_buttons()helper function in main.c only initializes interrupts for button number 0. (You can change this if you want!)
An alternative design would be to use different buttons for different purposes. We'll focus on the first method, but feel free to implement this however you think would work best.
For now, just print out a message when you detect the user's intent. Be sure to compile and upload your modified application to test it.
Next, create a new helper function to allow for programming new secrets. This function will have three parts:
The function should print a message about wanting input from the user.
- Let them know that they've entered this mode and that they should type a new HOTP secret.
The function should read input from the user to get the base32-encoded secret.
You'll want to use the Console functions
getch()can read characters of user input while
putnstr()can be used to echo each character the user types. Make a loop that reads the characters into a buffer.
Since the secret is in base32, special characters are not valid. The easiest way to handle this is to check the input character with
isalnum()and ignore it if it isn't alphanumeric.
When the user hits the enter key, a
\ncharacter will be received. This can be used to break from the loop.
The function should decode the secret and save it in the
- Use the
program_default_secret()implementation for guidance here. The
default_secrettakes the place of the string you read from the user, but otherwise the steps are the same.
- Use the
Connect the two pieces of code you created to allow the user to enter a new key. Then upload your code to test it!
- You can test that the new secret works with https://www.verifyr.com/en/otp/check#hotp as described previously.
Checkpoint: Your HOTP application should now take in user-entered secrets and generate HOTP codes for them based on button presses.
The second milestone is to save the HOTP struct in persistent flash rather than
in volatile memory. After doing so, the secret and current counter values will
persist after resets and when the USB device is unplugged. We'll do the saving
to flash with the Key-Value driver, which allows an application to save
information as key-value pairs. Completed code is available in
hotp_milestone_two/ in case you run into issues.
In the HOTP application code we will store the persistent key data as the "value" in a key-value pair.
Start by writing a function which saves the
hotp_key_tobject to a specific key (perhaps "hotp"). Use the
Now write a matching function which reads the same key to load the key data from persistent storage. Use the
Make sure to update the key-value pair whenever part of the HOTP key is modified, i.e. when programming a new secret or updating the counter.
Upload your code to test it. You should be able to keep the same secret and counter value on resets and also on power cycles.
- There is an on/off switch on the top left of the nRF52840DK you can use for power cycling.
Checkpoint: Your application should now both allow for the configuring of HOTP secrets and the HOTP secret and counter should be persistent across reboots.
The third and final application milestone is to add multiple HOTP keys and a
method for choosing between them. This milestone is optional, as the rest of
the tutorial will work without it. If you're short on time, you can skip it
without issue. Completed code is available in
hotp_milestone_three/ in case
you run into issues.
The recommended implementation of multiple HOTP keys is to assign one key per button (so four total for the nRF52840DK). A short press will advance the counter and output the HOTP code while a long press will allow for reprogramming of the HOTP secret.
The implementation here is totally up to you. Here are some suggestions to consider:
Select which key you are using based on the button number of the most recent press. You'll also need to enable interrupts for all of the buttons instead of just Button 1.
Make the HOTP key into an array with up to four slots. Choose different key names for storage.
Having multiple key slots allows for different numbers of digits for the HOTP code on different slots, which you could experiment with.
Checkpoint: Your application should now hold multiple HOTP keys, each of which can be configured and is persistent across reboots.