Skey: a FIDO2 security key on the Raspberry Pi Pico
Firmware for the Pico / Pico 2 that acts as a USB FIDO2 authenticator: CTAP2 and U2F, passkeys, BOOTSEL as the touch button, and clientPIN with PIN/UV auth protocols 1 and 2. A host simulator runs the same core so the python-fido2 test suite exercises it without hardware. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
6c7e6ab5f5
41 files changed
+4218
No files matched your search
@@ -0,0 +1,147 @@
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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`):
|
||||
|
||||
```bash
|
||||
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
|
||||
Reference in new issue
Block a user