dumbtv
dumbtv가 ADB로 Fire TV에서 광고·추적·업데이트 알림을 제거, 루트·앱 변경 없이, 무료 오픈소스
하드웨어 보안 키의 실제 기능과 설정 방법, FIDO2·TOTP·스마트카드·OpenPGP 등 표준을 중립적으로 안내
A short, vendor-neutral guide to what a hardware security key actually does — FIDO2 passkeys, TOTP, smart-card (PIV), and OpenPGP — how to set one up, and where to read the real standards.
보안Rust · ShellGitHub @framefilter온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
Vendor Neutral, Rust-Based Management UI and CLI for U2F/FIDO2 and other hardware security keys
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
An independent, vendor-neutral app for managing all your hardware security keys
in one place.
keyroost is an open-source Rust toolchain for hardware security keys, working
across vendors over PC/SC and USB HID. It speaks FIDO2/CTAP2, OATH (TOTP/HOTP),
and the OpenPGP and PIV card protocols, manages on-device OTP on Token2 FIDO keys,
and also programs the Token2 Molto2 / Molto2v2 TOTP token. Ships a Rust library,
a CLI (keyroostctl), and a desktop GUI (keyroost) with a dark and light
theme — implemented from public standards, with no vendor SDKs, no Python, and
no Qt.README 전문 보기 ↗An independent, vendor-neutral app for managing all your hardware security keys in one place.
keyroost is an open-source Rust toolchain for hardware security keys, working
across vendors over PC/SC and USB HID. It speaks FIDO2/CTAP2, OATH (TOTP/HOTP),
and the OpenPGP and PIV card protocols, manages on-device OTP on Token2 FIDO keys,
and also programs the Token2 Molto2 / Molto2v2 TOTP token. Ships a Rust library,
a CLI (keyroostctl), and a desktop GUI (keyroost) with a dark and light
theme — implemented from public standards, with no vendor SDKs, no Python, and
no Qt.
Built with AI. I saw a real need for this but never learned to code, so the parts I author — code, docs, and all — are written end-to-end with AI. (Contributions from others, such as Token2's, are their own human-designed and -developed work — see the Contributors section.) Since the AI I use learned from the vast commons of free and open-source software people have generously shared, releasing keyroost as FOSS isn't really a choice; it's giving back to what made it possible. Issues, review, and contributions are warmly welcome — see CONTRIBUTING.md.
New to hardware keys? Read the companion guide — "So you bought a hardware security key… now what?" — a short, vendor-neutral tour of what FIDO2, OATH, OpenPGP, and PIV actually do.
authenticatorGetInfo,
manage resident credentials (list / metadata / delete), set / change the PIN
and show how many attempts are left, reset a key (USB replug ceremony, or in place for a card in a
reader). Fingerprint (bio) enrollment — list / enroll / rename / delete — and
FIDO Metadata Service (MDS) authenticator identification in the GUI. PIN
protocols v1 and v2. CTAP 2.1 security policy over
authenticatorConfig (always-require-UV, minimum PIN length, force a PIN
change, enterprise attestation) and a largeBlob store for plaintext notes (fido blob)
over authenticatorLargeBlobs. Works over USB-HID and over a PC/SC reader —
both NFC (CTAP-over-NFC) and a contact / ISO-7816 chip reader (T=0) — not just
direct USB. Resident-credential metadata surfaces the fuller passkey detail
too: the user's UPN, display name, user id, and the full credential id.
Resident SSH credentials can be listed and their stored OpenSSH certificate
extracted from largeBlob to a -cert.pub file (fido ssh).oath reset) — the recovery path for a forgotten password. In
the GUI, secret fields have a reveal (eye) toggle so you can check an OTP
secret before committing it.gpg --export-secret-keys file onto a
card, and change a key's expiry date; or import an RSA key (2048-4096 bits)
from a PKCS#1/PKCS#8 PEM/DER file — each writes the v4 fingerprint and a
generation timestamp so GnuPG recognizes the key; sign (SHA-256 or SHA-1); decrypt; authenticate (a client/SSH
signature with the Authentication key via INTERNAL AUTHENTICATE); set
cardholder name / URL; change the user / admin PIN and unblock a locked PIN;
factory-reset the applet..p12 / .pfx file
(piv key import; legacy-encrypted files (RC2/3DES, the kind some
certificate authorities such as Spain's FNMT still issue) open directly;
on a Nitrokey 3, RSA keys into slot 9A; not available on HID Crescendo
cards; not yet verified on Token2 keys), clearing a slot's
certificate (piv cert delete) or key (piv key delete),
moving a key between slots (piv key move), writing a
fresh CHUID with a random GUID (piv chuid generate) so Windows re-reads a
reprovisioned card, and PIN / PUK / management-key changes and applet reset.
A certificate too large for a slot can be stored gzip-compressed, as the PIV
standard allows (--compress / --no-compress).
A read-only test runs every private-key operation a slot's key supports
(decrypt for RSA, key-agree for the ECDH curves, sign for RSA / ECDSA /
Ed25519) and checks each result against the slot certificate's public key.
keyroost identifies which PIV implementation a card runs (YubiKey, Token2,
Nitrokey, Swissbit, HID Crescendo and others) and offers vendor extensions
such as piv key move and piv key delete according to a built-in list of what each
supports. A feature the list marks unsupported stays visible, and the GUI's
Enable Anyway or the CLI's --force turns it on (except key import
from a file, which follows the list strictly).
Every slot-taking command addresses
9A/9C/9D/9E and the 20 Yubico retired key-management slots (82–95), so keys
can be archived and rotated. The GUI collects the
management key per operation (and wipes it after), which is ideal for a slot or
two; for provisioning many slots or keys, the CLI is the intended path — the
management key and PIN come from env/stdin once, so a shell loop does the batch
(see the PIV guide).otpauth:// URI or
a QR code scanned from a PNG/JPEG screenshot or the live screen; import a
whole authenticator export into consecutive slots; sync the host clock;
rotate the customer key; factory reset.otpauth:// URIs; a QR screenshot,
including a Google Authenticator export) into a YubiKey, Solo 2 or
Nitrokey 3 (OATH), a Token2 key's OTP storage, or a Molto2. keyroost shows
what each code becomes before it writes anything (oath import,
otp import, molto import --in, or drop the file on the GUI); see
Importing codes.prog info / seed / config, or the GUI's
programmable-token pane). The wire protocol is documented in
docs/PROTOCOL-token2prog.md.otp pin set,
otp pin verify, otp pin change, otp pin clear, otp pin status); note
there is no PIN reset — a blocked PIN is recoverable only by erasing every
OTP entry. On Bio3 keys with a fingerprint enrolled, a fingerprint touch can
release protected codes as an alternative to the PIN
(otp fingerprint status, otp fingerprint enable,
otp fingerprint disable, otp list --unlock fingerprint, and
otp list --unlock auto, which tries the fingerprint and falls back to
the PIN); fingerprint
enrollment itself is done through the key's FIDO2 fingerprint setup.keyroostctl factory-reset (and a card on
the GUI device Overview tab) resets every resettable applet on a key in turn:
OATH, OpenPGP, Token2 OTP, PIV, then FIDO2. In a terminal you confirm by
typing reset; a script passes --yes. On a USB key the FIDO2 step ends
with an unplug/replug (detected automatically) and a touch; a card in a smart-card reader is instead
reset in place (no replug, no touch — the card is power-cycled in the
reader). The Token2 OTP step, and the OATH step on a Nitrokey 3, ask for a
touch on the key. A FIDO2 key with no serial number is reset only while it
is the only key plugged in; otherwise keyroost names the keys to unplug
before anything is wiped. Only manufacturer-intended resets are used, and
each step reports its own outcome rather than being folded into one
"done".--device NAME
when several are connected, instead of by a reshuffling /dev/hidrawN path.
Keep the name on this computer (keys.json, which stores a salted
fingerprint of the key, never its serial) or on the key itself, where every
computer running keyroost sees it (see Naming a key).
Destructive operations act on one specific key, never "the first key
found": a lone key is used, and with several, a terminal asks you to pick
and a script is refused. The confirmation question names the key.
keys.json lives in %APPDATA%\keyroost on Windows and in
$XDG_CONFIG_HOME/keyroost (else ~/.config/keyroost) elsewhere, macOS
included, and names are validated with anti-spoofing checks while
allowing a relaxed, readable character set.
On Windows, keyroost recognizes a key's FIDO side and its smart-card reader
as one key from where both sit in the Windows device tree, without opening
either. On macOS, which reports no USB position, and on Windows when the
tree doesn't answer, keyroost asks each side for the identity it reports (a
YubiKey's serial, a Solo 2's ID, a Token2 key's serial) and joins them into
one entry when they match. If two keys of the same make are connected and
one doesn't answer, it is shown as two entries rather than guessed at.| Device | Capabilities | Notes |
|---|---|---|
| Token2 Molto2 / Molto2v2 | TOTP slot programming, code import | Hardware-verified. Programmed over the vendor-specific SM4-MAC protocol (docs/PROTOCOL.md); supports importing Aegis / 2FAS / otpauth-list exports and QR screenshots, clock sync, and customer-key rotation. |
| Token2 single-profile tokens (OTPC-P1-i / P2-i, miniOTP-2-i / 3-i, C301-i, C302-i) | Single-account TOTP seed + config programming | Programmed over the vendor-specific SM4-MAC protocol with a fixed device key (docs/PROTOCOL-token2prog.md); writes the seed and the TOTP algorithm / time-step / display-timeout over a contact or contactless PC/SC reader. The model is recognized from the device serial. |
| Token2 PIN+ Series | FIDO2 (+ bio), OTP (incl. import), OpenPGP, PIV | FIDO2 with fingerprint/bio enrollment and FIDO Metadata Service (MDS) display, plus on-device OTP (TOTP/HOTP) over CCID / NFC / USB-HID, validated on PIN+ hardware. Over USB-HID keyroost sends the OTP commands through the CTAPHID vendor command Token2 documented (#155), verified on a PIN+ Series FIDO + PGP key (release R3.4); keys that don't answer it get the earlier report-framed channel. Importing an authenticator export into OTP storage follows Token2's published OTP specification (see Importing codes). HID/keyboard HOTP applies to the models that carry it — recent releases (R3.2+/R3.3+) have no HID-HOTP and ship with the HID channel disabled by design; CCID is the intended path there. Keys running the R3.4+ OTP applet can put codes behind an OTP PIN; keys without it answer the capability probe with "no such command" and are unaffected. Contributed by @token2. PIV management was exercised on Token2 hardware by @episource (#128), and keyroost decodes the BCD-coded serial Token2's PIV and OpenPGP applets report; and show the key's full serial, read from its OTP applet. The OATH / OpenPGP smart-card applets are handled by the standard byte layers but (experimental). |
Each listed row notes what's actually been verified on that device; the final, generic row describes the standards-based behavior expected on untested but compliant keys.
Planned hardware support, not yet shipped:
fido commands will
apply, but it exposes no smart-card interface (no OATH / OpenPGP / PIV) and its
firmware reports a fixed, non-unique serial that needs placeholder handling
before it's first-class
(#37).Want a different key supported? Open an issue requesting it — hardware-support requests are tracked here and added to this roadmap.
keyroost is an independent implementation, not affiliated with or endorsed by any vendor named here. It works with their products by implementing publicly documented protocols; vendor and product names are used descriptively.
A genuine thank-you to these teams for their work on everyone's security: Yubico for helping create and champion U2F and FIDO2/WebAuthn and for publishing open specs and tooling; SoloKeys and Nitrokey for open, auditable security-key firmware and hardware (Nitrokey maintains the Trussed-based Solo 2 line); and Token2 for affordable programmable hardware TOTP. keyroost also rests on open standards from the FIDO Alliance, the OATH/IETF TOTP–HOTP RFCs, and the OpenPGP card specification.
Beyond the maintainers, keyroost is grateful for community contributions:
keyroost-token2prog)
and QR-from-screen import (#50);
and the OTP secret-reveal toggle plus a Windows key-naming / anti-spoofing
fix (#52,
#56); and OTP-PIN
protection for R3.4+ keys, in the CLI and the GUI
(#107,
#108). Signs the Windows
and macOS release builds out-of-band./dev/hidrawN path reading as "never
left", and card-serial keys failing to re-match after reinsertion)
(#96).(This credits their contributions to the codebase; it does not change keyroost's independent status described above.)
keyroost is built entirely from published specifications — no vendor SDKs. Every byte layer below is implemented in-tree against the documents named here. The two vendor-specific protocols are called out distinctly; everything else is an open industry standard.
FIDO2 / CTAP
OATH (one-time passwords)
OpenPGP card
keyroost-pgpkey): OpenPGP packets, ASCII armor and
passphrase-protected secret keys per RFC 4880, ECC keys per
RFC 6637, Curve25519 per
RFC 7748 /
RFC 8032.PIV
Token2 Molto2 / Molto2v2 (vendor-specific)
docs/PROTOCOL.md. It layers on:
Token2 single-profile programmable token (vendor-specific)
docs/PROTOCOL-token2prog.md.Token2 on-device OTP (vendor-specific)
Cryptographic primitives & encodings
keyroost-pivtest).otpauth:// URI scheme — OTP secret encoding and 2FA import.zeroize for wiping secret buffers.
External crates are added only when not doing so would be irresponsible or
impractical — audited cryptography we won't hand-roll under forbid(unsafe_code)
(RustCrypto: sha2 / hmac / aes / p256 / rsa / …) and platform glue
(pcsc, hidapi on macOS/Windows, clap, eframe/egui). The per-crate list
is in the table below, and the standing goal is to shrink it over time, not grow
it.otpauth:// URIs
come from an environment variable, stdin or a hidden prompt, never argv. The
tool never prints or persists them.keyroost ships through every mainstream channel below. The release archives, the
Flatpak, Homebrew, winget and the AUR package all carry both keyroost (the GUI)
and keyroostctl (the CLI); the AppImage is GUI-only. Pick whichever
fits your platform; the smart-card features need a host PC/SC daemon (see
Smart-card prerequisite).
Pre-1.0, availability tracks the latest release: when a version is published, these channels are updated to point at it. Replace
vX.Y.Zbelow with that release tag.
No toolchain needed. Download from the latest release:
| Platform | Asset |
|---|---|
| Linux x86_64 | keyroost-vX.Y.Z-linux-x86_64.tar.gz |
| macOS 11 (Big Sur) or newer, Apple Silicon + Intel | keyroost-vX.Y.Z-macos-universal2.tar.gz |
| Windows x86_64 | keyroost-vX.Y.Z-windows-x86_64.zip |
| Windows x86_64, code-signed | keyroost-vX.Y.Z-windows-x86_64-signed.zip |
| macOS installer, code-signed | keyroost-vX.Y.Z-macos-universal2-signed.pkg |
The signed builds carry the same binaries as the CI archives, signed
out-of-band by Token2 (Authenticode on Windows; an Apple Developer ID
installer on macOS, signed but not notarization-stapled). They may trail a
release by a few days — winget always waits for the signed zip. The CI
archives are covered by SHA256SUMS and build-provenance attestation instead.
Each archive carries both keyroost and keyroostctl; unpack it and move the
two executables onto your PATH. For example, on Linux x86_64:
curl -L https://github.com/framefilter/keyroost/releases/download/vX.Y.Z/keyroost-vX.Y.Z-linux-x86_64.tar.gz \
| tar xz # then move keyroostctl / keyroost onto your PATH
The prebuilt binaries need libpcsclite at runtime — on a FIDO-only machine install it
(apt install libpcsclite1 / dnf install pcsc-lite) or use a package that declares the dependency.
Needs the Rust toolchain — 1.92+ for the GUI (keyroost), 1.87+ for the CLI
(keyroostctl) and libraries — and, on Linux, the PC/SC dev package plus the
GUI's X11/Wayland/GL libraries (see
Smart-card prerequisite):
cargo install keyroostctl keyroost
The release binaries and the AppImage are built with the optional qr feature
(scan a TOTP QR from the live screen). cargo install does not enable it — add
cargo install keyroost --features qr if you want it.
Or let cargo-binstall fetch the same pre-built release archive instead of
compiling — useful on atomic distros (e.g. Bazzite) where cargo install's
build step is awkward:
cargo install cargo-binstall # if you don't have it yet; ensure its dir is on PATH
cargo binstall keyroostctl keyroost
brew tap framefilter/keyroost
brew install keyroost
The keyroost-bin package installs the prebuilt binaries plus the FIDO udev
rules. Use any AUR helper (or makepkg):
yay -S keyroost-bin
The repository is a flake exposing keyroost (the default package),
keyroostctl and a development shell. Builds from source. Contributed and
tested on x86_64 Linux; other systems are declared but unverified
(#109).
nix run github:framefilter/keyroost # GUI
nix run github:framefilter/keyroost#keyroostctl -- --help
nix profile install github:framefilter/keyroost
Live in the Microsoft catalog (microsoft/winget-pkgs):
winget install Framefilter.Keyroost
Add Flathub (for the shared runtime) and the maintainer's GPG-signed remote in
the same scope, then install the app (app-id
io.github.framefilter.keyroost). Using --user needs no root and avoids the
most common failure — a scope mismatch between Flathub and the keyroost remote,
which makes the install fail to find its runtime even when it's installed:
flatpak remote-add --if-not-exists --user flathub \
https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak remote-add --if-not-exists --user keyroost \
https://framefilter.github.io/keyroost-flatpak/keyroost.flatpakrepo
flatpak install --user keyroost io.github.framefilter.keyroost
# updates ride along with `flatpak update`
The Flatpak carries the CLI as well; run it with
flatpak run --command=keyroostctl io.github.framefilter.keyroost.
Prefer a system-wide install? Use --system on all three commands instead —
the key is keeping Flathub and the keyroost remote in the same scope.
Or grab the offline single-file bundle (keyroost.flatpak) attached to each
release:
flatpak install ./keyroost.flatpak
The Flatpak bundles the pcsc-lite client and talks to the host pcscd, so
you still need that daemon running on the host (see
Smart-card prerequisite).
Download keyroost-x86_64.AppImage from the
latest release — the
asset name is version-less, so this URL always fetches the current build. It
needs glibc 2.35 or newer (see the note under
Smart-card prerequisite):
curl -LO https://github.com/framefilter/keyroost/releases/latest/download/keyroost-x86_64.AppImage
chmod +x keyroost-x86_64.AppImage
./keyroost-x86_64.AppImage
# On FUSE3-only distros, install libfuse2, or run without FUSE:
./keyroost-x86_64.AppImage --appimage-extract-and-run
The AppImage is GPG-signed (key fingerprint F9E0 4EED 9DC5 DC94 AB2E 6100 6C81 DC35 3794 478B); see
SECURITY.md → Verifying the AppImage.
Uses the host's pcsc-lite. The AppImage always prefers the host's pcsc-lite client library, so the smart-card client matches the host's
pcscddaemon; smart cards needpcscdinstalled (see Smart-card prerequisite). On a host without pcsc-lite it still starts, using a bundled copy of the library: FIDO works and the smart-card features report unavailable untilpcscdis installed.
The smart-card features (OATH / OpenPGP / PIV, and Token2 Molto2 programming)
talk to the card over PC/SC and need the pcscd daemon running on the host.
macOS and Windows have PC/SC built in. On Linux, install the PC/SC library +
daemon (the package name differs per distro). Building from source with cargo
additionally needs the PC/SC dev package, and the GUI needs the X11/Wayland/GL
libraries that eframe/egui link against.
Once installed, keyroostctl doctor checks all of this for you — PC/SC service,
readers, FIDO HID access, and udev rules — read-only, touching no key.
keyroost is otherwise distro-neutral — it talks to the kernel's hidraw/sysfs
and to PC/SC, both of which every mainstream distribution provides; only the
package names differ. Common distro one-liners:
# Debian / Ubuntu
sudo apt install libpcsclite-dev pcscd \
libxkbcommon-dev libwayland-dev libxcb1-dev libgl1-mesa-dev
# Fedora / RHEL
sudo dnf install pcsc-lite-devel pcsc-lite pkgconf-pkg-config gcc \
libxkbcommon-devel libxkbcommon-x11-devel wayland-devel libxcb-devel \
mesa-libGL-devel
# Arch
sudo pacman -S pcsclite ccid pkgconf gcc \
libxkbcommon libxcb wayland mesa
sudo systemctl enable --now pcscd
(For the CLI only you can drop the libxkbcommon/wayland/xcb/mesa
packages — those are just for the GUI.) macOS and Windows have PC/SC built in,
and the FIDO HID backend uses hidapi (IOKit / hid.dll) automatically — no extra
packages. Linux gets the most hands-on testing; macOS and Windows trail it but
are now tested on real keys regularly too.
(Building the GUI on Windows compiles a small build script that embeds the
app icon and version info into keyroost.exe; it needs a resource compiler —
rc.exe, part of the Windows SDK that ships with the standard MSVC toolchain
setup. The CLI has no build script.)
Windows and FIDO: Windows reserves raw FIDO HID access for elevated processes (the OS routes normal apps through its own WebAuthn API instead). Expect the
fidocommands and the GUI's FIDO2 tab to require an elevated ("Run as administrator") session on Windows; the Molto2, OATH, OpenPGP, and PIV features go over PC/SC and work unelevated. Elevate for the FIDO command you need, then drop back — don't run the whole tool elevated as a habit. Even without admin the GUI now detects an attached FIDO key (via readable HID metadata, without opening the protected interface) and shows an "Administrator rights needed" card with a button to relaunch elevated or open Windows' own security-key settings.keyroostctl list,doctorand thefidocommands (including--reader) say the same.
Prebuilt binaries: the Linux release tarball and the AppImage are built on Ubuntu 22.04 and need glibc 2.35 or newer (Ubuntu 22.04, Debian 12, Fedora 36 and later); on an older system (e.g. RHEL 9) they fail with a
GLIBC_…error. There, build from source with the commands above —cargo installhandles the rest.
Wayland and clipboard auto-clear: after copying an OTP code the GUI clears the clipboard ~45 s later, but only if the clipboard still holds that code. The check reads the clipboard via X11/XWayland; on a pure-Wayland session without XWayland clipboard sync it can't see the contents and fails open (nothing is cleared) rather than clobbering whatever you copied since. GNOME and KDE sync the two clipboards, so the clear works there; on other compositors treat the auto-clear as best-effort. Copied codes are also marked to stay out of clipboard history on macOS, Windows and X11; a native Wayland window copies them unmarked.
Forcing X11 (
KEYROOST_X11=1): the GUI runs natively on Wayland by default. The egui/eframe 0.35 bump fixed a Wayland/KDE input bug where text entry under native Wayland — notably on KDE Plasma — could misbehave, but if you still hit broken text input setKEYROOST_X11=1to force the GUI onto XWayland as a fallback. It's opt-in; leave it unset for the native-Wayland path.
The OATH, OpenPGP, and PIV applets are reached over PC/SC and need no special
permissions. Talking to a key's FIDO interface (the fido commands, and the
GUI's FIDO2 tab), though, opens a /dev/hidraw* node, which is
root-only by default. Install the bundled udev rules to grant the logged-in user
access:
sudo cp udev/70-keyroost-fido.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger
The rules use uaccess (and a plugdev fallback), are keyed by vendor/USB so
they apply before the hidraw node is created, and cover the common FIDO vendors
(Yubico, SoloKeys, Nitrokey, Feitian, Token2, and others). Re-plug the key after
installing them.
The Homebrew and AUR packages install keyroostctl's bash and fish
completions, plus man pages; for zsh, add the line below to ~/.zshrc.
Otherwise, set completions up once for your shell:
# bash: this session only, or for every session
source <(keyroostctl completions bash)
mkdir -p ~/.local/share/bash-completion/completions
keyroostctl completions bash > ~/.local/share/bash-completion/completions/keyroostctl
# zsh: add this line to ~/.zshrc (after compinit)
source <(keyroostctl completions zsh)
# fish
mkdir -p ~/.config/fish/completions
keyroostctl completions fish > ~/.config/fish/completions/keyroostctl.fish
# PowerShell: add this line to your $PROFILE
keyroostctl completions powershell | Out-String | Invoke-Expression
The script asks keyroostctl for suggestions as you type, so
--device <Tab> offers the names recorded in this computer's keys.json
(it never reads the keys). If you installed a completion file
from v0.12.0 or earlier, generate it again to get this.
# discover connected devices: PC/SC readers + FIDO authenticators (USB-HID and NFC)
keyroostctl list
# diagnose the local environment (PC/SC service, readers, FIDO HID access,
# udev rules, registry permissions) — read-only, touches no key
keyroostctl doctor
# --- FIDO2 (YubiKey / Solo 2 / Nitrokey 3), over USB-HID or an NFC reader ---
keyroostctl fido info
keyroostctl fido pin status
keyroostctl fido credential list # asks for the PIN (nothing is shown as you type)
keyroostctl fido ssh list # list SSH certs stored in resident credentials
keyroostctl fido ssh export --rp ssh:demo --out demo-cert.pub
# --- OATH over PC/SC ---
keyroostctl oath list
keyroostctl oath code 'GitHub:[email protected]' --device my-yubikey
keyroostctl oath import -i export.json --dry-run # show what an export would add; writes nothing
# --- OpenPGP card ---
keyroostctl openpgp info --device my-yubikey
keyroostctl openpgp sign --in msg.txt --device my-yubikey # asks for the PIN
keyroostctl openpgp authenticate --in chal.bin --device my-yubikey # client/SSH auth (Auth key)
# --- PIV ---
keyroostctl piv info --device my-yubikey
keyroostctl piv key generate --slot 9a --algorithm eccp256 # asks for the management key
# import a key and certificate issued as a .p12/.pfx file (for example a personal
# certificate from a certificate authority); asks for the file password, the
# management key and the PIN, then tests the slot. A file with several keys
# lists them for --entry N.
keyroostctl piv key import --slot 9a --in my-certificate.p12 --device my-yubikey
# bulk-provision several slots: management key + PIN from env once, loop the rest.
# (the GUI asks per operation; the CLI is the path for many slots/keys)
export PIV_MGMT=... # AES-192 / 3DES management key, hex; never put it in argv
export PIV_PIN=...
for slot in 9a 9c 9d 9e; do
keyroostctl piv key generate --slot "$slot" --algorithm eccp256 \
--mgmt-key env:PIV_MGMT --device my-yubikey
keyroostctl piv cert generate --slot "$slot" --subject "CN=$USER" \
--mgmt-key env:PIV_MGMT --pin env:PIV_PIN --device my-yubikey
done
# (in a terminal, a step that would replace a slot in use asks y/N first;
# add --yes to overwrite without asking)
# on a card without GET METADATA (non-Yubico PIV, or Yubico firmware < 5.3)
# the two steps can't share the key by slot name across invocations; fold
# them into one so the fresh public key never needs a temp file:
# keyroostctl piv cert generate --slot 9a --subject "CN=$USER" --generate-key \
# --algorithm eccp256 --mgmt-key env:PIV_MGMT --pin env:PIV_PIN
# (same --generate-key convenience on `piv cert request`)
# --- Token2 Molto2 (TOTP programming) ---
keyroostctl molto info
keyroostctl molto import --slot 0 # asks for the otpauth:// URI (hidden)
keyroostctl molto import --slot 0 --uri env:URI # or from an environment variable
keyroostctl molto import --in ~/Downloads/aegis.json --slot 0 --dry-run # validate first
keyroostctl molto import --in ~/Downloads/aegis.json --slot 0 --file-password env:PW # encrypted export
keyroostctl molto seed set --slot 5 # asks for the seed (base32; --encoding hex for hex)
# --- Token2 single-profile programmable token (OTPC / miniOTP / C30x) ---
keyroostctl prog info # serial, model, and on-device clock
keyroostctl prog seed set # asks for the base32 seed (hidden)
keyroostctl prog config set --algorithm sha1 --period 30 --display-timeout 30
# --- Token2 on-device OTP (PIN+ Series FIDO keys) ---
keyroostctl otp list
keyroostctl otp add --app GitHub --account [email protected] # asks for the seed (hidden)
keyroostctl otp code --app GitHub --account [email protected]
keyroostctl otp import -i export.json # add every code in an export (asks first)
# --- Destructive operations (ask y/N, or type "reset", in a terminal) ---
keyroostctl factory-reset --device my-yubikey # asks you to type "reset"
keyroostctl factory-reset --device my-yubikey --yes # in a script
# name a key to target it when several are plugged in (opt-in)
keyroostctl name set my-yubikey # on this computer
keyroostctl name set my-yubikey --store key # on the key (asks for the FIDO PIN)
keyroostctl name list
# machine-readable output for scripts (status and query commands)
keyroostctl --json list # {"keys": [...]}, each with its --device value
keyroostctl --json piv info --device my-yubikey
# man pages (completions: see "Shell completions" above)
keyroostctl manpage ./man && man -l ./man/keyroostctl-piv.1
# launch the GUI (per-device tabs: Overview, FIDO2, Authenticator, OpenPGP, PIV,
# On-device OTP, plus the distinct Molto2 and single-profile programmable-token views)
keyroost
With one key connected, every command uses it. With several, a command run in
a terminal shows a numbered list to pick from; a script is refused with the
exact --device value for each key. --device takes a saved name, a serial,
or the number keyroostctl list shows (prefix name:, serial: or list:
to say which you mean); keyroostctl --json list gives scripts the same
values. A Molto2 answers to either serial keyroost shows for it: the one in
keyroostctl list or the one molto info prints. --reader (a reader
name, or a unique part of one) and --path are expert overrides: they skip
the capability check, and a value that matches no detected key is used as
typed. Neither can be combined with --device.
Five flags have a short form, with the same meaning on every command:
-d (--device), -y (--yes), -o (--out), -i (--in) and
-s (--slot).
Commands that erase or replace something on the key (resets, deletes,
overwriting a used slot or seed) name the key and ask y/N first
(factory-reset and otp interface ask you to type a phrase). A script has
no terminal to ask on, so it adds --yes; so does a command whose PIN or
seed is piped in on stdin. If you answered a question, keyroost checks it is
still the same key before acting.
keyroostctl name set "Work YubiKey" # keep the name on this computer
keyroostctl name set "Work YubiKey" --store key # keep it on the key (asks for the FIDO PIN)
keyroostctl name list # every name, where it lives, what is connected
keyroostctl piv info -d "Work YubiKey" # use it
keyroostctl name clear "Work YubiKey" # remove it, wherever it is stored
With several keys connected, say which one to name with -d (a list
number or serial:…), or pick from the list a terminal shows. Running
name set on a named key renames it where its name already is; a name on
this computer moves to the key with --store key.
A name on this computer goes in keys.json with a salted fingerprint of the
key, never its serial. The salt is a random value in keys.salt beside it.
If keys.salt is lost, the names on this computer no longer match their keys:
clear them and set them again (putting the old keys.salt back doesn't help).
A keys.json from v0.12 or earlier, which held serials, is converted the
first time this version reads it.
-d "Work YubiKey" always means that one. The other shows as
Work YubiKey (1234), with the last four characters of its serial; select
it by its list number or serial:….fido blob clear and a FIDO reset erase the name with everything else
(fido blob clear --keep-name keeps it). The FIDO2 standard also lets
other software remove large-blob entries that no passkey uses; we haven't
seen it happen. If a key loses its name, it shows as unnamed until the name
is written back: name list prints the command to do so, and the desktop
app offers to.The format of the name entry, with test vectors for other tools, is in
docs/PROTOCOL-device-label.md.
keyroostctl never takes a PIN, password, key, seed or otpauth:// URI on the
command line, where it would land in shell history and ps. By hand, leave
the flag out and a terminal asks for the secret without showing what you type.
In a script, the flag's value says where the secret comes from: env:NAME
reads an environment variable and stdin reads one line:
keyroostctl fido credential list # asks "PIN:" (nothing is shown as you type)
keyroostctl fido credential list --pin env:KR_PIN # read from the KR_PIN environment variable
pass show fido-pin | keyroostctl fido credential list --pin stdin # one line from stdin
Typing the secret itself as the value (--pin 123456) is refused, and the
value is never shown. --mgmt-key default uses the factory-default PIV
management key.
A new PIN, password or key typed at the prompt is asked twice and must match.
When one command reads two secrets from stdin, the current one is the first
line and the new one the second (each flag's --help says which line):
printf '%s\n%s\n' "$OLD_PIN" "$NEW_PIN" | keyroostctl piv pin change --pin stdin --new-pin stdin
otp add and oath add read the seed first, then the PIN or password; on
molto, --customer-key stdin is always the first line. Seeds are base32
unless --encoding hex; a new Molto2 customer key is hex unless --encoding ascii, and --customer-key-encoding says how the current one is written. A
Molto2 customer key left out means the factory default. In a script with no
terminal, a command with no source for a secret it needs is refused, naming
the flag. In Git Bash (mintty) on Windows the prompt needs a real console:
run winpty keyroostctl …, or use env:NAME.
Move the codes from an authenticator app onto a key in one step. keyroost
reads Aegis exports (plain or encrypted), 2FAS exports, a text file with one
otpauth:// URI per line, and QR screenshots (PNG or JPEG), including a
Google Authenticator export.
keyroostctl oath import -i export.json --dry-run # show what would happen; writes nothing
keyroostctl oath import -i export.json # YubiKey, Solo 2, Nitrokey 3: asks once, then adds
keyroostctl otp import -i export.json # a Token2 key's OTP storage
keyroostctl molto import -i export.json --slot 90 # a Molto2, from slot #90 on
keyroostctl oath import -i export.json --file-password env:EXPORT_PW # an encrypted Aegis export
In the GUI, drop the file on the window, or click Import file… in a key's Authenticator, On-device OTP or Molto2 view. The Import authenticator codes dialog lists the entries, lets you pick the key under Add to, and shows what each entry will become before anything is written. An encrypted export asks for its password in the dialog. A certificate or key file dropped on the window gets a note saying where it can be imported instead.
Before writing, keyroost reads what is already on the key and marks each entry:
--replace (in the GUI, "Replace codes already on the key
that have the same name") replaces it instead. Some keys refuse a second
code with the same name; there, replacing removes the old code first, and
if adding the new one then fails, the old code is gone.On a Molto2, entries fill consecutive slots from --slot. A slot already in
use is replaced (replace), an entry with no issuer or account is
skipped without using up a slot, and entries past slot #99 are skipped.
keyroost asks once before writing (a script adds -y). It writes one code
at a time and stops at the first error, saying what was added and what was
not. Running the same import again is safe: codes it already added now
show as exists. --dry-run writes nothing; for oath import and otp import the key must be connected, because the preview depends on what is
on it. molto import --dry-run reads slot use from a connected Molto2, and
works without one too. --json prints the plan and, after writing, what
happened to each entry.
An encrypted export's password comes from --file-password env:NAME or
--file-password stdin, or a terminal asks for it. When the export's
password and the key's password or PIN are both piped in, the export's
password is the first line. On molto import, --customer-key stdin is
the first line and the export's password the second.
What each key can store:
| Key | Holds | Limits |
|---|---|---|
| YubiKey | 32 codes, or 64 from firmware 5.7 | names up to 64 bytes; SHA-512 from firmware 4.3.1; 6 to 8 digits |
| Solo 2 | not reported (writing stops if it fills up) | names up to 127 bytes; no SHA-512; secrets up to 64 bytes |
| Nitrokey 3 | 50 codes | names up to 128 bytes; 30-second codes only (the key doesn't store the period); no SHA-512 |
| Token2 OTP storage | 50 codes | app and account up to 64 bytes each, plain ASCII only; no SHA-512; HOTP only with a counter of 0 (the key always starts at 0) |
| Molto2 | 100 slots (#00 to #99) | TOTP only, 30 or 60 seconds; SHA-1 or SHA-256; 4, 6, 8 or 10 digits; titles shortened to 12 bytes |
On a YubiKey or Solo 2, a TOTP code with a period other than 30 seconds is
stored under a name that starts with its period, such as 60/GitHub:alice
(the naming Yubico's own tools use). oath code and the GUI compute
those codes with that period.
A Solo 2 name longer than 127 bytes is refused by oath import, oath add
and the GUI before anything is sent: on Solo 2 firmware 2.964, a 128-byte
name made the key stop responding until it was unplugged. On YubiKey 4
firmware 4.0 to 4.3.4, a name that is the start of another name already on
the key is skipped (and oath add refuses it), following the same rule as
Yubico's ykman.
Importing into a Token2 key's OTP storage and into a Nitrokey 3 is built from the published specifications (Token2's OTP specification and Nitrokey's secrets-app source). If you use one of these keys, reports in the issue tracker are welcome.
A command prints its result on stdout and everything else (prompts, progress, notes, warnings) on stderr. You see both at a terminal, but redirecting saves only the result:
keyroostctl molto list > slots.txt # the file gets just the table
keyroostctl piv cert export --slot 9a # prints the certificate as PEM
With --json (on the commands that support it), a command prints one
object; a list sits under a named key such as keys or accounts, and
every field is there, null when unknown. Don't rely on key order. The
--debug output is for people reading along and may change between
releases. So may the human-readable text of any command; to read values in
a script, use --json where a command has it.
Breaking changes are tracked per release on the site's
migration notes
page, with the exact before → after for scripts and library consumers.
v0.15.0 renames no command or flag; a PIN or touch policy on a
Nitrokey 3 is now refused without --force, Rust 1.87 is the new floor, and
piv --json info now reports
"applet_fingerprint": "Trussed::Nitrokey" (was Trussed::NitroKey), and
fido --json info's firmware is the vendor's own string (a Solo 2's
2.964, a Token2's R3.4) or null. Library users: see the migration page.
v0.14.0 changes openpgp key import --generate: it now needs
--backup FILE and --name, fills all three slots, and no longer takes
--slot. Library users: usb_bus / usb_address on HidDevice,
ReaderProbe and YubiKeyCcid are replaced by one usb_device field.
v0.13.0 makes more commands ask before they erase or replace something
(scripts pass --yes), refuses, in a script, to pick among several keys, and
takes no secret on the command line (each secret flag takes env:NAME or
stdin, and a terminal asks when it is left out). Commands are regrouped
(piv pin change, fido credential list), five short flags mean the same
everywhere, and two commands read their stdin lines in a new order. It also
reshapes --json output, moves everything but the result to stderr, and
makes piv cert export write PEM.
Two older ones to know about: v0.7.5 renames the global device selector from
--name to --device, and v0.6.0 moved the Molto2 / FIDO commands under
the molto and fido groups.
One name to watch: in ≤0.5.x keyroostctl factory-reset meant the Molto2
profile reset (now keyroostctl molto reset). The name is live again, but it
now wipes every applet on a key. See the migration page before re-running an
old script.
| Crate | Purpose | External deps |
|---|---|---|
keyroost-proto | Pure-Rust Molto2 wire protocol (SM4, SHA-1, APDU, MAC) | none |
keyroost-transport | PC/SC discovery, Molto2 session, CCID serial, OATH/OpenPGP/PIV applets, Token2 OTP session, import writers for OATH / Token2 OTP / Molto2 (in-tree keyroost-import), PIV key import from .p12 / .pfx (in-tree keyroost-p12) and the slot self-test (in-tree keyroost-pivtest) | pcsc, aes/des/cipher (mgmt-key auth), getrandom, zeroize, miniz_oxide (gzip-compressed PIV certificates); hidapi on macOS/Windows; in-tree keyroost-windevtree on Windows |
keyroost-hid | USB HID enumeration of FIDO devices, and the "same physical USB device" evidence used to pair a key's FIDO and smart-card sides | none on Linux (sysfs); hidapi on macOS/Windows; in-tree keyroost-windevtree on Windows |
keyroost-ctap | FIDO2/CTAP-HID transport, CBOR, PIN protocols, credential management, large-blob storage (including the name a key carries) | RustCrypto (sha2/hmac/aes/cbc/p256/rand_core) for client-PIN, aes-gcm + miniz_oxide for largeBlob entries, zeroize; hidapi on macOS/Windows |
keyroost-oath | Pure-Rust Yubico/Trussed OATH (TOTP/HOTP) byte layer | zeroize |
keyroost-openpgp | Pure-Rust OpenPGP Card v3.4 byte layer (APDU + BER-TLV) | zeroize |
keyroost-piv | Pure-Rust PIV (SP 800-73-4) byte layer; full management + SPKI/PEM, applet fingerprinting and per-device feature gates | zeroize |
keyroost-p12 | Host-side PKCS#12 (.p12 / .pfx) reader for piv key import: legacy and modern encryption, MAC check, keys paired with their certificates |
The Molto2 wire protocol is documented in docs/PROTOCOL.md
— the APDUs, the SM4-based MAC, and the TLV config payload, described as facts
about the device rather than any one implementation. The sibling single-profile
programmable token (OTPC / miniOTP / C30x) is documented the same way in
docs/PROTOCOL-token2prog.md. The name a key
can carry in its FIDO2 large-blob array is documented, with test vectors, in
docs/PROTOCOL-device-label.md. The FIDO2,
OATH, OpenPGP and PIV layers follow their respective public standards.
General questions, packaging notes, and other non-security correspondence are welcome at [email protected].
Security issues should not go to email — please use GitHub's private
vulnerability reporting (see SECURITY.md) so the report stays
private until a fix ships.
Licensed under either of
at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
This dual-license is the Rust ecosystem default and matches what serde,
tokio, clap, and most of the ecosystem use.
piv infoopenpgp info| YubiKey (5 series) | FIDO2, OATH (incl. import), OpenPGP, PIV | Built and verified against a YubiKey 5.7, including PIV key import from a .p12 / .pfx file (RSA-2048/4096, ECC P-256/P-384, Ed25519, X25519, with PIN and touch policies). |
| SoloKeys Solo 2 | FIDO2, OATH (incl. import) | Trussed firmware; no OpenPGP applet. HOTP caveat: the last-shipped Solo 2 firmware (2.964) computes HOTP over a 4-byte counter where RFC 4226 specifies 8, so its HOTP codes won't verify against standards-compliant servers (hardware-verified; fixed in the current upstream Trussed secrets app, but Solo 2 no longer receives firmware updates). TOTP is unaffected — its 8-byte time challenge comes from the host. |
| Nitrokey 3 | FIDO2, OATH (incl. import), PIV; OpenPGP detected | Built around the same Trussed firmware core as Solo 2, but the final firmware is different — e.g. Nitrokey 3 has PIV support, while Solo 2 does not. PIV verified on a Nitrokey 3A NFC (firmware 1.8.3), contributed by @episource; keyroost reads the serial and firmware version from the Nitrokey admin application. Importing an RSA-2048 key and certificate from a .p12 / .pfx file into slot 9A verified on firmware 1.9.1. OATH verified on firmware 1.9.0: adding, listing, TOTP and HOTP codes, and touch-required accounts, which also need a touch to save; deleting any account, or resetting the Authenticator, also needs a touch. This firmware has no CALCULATE ALL, so the Authenticator tab reads codes one at a time. Importing codes into its OATH storage follows Nitrokey's published secrets-app source (see Importing codes). The OpenPGP applet is detected but not yet exercised by this project. |
| Swissbit iShield Key 2 Pro (and iShield Key 1) | PIV (partial) | Key generation and certificate handling verified by @episource on their own iShield Key 2 Pro. keyroost doesn't use the slot key type and PIN/touch policy this card reports in GET METADATA (#113), so a slot without a certificate reads "no certificate (a key may be present)". Answers the Yubico version extension with four bytes instead of three; keyroost keeps the reply as sent. On the iShield Key 1 and 2, keyroost reads the serial and device name from Swissbit's own management applications (and, on the Key 1, the firmware version) for piv info and the GUI, contributed by @episource (#163). Other applets not yet exercised by this project. |
| Any standards-compliant FIDO2 key (e.g. Thales, Feitian, Titan) | FIDO2 / CTAP2; OATH / OpenPGP / PIV only if the key carries those applets | keyroost implements the published specs, not vendor-specific behavior, so the fido commands — getInfo, passkey management, PIN, reset — work on any CTAP2 authenticator, including ones not listed here. Optional features (fingerprint, largeBlob, authenticatorConfig) surface only when the key advertises them in getInfo. The smart-card applets apply only to keys that expose an OATH / OpenPGP / PIV applet over PC/SC. Older U2F-only (CTAP1) keys show as U2F in list and the app and don't support the CTAP2 management commands. |
keyroost and keyroostctl packages plus a development shell, tested on
x86_64 Linux (#109).flake.lock current
(#132).piv chuid generate
(#102), short-APDU
chaining (#101), Identiv
uTrust mutual-auth tolerance
(#104), any-length GET
VERSION / Swissbit compat (#110),
the PIV activity log and APDU trace
(#114), --generate-key
on piv cert generate / piv cert request
(#116), status-word
meanings in errors (#118),
the PIV-refresh APDU deduplication
(#119), and PIV applet
fingerprinting with per-device feature gates, HID Crescendo management and
ECC P-521 (#128),
Swissbit iShield serial, device name and firmware reads
(#163), and X.509 key
usage for certificates and CSRs
(#164) — most of it
hardware-verified on their own cards.rc2, des, cbc, aes, hmac, sha2, pbkdf2, p256/p384, ed25519-dalek, x25519-dalek, rsa, zeroize; in-tree keyroost-proto, keyroost-piv, keyroost-oath |
keyroost-pivtest | Host-side round-trip self-test for a PIV slot key (decrypt / key-agree / sign), checked against a public key: the slot certificate's, or one given (an imported key's) | rsa, p256, p384, p521, ed25519-dalek, x25519-dalek, zeroize |
keyroost-token2otp | Pure-Rust Token2 OTP-on-FIDO byte/codec layer (APDU + HID framing) | RustCrypto (sha2/hmac/aes/cbc/p256/rand_core) for ECDH seed encryption and the OTP-PIN session, zeroize |
keyroost-token2prog | Pure-Rust Token2 single-profile programmable-token wire protocol (SM4 seed/MAC, fixed device key, config TLV); reuses keyroost-proto | zeroize |
keyroost-keyring | Friendly-name registry (keys.json, salted fingerprints) | serde, serde_json, getrandom (the per-computer salt); in-tree SHA-256 from keyroost-proto |
keyroost-resolve | Shared key-identity resolution (USB + CCID serials, topology match) and naming (names on the key and on this computer) | none (in-tree only, including keyroost-ctap) |
keyroost-rsakey | Host-side RSA key loading (PKCS#1/PKCS#8, PEM/DER, 2048-4096 bits) for OpenPGP import | rsa, zeroize |
keyroost-pgpkey | Host-side OpenPGP key sets: key generation, GnuPG-compatible passphrase-protected backups, reading backups and GnuPG exports, expiry changes; restore onto a card behind the card feature | rsa, p256, p384, p521, k256, ed25519-dalek, x25519-dalek, sha2, aes, rand_core, zeroize; in-tree keyroost-transport (behind card) |
keyroost-import | otpauth:// + Aegis / 2FAS / otpauth-list parsers, file classification, and the per-key import plan and write loop | zeroize; serde/serde_json (behind bulk); scrypt, aes-gcm, base64 (behind encrypted, which implies bulk) |
keyroost-qr | QR 2FA import from PNG/JPEG screenshots and Google Authenticator export batches — always built in; the GUI's live screen capture is behind the qr feature (on in the release archives and the AppImage, off in the Flatpak) | rqrr, png, jpeg-decoder, zeroize |
keyroost-winwebauthn | Windows-only helper for the non-admin FIDO2 path: detect a FIDO key via the HID access-denied signal, open Windows' security-key settings, and relaunch elevated; inert on non-Windows | windows-sys (Windows only) |
keyroost-windevtree | Windows-only device-tree lookups that let a key's FIDO HID node and smart-card reader be recognized as one physical key, without opening the device or connecting to the card; isolates the unsafe Win32 FFI; inert on non-Windows | windows-sys (Windows only) |
keyroost-screengrab | Windows-only still screen capture (GDI BitBlt) for QR-from-screen; isolates the unsafe Win32 FFI from the GUI crate; inert on non-Windows | windows-sys (Windows only) |
keyroostctl | Command-line interface | clap, clap_complete, clap_mangen, serde/serde_json, zeroize, rpassword (hidden PIN/password prompt) |
keyroost | egui desktop GUI | eframe, egui, arboard, rfd, pollster, png, base64, serde/serde_json, zeroize; x11rb/ashpd on Linux (qr feature); wgpu (DirectX 12 only) on Windows; winresource build-dep on Windows |