MCPcopy Index your code
hub / github.com/TheMaxMur/RS-Key

github.com/TheMaxMur/RS-Key @v0.3.1

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.3.1 ↗ · + Follow
3,833 symbols 15,435 edges 368 files 1,393 documented · 36%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

RS-Key

ci deep-checks docs

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 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
Three boards, one firmware — stick · trusted display · RP2350-One Registering a passkey — the PIN & Approve/Deny happen on the device's own screen

RS-Key (RSK, Raspberry Security Key — also a nod to its being written in Rust) is open-source security-key firmware for the Raspberry Pi RP2350. It makes an RP2350 board behave like a USB authenticator and ships the host tooling to drive it. It is written in Rust (no_std, embassy) and is meant for development, research, and controlled experiments — not as a drop-in replacement for an audited commercial key.

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.

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

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 scheme (COSE −48); advertising it 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

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)"]

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.

Quick start

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

cargo build --release -p firmware
picotool uf2 convert target/thumbv8m.main-none-eabihf/release/firmware -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). Full walkthrough: docs/quickstart.md. On Linux, the CCID half needs a little host setup: docs/linux.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.

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

picotool seal"] --> p2{{"burn OTP fuses

IRREVERSIBLE"}} --> p3["secure boot:

only your signed images"]
    end

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).

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 Brainpool / X448 / Ed448 OpenPGP curves (no mature no_std Rust implementations).
  • 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.

Extension points exported contracts — how you extend this code

Rng (Interface)
A source of random bytes — the device TRNG in `firmware`, a deterministic stream in tests. Decouples the FIDO logic from [17 …
crates/rsk-fido/src/lib.rs
Storage (Interface)
A persistent map from 16-bit file id to a byte value. [9 implementers]
crates/rsk-fs/src/storage.rs
Applet (Interface)
A selectable smartcard applet. `C` is a shared context (the file system in `firmware`) the dispatcher threads into ever [9 …
crates/rsk-sdk/src/applet.rs
Rng (Interface)
Random-byte source. `firmware` backs this with the RP2350 TRNG; tests use a deterministic counter. [8 implementers]
crates/rsk-openpgp/src/lib.rs
Rng (Interface)
Randomness source for at-rest seal nonces (the firmware backs it with the hardware TRNG). Mirrors the sibling applets' ` [35 …
crates/rsk-otp/src/lib.rs
Platform (Interface)
Firmware-side services the applet needs: OTP secure-boot status (read-only), the session RTC, and the deferred reboot (e [4 …
crates/rsk-rescue/src/lib.rs
FrameSink (Interface)
crate-internal, single-threaded executor — no Send bound needed [3 implementers]
crates/rsk-usb/src/ctaphid.rs
DeviceProvider (Interface)
Everything the cockpit can ask of a device. Implemented by [`HardwareProvider`] (real I/O) and [`MockProvider`] (`--demo [2 …
tools/tui/src/device.rs

Core symbols most depended-on inside this repo

len
called by 492
crates/rsk-sdk/src/applet.rs
get
called by 329
crates/rsk-fs/src/sealed.rs
len
called by 216
crates/rsk-openpgp/src/dobj.rs
len
called by 143
tools/tui/src/model.rs
cbor
called by 137
crates/rsk-fido/src/cbordec.rs
push
called by 109
tools/tui/src/model.rs
iter
called by 101
tools/tui/src/model.rs
die
called by 97
tools/rsk/common.py

Shape

Function 2,678
Method 792
Class 265
Enum 80
Interface 18

Languages

Rust86%
Python13%
C1%
TypeScript1%

Modules by API surface

crates/rsk-ui/src/lib.rs92 symbols
crates/rsk-ui/src/render_tests.rs76 symbols
fuzz/tests/miri.rs70 symbols
crates/rsk-fido/src/getassertion_tests.rs69 symbols
crates/rsk-fido/src/vendor_tests.rs65 symbols
crates/rsk-fido/src/clientpin_tests.rs62 symbols
tools/tui/src/device.rs59 symbols
crates/rsk-openpgp/src/keys.rs59 symbols
crates/rsk-piv/src/tests.rs56 symbols
crates/rsk-openpgp/src/tests.rs56 symbols
crates/rsk-piv/src/lib.rs52 symbols
crates/rsk-fido/src/makecredential_tests.rs52 symbols

For agents

$ claude mcp add RS-Key \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact