- Scheme 78.4%
- Rust 9.5%
- Shell 9.3%
- Makefile 2.7%
- Common Lisp 0.1%
| .builds | ||
| .jerboa | ||
| bin | ||
| docs | ||
| scripts | ||
| support | ||
| test | ||
| yubikey | ||
| yubikey-native | ||
| .gitignore | ||
| .gitsafeignore | ||
| .jerbuild | ||
| AGENTS.md | ||
| jpkg.lock | ||
| jpkg.policy.sexp | ||
| jpkg.sexp | ||
| LICENSE | ||
| LINUX-PIV-JSH-HANDOFF.md | ||
| Makefile | ||
| PLAN-NATIVE.md | ||
| PLAN.md | ||
| README.md | ||
| rust-toolchain.toml | ||
| SECURITY.md | ||
| yubikey.json | ||
jerboa-yubikey
YubiKey support for Jerboa —
a pure-Jerboa replacement for Yubico's ykman, plus a library that
other Jerboa apps can use for YubiKey-backed authentication.
Status
Pre-alpha but usable: device info, OATH, OTP slot config, and PIV all work
over built-in PC/SC. PIV can authenticate the management key, generate a key
on-card, and decrypt / unwrap — enough to provision and unlock a slot with no
external tool (no ykman, no yubico-piv-tool). A native FIDO2 assertion
primitive (HID CTAP2) is also included. See PLAN.md for the roadmap.
What's in scope
Everything ykman does, in three milestones:
- M1 — OATH + info. TOTP/HOTP credential management over CCID.
- M2 — Challenge-response. HMAC-SHA1 for password derivation
(KeePassXC-style). This is the feature that unlocks YubiKey-backed
auth for
jerboa-shell. - M3 — Management, PIV, OpenPGP, FIDO. Full ykman parity.
Why not just call ykman?
ykman is a Python CLI. Calling it from another program forces secrets
through argv and stdout — a regression in security. As a library,
jerboa-yubikey keeps secret material in-process.
CLI
umask 077
ykjr info # device info (serial, firmware, apps)
ykjr list # list connected YubiKeys
ykjr oath accounts list # list OATH credentials
ykjr oath accounts code github:alice # generate TOTP code
ykjr oath accounts add github:alice --secret-fd 3 3<oath-seed.txt
ykjr oath accounts import --uri-fd 3 3<otpauth-uri.txt
ykjr oath accounts import ./qr.png --qr # import QR image via zbarimg
ykjr oath accounts import ./otpauth.txt # import text containing URI
ykjr otp chalresp 2 --key-fd 3 3<otp-key.hex
ykjr otp chalresp 2 --generate --export-key-fd 3 3>otp-key.hex
ykjr piv info # PIV slots
ykjr piv pin verify --pin-fd 3 3<piv-pin.txt
With no dedicated-fd option, secret-bearing commands prompt on the controlling terminal with echo disabled. Secrets are never accepted through argv. Each named fd consumes exactly one newline-delimited value, so change/unblock commands may use separate fds or consecutive lines on one shared fd. Generated OTP keys are never printed and explicit export refuses a terminal descriptor.
Library usage (from another Jerboa app)
(import (yubikey auth))
;; Challenge-response: HMAC-SHA1 with key-resident secret.
(yubikey-chalresp 2 #vu8(0 1 2 ...)) ;; => bytevector(20)
;; Deterministic password derivation per host.
(yubikey-derive-password "host.example" 2)
;; OATH lookup.
(yubikey-oath-code "github:alice") ;; => "123456"
;; WebAuthn/FIDO2 assertion for a service-supplied challenge.
(import (yubikey fido2))
(define assertion
(yubikey-fido2-assert "proton.me" client-data-json credential-id))
client-data-json must be the exact WebAuthn clientDataJSON bytes sent
back to the service. The native CTAP backend hashes those bytes once before
asking the YubiKey to sign, and returns credential ID, authenticator data,
and signature bytevectors.
PIV is the basis for "use my YubiKey to unlock an encrypted file" — provision a
slot and unwrap a key with no external tool (built-in PC/SC). The management
key is mutually authenticated (AES, default key) before GENERATE:
(import (yubikey piv) (yubikey transport connection))
(yk-with-connection
(lambda (conn)
;; one-time provisioning (default AES-192 management key on YK 5.7+):
(yk-piv-authenticate-management-key conn PIV-DEFAULT-MGMT-KEY)
(yk-piv-generate-key conn PIV-SLOT-KEY-MGMT
'algorithm: PIV-ALG-RSA-2048 'pin-policy: 3 'touch-policy: 3) ;; → public key
;; later: unwrap a key wrapped to that public key (you do RSA-OAEP in software):
(yk-piv-pin-verify conn "123456")
(yk-piv-decrypt conn PIV-SLOT-KEY-MGMT wrapped-256-bytes))) ;; → plaintext block
Build
make run ARGS='info' # development (interpreter)
make test # tests
cargo build --manifest-path yubikey-native/Cargo.toml --release
# optional shared native backend
make binary # native binary (dynamic, requires Chez)
make release-evidence # tests, binary smoke, audit, SBOM, reproducibility
Release evidence and dependency provenance are documented in
docs/release-evidence.md and
docs/dependency-provenance.md.
Dependencies
- A Yubico YubiKey (any series, ideally YK 5 or later).
- Transport:
- Linux/BSD: the direct-USB Rust backend (
yubikey-native, built bymake binary) is the primary — nopcscd, nolibpcsclite, no daemons. Linux just needs a udev rule granting your user access to the YubiKey's USB device node (the standard Yubico69-yubikey.rulesworks).pcsc-liteis kept as an opportunistic fallback if the native backend fails to claim the device. - macOS: PC/SC via Apple's built-in
PCSC.framework— nothing to install. The native backend is not used on macOS because the OS owns the CCID interface and won't let a second process claim it.
- Linux/BSD: the direct-USB Rust backend (
- FIDO2 assertions use the native HID backend (CTAP2 is not a PC/SC applet),
so they still need
yubikey-nativebuilt. - Jerboa toolchain.
- Optional QR image import:
zbarimg(zbar-toolson many Linux distros).
License
Apache License 2.0. See LICENSE.