Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

730 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RS-Key

ci deep-checks docs

An open-source hardware passkey. Flash one file onto a Raspberry Pi RP2350 board and it becomes a USB security key: passkey logins in the browser, ssh and git signing, GPG, PIV, and TOTP codes.

What this is Firmware. A .uf2 file you drop onto a board. Nothing here is for sale.
What you need Any RP2350 board (from about $5) and a USB cable. No soldering, no programmer, no toolchain.
What you get A USB authenticator your browser, ssh, gpg and ykman already know how to talk to.

What RS-Key is: any RP2350 board, plus this firmware, gives you passkey and WebAuthn logins, ssh and git signing, an OpenPGP card for gpg, a PIV smart card, and TOTP codes

This project is experimental. It has had no external security audit, the RP2350 is not a secure element, and a stolen board is only as strong as the optional OTP / secure-boot hardening you have applied to it. Don't use it to guard credentials you can't afford to lose or have stolen. Read the threat model and limitations before trusting it with anything real.

Quick start

From a fresh board to a passkey login. Nothing to build.

  1. Download the newest rs-key-<version>-default.uf2 from the releases page.
  2. Hold the board's BOOT button while you plug it in. A drive named RP2350 appears.
  3. Copy the .uf2 onto that drive. The board reboots as a security key.
  4. Go to webauthn.io and register a passkey. The browser walks you through setting a PIN the first time, then asks for a touch: press the BOOT button.

Which file to take:

Your board Image
Most RP2350 boards, 4 MB flash default
2 MB flash (Seeed XIAO RP2350, Waveshare RP2350-Zero-CM) 2mb
16 MB flash (TenStar RP2350-USB) 16mb
Waveshare RP2350-Touch-LCD-2.8 display

The other ten images are behaviour variants (post-quantum algorithms, the FIPS profile, alwaysUv, PIN hardening). Full table, plus how to verify the cosign signature and reproduce the build yourself: docs/releases.md.

The longer walkthrough, with the PIN, ssh and gpg steps, is docs/quickstart.md. On Linux the smart-card half needs a little host setup first: docs/linux.md.

Once it is flashed you can configure it from a GUI instead of a terminal: PicoForge (third-party, from the librekeys project) reads the device's state and writes the same config surface the rsk CLI does — see Host tools.

PicoForge on its Device Overview page, reading a freshly flashed RS-Key: a sidebar of sections (Home, Passkeys, Accounts, Slots, PIV, OpenPGP, Audit, Backup, Lock, Attestation, Configuration, Security, Offboard) and four cards — Device Information (serial number, firmware RS-Key build 0x0872, VID:PID 1209:0001, product name RS-Key Security Key, storage 2 of 1536 KB, 4 MB flash chip), FIDO2 Information (AAGUID, U2F_V2 and FIDO 2.0 / 2.1 / 2.3, PIN Not Set, resident keys supported, 256 remaining credentials), LED Configuration (GPIO 16, 30 s presence touch timeout) and Security Status (boot mode Development, debug enabled, secure lock pending)
A board straight out of step 3, in PicoForge: the default 1209:0001 identity, no PIN yet, boot mode still Development

The demo below runs on a board with a screen. A plain RP2350 board behaves the same way, with two differences: the host asks for the PIN instead of the device, and you confirm with the physical button instead of an on-screen one.

Registering a passkey on webauthn.io with the trusted-display build: the device screen shows a Device PIN pad, then an Approve / Save-passkey prompt, and the browser confirms you are logged in. The PIN and the approval both happen on the device's own screen
Step 4 on the display image: the PIN and the Approve/Deny happen on the device's own screen

What it supports

  • FIDO2 / WebAuthn / U2F — passkeys, two-factor logins, ssh ed25519-sk
  • OpenPGP card 3.4gpg signing, decryption, authentication (EC + RSA)
  • PIV — X.509 smart-card via PKCS#11 (or ykman piv, which needs the opt-in VIDPID=Yubikey5 build)
  • OATH — TOTP / HOTP codes (ykman oath, Yubico Authenticator — both need the opt-in VIDPID=Yubikey5 build)
  • Yubico-style OTP — four slots, plus a USB-keyboard interface that types the code
  • Seed backup — export the FIDO master seed as BIP-39 / SLIP-39 words
  • At-rest soft-lock — keep the FIDO seed in flash encrypted to a key only you hold
  • On-device audit journal and enterprise (org-provisioned) attestation
  • Post-quantum FIDO2 (experimental) — implements the ML-DSA-44 (COSE −48) and ML-DSA-65 (−49) schemes; ML-DSA-65 uses an in-tree, stack-optimized FIPS 204 implementation so it fits the RP2350. Advertising them in getInfo is off by default because some shipped browsers reject an unknown algorithm id. This is not a FIPS-validated module.

Capacities are flash-bound and generous (e.g. up to 256 resident passkeys, 255 OATH accounts, 24 PIV slots, 4 OTP slots); details are in the feature guides and build options.

flowchart TD
    host["Host software<br/>browser · ssh · gpg · ykman · rsk / rsk-tui"]
    host -->|USB| usb["Composite USB device"]
    usb --> fido["FIDO HID"]
    usb --> ccid["CCID (smart-card)"]
    usb --> kbd["Keyboard (OTP typing)"]
    fido & ccid & kbd --> applets["Applets: FIDO2/U2F · OpenPGP · PIV · OATH · OTP · mgmt"]
    applets --> core["Master seed · TRNG · flash store"]
    core --> rp["RP2350 (no secure element)"]
Loading

What it does not protect against

  • Physical / lab attacks — decapping, microprobing, fault injection beyond the on-chip glitch detectors, power/EM side channels, and flash-emulation TOCTOU. The RP2350 is not a secure element; if your threat model includes a funded lab, buy a certified key.
  • A compromised host with the device unlocked — like any security key, it will perform operations you have authorized while plugged in and unlocked.
  • Loss of secrets without the optional hardening — at-rest protection only becomes meaningful after you fuse the OTP master key (see Production, below).

Full reasoning: docs/threat-model.md.

Hardware

Any RP2350 board with USB. Developed and tested on the Waveshare RP2350-One (WS2812 status LED on GPIO16; boards without an LED run fine). A different flash size, LED pin, or presence-button GPIO is a one-line build knob. Details: docs/hardware.md.

Three RS-Key boards on a blueprint background: a bare RP2350 USB stick, the trusted-display variant showing its Home "Ready" screen (USB connected, Device PIN set, 2 passkeys), and a Waveshare RP2350-One
Three boards, one firmware: stick · trusted display · RP2350-One

Documentation

The docs live in docs/ and are published as a site: https://themaxmur.github.io/RS-Key/.

Quick start flash, enroll, first login
Hardware supported boards and build knobs
Build options every flag: VID/PID presets, version, touch, PQC, FIPS profile
Production setup OTP fuses + secure boot, step by step (irreversible)
Feature guides FIDO2, SSH, OpenPGP, PIV, OATH, OTP, backup, soft-lock, LED, audit, …
Threat model · Limitations what it protects against, and what it does not
Architecture · unsafe audit how it's built; every unsafe site
Testing · Interop host tests, fuzzing; real-tool results
Linux setup · Motivation pcscd/udev/polkit; why this exists

Build it yourself

The release images are reproducible, so you can rebuild any of them bit for bit (docs/releases.md). To build your own:

git clone https://github.com/TheMaxMur/RS-Key && cd RS-Key
nix develop                       # toolchain, picotool, host tools, everything

cargo build --release -p firmware
scripts/pt.sh target/thumbv8m.main-none-eabihf/release/firmware firmware-pt.elf   # fence the key's storage off the bootloader
picotool uf2 convert firmware-pt.elf -t elf firmware.uf2

# hold BOOTSEL, plug the board in, then flash, either way:
cp firmware.uf2 /Volumes/RP2350/                    # macOS drag-and-drop; Linux: the mounted RP2350 volume
picotool load -v firmware.uf2 && picotool reboot    # more robust; verifies the write (use if cp flakes)

Re-plug the board and it enumerates as a composite USB authenticator. The default build requires a physical touch (the BOOTSEL button) for FIDO operations; build with --features no-touch for a no-touch build (the automated test suites need it). Every compile-time knob, including the flash size, LED pin and USB identity, is in docs/build.md.

Development setup

nix develop is the whole setup: Rust with the thumbv8m.main-none-eabihf target, picotool, the Python host stack, and the security tooling. One command is the merge gate, and CI runs exactly the same script:

nix develop -c ./scripts/check.sh   # fmt, clippy, host tests, firmware builds, audit, deny, gitleaks

See CONTRIBUTING.md and docs/testing.md.

No board? Run the emulator

tools/emu runs the same crates/rsk-* applet code a real key runs — FIDO2/U2F, PIV, OpenPGP, OATH — over sockets instead of USB, so the protocol test suites work with nothing plugged in:

nix develop -c ./scripts/emu-suites.sh          # every suite that needs no board
cargo run --manifest-path tools/emu/Cargo.toml --target "$HOST" -- --display

--display opens the trusted screen in a window: the whole flow, not a viewer, so the Approve/Deny ceremony can be tried with a mouse. On Linux, --usbip attaches it to the kernel as a real USB device, which is how a browser, ykman or gpg reach it.

It is not a security key: no secure boot, no OTP root, no fuses, and the seed sits in a file. It emulates behaviour, not the device. See tools/emu/README.md.

Production / secure boot (irreversible — read first)

By default the firmware flashes by drag-and-drop and roots its at-rest encryption in a key derived on the device. An optional, opt-in path hardens that: it fuses a random master key into RP2350 OTP and enables secure boot so the board runs only images you sign.

flowchart LR
    subgraph dev["Default (development)"]
      d1["drag-and-drop UF2"] --> d2["flash-derived key"] --> d3["boots any image"]
    end
    subgraph prod["Production (opt-in)"]
      p1["sign UF2<br/>picotool seal"] --> p2{{"burn OTP fuses<br/>IRREVERSIBLE"}} --> p3["secure boot:<br/>only your signed images"]
    end
Loading

These steps burn one-time-programmable fuses: they cannot be undone, they change your reflash workflow forever (signed images only), and a mistake can brick the board. They are also what makes a stolen board's flash dump useless. Read docs/production.md end to end before running anything.

Host tools

Inside the dev shell two commands are on PATH:

  • rsk — the device CLI (Python): rsk status, rsk backup, rsk lock, rsk secure-boot, rsk otp, rsk fido, rsk led, rsk reboot, … (rsk --help)
  • rsk-tui — a terminal dashboard for day-to-day reads and a few in-band actions (guide; rsk-tui --demo needs no hardware)

Without the dev shell, rsk also runs on a plain Python ≥ 3.9 toolchain via uv or pip — uvx --from ./tools rsk status, uv tool install ./tools, or pipx install ./tools. Details and the native-lib notes are in tools/README.md.

Separately, rsk-wipe is a RAM-only flash-erase image you flash deliberately to wipe a board for clean-slate testing — it is built and flashed like firmware, not run from PATH (rsk-wipe/README.md).

Third-party: PicoForge (from the librekeys project) is a desktop GUI that configures an RS-Key over the same phy record rsk hw writes — see the host protocol.

Also third-party, and a developer tool rather than a setup one: Telesma is a desktop workbench for inspecting and managing a local FIDO2/CTAP authenticator, built on go-ctap/ctap — an independent CTAP 2.0–2.3 client stack, which is what makes it interesting here: a second opinion on what this firmware answers, from a parser that is not libfido2 or python-fido2. It has not been run against RS-Key yet; it is in the interop matrix.

Limitations (short list)

  • No secure element. OTP + secure boot is real hardening, but physical attacks are out of scope.
  • Seed backup covers the deterministic identity only — resident passkeys, OpenPGP and PIV keys do not survive a board swap.
  • No brainpoolP512r1 / X448 / Ed448 OpenPGP curves (no mature no_std Rust arithmetic yet); brainpoolP256r1 and P384r1 are supported.
  • The default USB identity is RS-Key's own pid.codes id 0x1209:0x0001; the YubiKey USB identity that ykman / Yubico Authenticator auto-recognize is the opt-in VIDPID=Yubikey5 build, not for distribution.

Details and reasoning: docs/limitations.md.

License

AGPL-3.0-only — see LICENSE, NOTICE, and COMPLIANCE.md. RS-Key is a from-scratch Rust reimplementation of the AGPL-3.0-only pico-keys firmware family (pico-fido / pico-openpgp / pico-keys-sdk) by Pol Henarejos; the upstream grant is version-3-only, so RS-Key inherits it and so must forks. Not affiliated with or endorsed by Yubico, Nitrokey, or Raspberry Pi. See motivation.

The name: Raspberry Security Key, and a nod to Rust. Nothing to do with RSA. The crates and the CLI shorten it to rsk.

About

Turn a $5 Raspberry Pi RP2350 board into an open-source hardware passkey: WebAuthn/FIDO2 logins, ssh & git signing, OpenPGP, PIV, TOTP. Rust firmware, no_std.

Topics

Resources

Contributing

Security policy

Stars

340 stars

Watchers

9 watching

Forks

Releases

Used by

Contributors

Languages