# 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 ` (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 ## License MIT; see [LICENSE](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.