Turns your cheap STM32F103 or EFM32HG board into U2F token.
U2F-TOKEN is known to work on:
- Tomu board (EFM32HG) is an excellent device to use for U2F as it can sit entirely inside of your USB port (and it is opensource)
- Blue pill (STM32F103) as well as Black pill
- Countless ST32F103 based Chinese St-Link V2 clones can be turned into U2F devices with U2F-TOKEN
- Variety of Maple Mini clones which can be found on Aliexpress
On Linux you need to add the following rules to be able to use your device as
non root user. Create the file /etc/udev/rules.d/10-u2f-token.rules
(as root)
and paste in the following lines:
ACTION=="add|change", KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="0e90", TAG+="uaccess"
ACTION=="add|change", SUBSYSTEM=="usb", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="0e90", TAG+="uaccess"
Snap has additional security measures so that you will need to add a tag to the hidraw rule for each snap app that you want to have access to the token.
For Chromium and Firefox (snap since ubuntu 21.10) this then reads:
ACTION=="add|change", KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="0e90", TAG+="uaccess", TAG+="snap_firefox_firefox", TAG+="snap_chromium_chromedriver"
Python 3.6 or later and pip are needed. You will also need the hidapi
library:
- To install on OS X run
brew install hidapi
- To install on Ubuntu run
sudo apt install libhidapi-hidraw0 python3-hid
- To install on Archlinux, run
pacman -Sy hidapi
If you are on Linux, you will also need to install the aforementioned Udev rules.
Download the binary suitable for your board at releases page.
Binaries for Tomu are built with bootloader support, use the following command to flash the firmware:
dfu-util -d 1209:70b1 -D u2f-TOMU.bin
Binaries for STM32 boards are built without bootloader support, you need to flash the firmware using SWD or JTAG interface. Example using OpenOCD and STLINK-V2:
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c 'init' -c 'halt' -c 'flash write_image erase unlock u2f-BLUE_PILL.bin 0x08000000' -c 'exit'
(replace BLUE_PILL with appropriate board name)
After flashing device with binary it requires to be initialized. Release binaries come with readout protection enabled and without attestation certificate provisioned. To initialize the device, clone this repository and run (Python 3.6+ and the hidapi library are required):
pip3 install -r requirements.txt --user
cd src/cert
python3 certtool init
You will see something similar to:
Trying to initialize device HIDDevice:
USB_16d0_0e90_14100000 | 16d0:e90 | unknown | U2F-token (STM32) | 1.00
release_number: 256
usage_page: 61904
usage: 1
interface_number: -1
Success
The above command will upload pre-generated attestaion.der
from this
repository to the device. If for whatever reason you want to use your own
certificate, tweak and run ./gen.sh
to generate it.
Test your key with latest Chrome or Firefox browser using this page.
Install and setup Command Line tools for Xcode on macOS.
Install build-essentials package on Debian/Ubuntu:
sudo apt install build-essential
Installing on macOS with homebrew:
brew tap osx-cross/arm
brew install arm-gcc-bin
Installing on Debian/Ubuntu:
sudo apt-add-repository ppa:team-gcc-arm-embedded/ppa
sudo apt update
sudo apt install gcc-arm-embedded
For Kali Linux:
apt install gcc-arm-none-eabi
MacOS comes with openssl/libressl installed out of the box.
Installing on Debian/Ubuntu:
sudo apt install openssl
There is a tiny python script used to convert certificates generated by OpenSSL from DER format into C-array. It depends on asn1crypto package.
To install with pip:
pip install --user --upgrade asn1crypto
Installing on macOS with homebrew:
brew install open-ocd
Installing on Debian/Ubuntu:
sudo apt install openocd
git clone https://github.com/gl-sergei/u2f-token.git
cd u2f-token
git submodule update --init
cd src
make TARGET=<target>
will produce firmware file build/u2f.bin
.
Supported targets are:
Use BLUE_PILL or BLACK_PILL for generic STM32F103 board without push button.
If build was unsuccessful for whatever reason and you want to start from scratch you may want to run
make clean
to remove all object files, or
make certclean
to remove generated certificates, or
make distclean
to remove board.h
symlink, or even all of the above.
Make sure to enable readout protection if you are going to use your device as
2FA for your accounts. Build firmware with ENFORCE_DEBUG_LOCK=1
:
make clean
make TARGET=<target> ENFORCE_DEBUG_LOCK=1
Firmware generates EC private key on its first boot and erases it when it enters the bootloader. You may want to backup your private key and make it survive firmware upgrade. To achieve this, generate the key on your host machine and inject it into the firmware binary.
Generate your private key:
openssl ecparam -name prime256v1 -genkey -noout -outform der -out key.der
You may want to encrypt your key.der
and back it up.
Check device's authentication counter if you are going to perform the firmware
upgrade. You can see it in Yubikey demo site output. For the new device, you can
skip ctr
parameter all together or set it to 1. Let's say the current counter
value is 1000.
Use this command to patch firmware binary:
./inject_key.py --key key.der --ctr 1001
Start OpenOCD:
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg
On other terminal run:
telnet localhost 4444
> reset halt
> stm32f1x unlock 0
> reset halt
> program build/u2f.elf verify reset
> shutdown
Providing you have Toboot installed:
dfu-util -v -d 1209:70b1 -D build/u2f.bin
After flashing device you still need to initialize device as described in Initialize device
U2F-TOKEN is using Neug to generate high quality random numbers. The source of entropy is built-in Analog to Digital Converter (both STM32F103 and EFM32HG have ones). 32-bit ADC output is passed through CRC32 35 times to get 1120 bit input for SHA256-based whitening element.
I ran this suite on 1.7M of raw RNG output from Tomu board.
SUMMARY
-------
monobit_test 0.119269669219 PASS
frequency_within_block_test 0.11518538339 PASS
runs_test 0.0626194973829 PASS
longest_run_ones_in_a_block_test 0.585067135452 PASS
binary_matrix_rank_test 0.862015222632 PASS
dft_test 0.965404804209 PASS
non_overlapping_template_matching_test 1.00000631693 PASS
overlapping_template_matching_test 0.35076924588 PASS
maurers_universal_test 0.999954925686 PASS
linear_complexity_test 0.906328320146 PASS
serial_test 0.159775233458 PASS
approximate_entropy_test 0.15960828003 PASS
cumulative_sums_test 0.0774471722878 PASS
random_excursion_test 0.251774950817 PASS
random_excursion_variant_test 0.0871834280054 PASS
Device key is a private key for ECDSA p256r1. Any 256 bits are valid key. It is generated using RNG on first run and stored in device's flash memory. It should not leave the device. You must protect your device's flash from readout to protect device key (see below).
U2F protocol specifies two actions - register and authenticate.
Register takes appID and challenge from the caller and returns key handle, public key corresponding to that key handle and attestation certificate. No one usually verify the validity of attestation certificate.
Authenticate takes the key handle, appID and another challenge, then it signs both with the private key corresponding to the key handle. Caller then able to check if signature has been made by the same device using a public key from register step. Device in turn has to make sure to refuse signing requests for unknown key handles and don't mess private keys corresponding to different key handles.
If embedded devices would have unlimited storage, the best would be to store all pairs of key handle and private key on the device. But it is not the case, so the private key actually computed based on key handle and device key.
Here is how it is done by U2F-TOKEN firmware:
- Generate random
nonce
(32 bytes) - Compute
HMAC_SHA265(app_id + nonce)
using device key. Result becomes a private key (32 bytes) - Compute
HMAC_SHA256(private_k + app_id) + nonce
. Result becomes a key handle (64 bytes) - Compute public key for the private key (it's a nice feature of ECC that public key can be easily computed for given private key, but it is not that easy to do vice versa) and hand it out to the caller along with the key handle
- Extract
nonce
(last 32 bytes of key handle) - Compute
HMAC_SHA256(app_id + nonce)
using device key. This is a private key - Compute
HMAC_SHA256(private_k)
and compare it to the first 32 bytes of key handle, they should match. If they don't, return key not found error.
Key handle should look random for casual observer and it does. I ran the same suite on 200K of key handle data generated on Tomu board:
SUMMARY
-------
monobit_test 0.228183190471 PASS
frequency_within_block_test 0.226128174333 PASS
runs_test 0.0567490131255 PASS
longest_run_ones_in_a_block_test 0.748631279961 PASS
binary_matrix_rank_test 0.612219673314 PASS
dft_test 0.553339041027 PASS
non_overlapping_template_matching_test 1.00000008312 PASS
overlapping_template_matching_test 0.932591232105 PASS
maurers_universal_test 0.999315334632 PASS
linear_complexity_test 0.335268833847 PASS
serial_test 0.201711285468 PASS
approximate_entropy_test 0.20129897411 PASS
cumulative_sums_test 0.11790756896 PASS
random_excursion_test 0.199118786038 PASS
random_excursion_variant_test 0.157445636324 PASS
STM32F103 and EFM32HG devices are not tamper resistant. Highly skilled hacker with the right equipment will be able to read the contents of device's flash and get the device key. Then he will be able to clone your device and use it as second factor to log into your account providing that he knows your passwords as well.
However, they are resistant enough for daily use as second factor authentication device, because:
- Firmware does not allow to read flash contents by USB, neither does it disclose device key
- Both STM32F103 and EFM32HG have "flash readout protection" feature. Once
enabled, this feature protecting flash from being read via debug interface.
See this post for EFM32, and this question for STM32F103. See also
efm32 debuglock
andstm32f1x lock
commands in OpenOCD manual.
Chopstx is a threading library for Cortex-M0 and Cortex-M3 processors written by Niibe Yutaka.
ECC is taken from Gnuk project by Niibe Yutaka.
Some constants and memory layout structures were taken from EFM32 platform libraries by Silicon Laboratories.
Copyright © 2017 Sergei Glushchenko
This program is 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, either version 3 of the License, or (at your option) any later version.
This program is 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 http://www.gnu.org/licenses/.
As additional permission under GNU GPL version 3 section 7, you may distribute non-source form of the Program without the copy of the GNU GPL normally required by section 4, provided you inform the recipients of GNU GPL by a written offer.