Files
Skey/README.md
T
2026-10-14 15:12:31 -04:00

5.3 KiB

Skey

A FIDO2 / WebAuthn security key built on a Raspberry Pi Pico or Pico 2. It plugs in over USB and works with browsers for passkeys and 2FA, and with ssh-keygen -t ecdsa-sk. No drivers are needed.

What works

  • CTAP2: makeCredential, getAssertion, getNextAssertion, getInfo, reset, selection
  • U2F (CTAP1) register/authenticate, so older sites work too
  • ES256 (P-256) credentials with packed attestation
  • Up to 64 discoverable credentials (passkeys)
  • Touch to confirm, using the Pico's BOOTSEL button
  • PIN (clientPIN, PIN/UV auth protocols 1 and 2), for sites that require user verification
  • Unlimited non-discoverable credentials. The credential ID carries everything needed to re-derive the key, so nothing is stored for them.

Not yet supported: hmac-secret, credential management.

Layout

src/        authenticator core: CTAPHID, CTAP1, CTAP2, CBOR, crypto, storage
firmware/   Pico specifics: USB descriptors, flash, RNG, LED, BOOTSEL button
host/       desktop simulator speaking CTAPHID over stdin/stdout
tests/      python-fido2 tests run against the simulator
tools/      attestation certificate generator

The core only talks to hardware through src/platform.h, so the simulator runs exactly the same protocol and storage code as the firmware. The simulator's flash file even follows NOR rules: erase whole sectors, and program whole pages that can only clear bits.

Setup

sudo apt install build-essential cmake ninja-build git gcc-arm-none-eabi \
    libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib libmbedtls-dev \
    python3-fido2 python3-pytest libusb-1.0-0-dev pkg-config

git clone --depth 1 https://github.com/raspberrypi/pico-sdk deps/pico-sdk
git -C deps/pico-sdk submodule update --init --depth 1 lib/tinyusb lib/mbedtls

Set PICO_SDK_PATH instead if you already have an SDK checkout.

Simulator and tests

cmake -B build-host -G Ninja -DSKEY_HOST=ON
cmake --build build-host
python3 -m pytest tests

The simulator approves every request unless SKEY_BUTTON_FILE is set. When it is set, creating that file counts as a button press. The tests in tests/test_presence.py use this.

Firmware

cmake -B build-pico -G Ninja -DPICO_BOARD=pico     # or pico2
cmake --build build-pico

Hold BOOTSEL while plugging in the Pico, then copy build-pico/skey.uf2 to the RPI-RP2 (or RP2350) drive. The board restarts as "Skey FIDO2 Key".

Using the button

The BOOTSEL button is the touch button. Press it briefly; holding it while plugging in still enters the bootloader as usual.

LED Meaning
off idle
on handling a request
fast blink waiting for a touch: press BOOTSEL
slow blink an older U2F site asked for a touch: press BOOTSEL
  • A press only counts if it happens after the request arrives, and each press approves one request.
  • An unanswered request times out after 30 seconds.
  • Closing the browser prompt cancels the wait.

To try it out:

  • https://webauthn.io: register, then authenticate
  • fido2-token -L and fido2-token -I <dev> (package fido2-tools)
  • ssh-keygen -t ecdsa-sk for an SSH key backed by the device

On Linux, systemd recognizes FIDO devices by their HID usage page, so no udev rules are needed. The exception is snap-packaged browsers, such as Ubuntu's Firefox. Snaps can only open keys on a fixed list of USB IDs, so without the rule below the browser waits forever and never sees the key:

sudo cp udev/69-skey-snap.rules /etc/udev/rules.d/
sudo udevadm control --reload

Then unplug the key and plug it back in.

PIN

The key works without a PIN. Once you set one:

  • every new registration asks for it;
  • logins ask for it when the site requires user verification;
  • the key returns account names, so the browser can show which passkey you are picking.

Set or change it in Chrome under Settings > Privacy and security > Security > Manage security keys. From a terminal, use fido2-token (package fido2-tools):

fido2-token -L                   # find the device, e.g. /dev/hidraw11
fido2-token -S /dev/hidraw11     # set a PIN
fido2-token -C /dev/hidraw11     # change it

A PIN must be at least 4 characters. 8 wrong guesses in a row lock the PIN for good, and only a reset (which also deletes every credential) clears it. After 3 wrong guesses, the key also needs unplugging before it accepts more.

Security notes

  • Secrets live in flash. The master secret sits in the last 64 KB of flash in plain form. Anyone holding the key can read it via BOOTSEL. Pico 2 secure boot plus OTP storage would fix that.
  • Attestation key. The first build creates one in attestation/ (gitignored). Every key you flash from that checkout shares it, just like a vendor's batch attestation.
  • Reset window. authenticatorReset only works within 10 seconds of plugging in.
  • Randomness. The Pico 2 has a hardware TRNG. The RP2040 relies on ROSC jitter via pico_rand, whitened with SHA-256.

Roadmap

  1. Credential management (list and delete passkeys), then advertise FIDO_2_1
  2. hmac-secret / prf extension
  3. Pico 2: keep the master secret in OTP and enable secure boot
  4. Optional external button on a GPIO pin

License

MIT; see LICENSE. Firmware builds also include the Pico SDK (BSD-3-Clause), TinyUSB (MIT) and Mbed TLS (Apache-2.0 or GPL-2.0-or-later), each under its own license.