Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,60 @@ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.10.0] - 2026-09-20

### Added
- **Nix flake.** The repository now builds as a flake: `keyroost` (the
default package), `keyroostctl`, and a development shell, with the GUI's
X11/Wayland libraries wired into the binary's rpath on Linux. Tested on
x86_64 Linux. Contributed by @MakeShiftArtist. ([#109])
- **PIV slot key self-test.** `keyroostctl piv test --slot <slot>` (and a
Test button in the GUI slot pane) proves a slot's private key works end to
end: keyroost builds a fixed challenge from the slot certificate's public
key, the card runs every operation the key type supports (decrypt for RSA,
key agreement for P-256/P-384/X25519, sign for RSA/ECDSA/Ed25519), and the
host verifies each result against that public key. Read-only on the card;
needs the PIN unless the slot's PIN policy is "never". The verification
side lives in a new `keyroost-pivtest` crate, which adds `p384`,
`ed25519-dalek` and `x25519-dalek` (RustCrypto/dalek) to the tree.
Contributed by @episource. ([#127])
- **Fingerprint unlock for Token2 Bio3 OTP.** On an R3.4 Bio3 key with a
fingerprint enrolled, protected OTP codes can be released by a sensor touch
as well as by the PIN. The CLI's `otp` group gains `fp-status`, `fp-enable`,
`fp-disable`, `fp-list` (touch to unlock) and `unlock-list` (fingerprint
first, PIN fallback; `--pin-only` forces the PIN), and the GUI's unlock
prompt offers a touch button alongside the PIN field when fingerprint
protection is on. Enrollment itself is done through the key's FIDO2
fingerprint setup. Contributed by @token2. ([#130])

### Changed
- **`keyroost-token2otp`'s `PinFlag` is now `#[non_exhaustive]`.** It gained an
`fp_enable` field this release; sealing the struct lets later firmware flags
be added as fields without breaking library consumers, matching the other
response types. Obtain a `PinFlag` from `PinFlag::parse` rather than a struct
literal. ([#130])

### Fixed
- **PIV management-key authentication no longer assumes 3DES on cards that
lack GET METADATA.** That Yubico extension is the only place a card reports
its 9B key's algorithm, and applets without it (the Cryptnox OpenFIPS201
variant, which ships with an AES key) were authenticated as 3DES and
failed. When the extension is absent, keyroost now probes each algorithm
with a bare GENERAL AUTHENTICATE witness request (no key material leaves
the host), keeps the ones the card accepts, and narrows by the length of
the key in hand; a 24-byte key that fits both 3DES and AES-192 still
resolves to 3DES, so older YubiKeys behave as before. Applies to the CLI
and the GUI alike, and `--debug` traces each probe's verdict. Contributed
by @episource. ([#124])

### Security
- **The `--debug` trace no longer prints Token2 OTP PIN material.** The
PIN-bearing commands — `SET_OTP_PIN`, `CHANGE_OTP_PIN`, and the PIN-carrying
forms of `VERIFY_OTP_PIN` (a plain PIN verify, and the R3.4 Bio3
fingerprint-protection toggle) — now have their request bodies length-redacted
in the trace, the same as seed writes. The 1-byte `VERIFY` forms that carry no
secret (lock, fingerprint verify) still show in full. ([#131])

## [0.9.0] - 2026-08-31

### Added
Expand Down Expand Up @@ -1022,12 +1076,18 @@ multi-vendor hardware-security-key manager, then took its neutral name. Highligh
[#104]: https://github.com/framefilter/keyroost/pull/104
[#106]: https://github.com/framefilter/keyroost/issues/106
[#107]: https://github.com/framefilter/keyroost/issues/107
[#109]: https://github.com/framefilter/keyroost/issues/109
[#110]: https://github.com/framefilter/keyroost/pull/110
[#114]: https://github.com/framefilter/keyroost/pull/114
[#116]: https://github.com/framefilter/keyroost/pull/116
[#118]: https://github.com/framefilter/keyroost/pull/118
[#119]: https://github.com/framefilter/keyroost/pull/119
[Unreleased]: https://github.com/framefilter/keyroost/compare/v0.9.0...HEAD
[#124]: https://github.com/framefilter/keyroost/issues/124
[#127]: https://github.com/framefilter/keyroost/issues/127
[#130]: https://github.com/framefilter/keyroost/issues/130
[#131]: https://github.com/framefilter/keyroost/issues/131
[Unreleased]: https://github.com/framefilter/keyroost/compare/v0.10.0...HEAD
[0.10.0]: https://github.com/framefilter/keyroost/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/framefilter/keyroost/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/framefilter/keyroost/compare/v0.7.8...v0.8.0
[0.7.8]: https://github.com/framefilter/keyroost/compare/v0.7.7...v0.7.8
Expand Down
38 changes: 19 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ members = [
]

[workspace.package]
version = "0.9.0"
version = "0.10.0"
edition = "2021"
rust-version = "1.85"
license = "MIT OR Apache-2.0"
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ a short, vendor-neutral tour of what FIDO2, OATH, OpenPGP, and PIV actually do.
clearing a slot's certificate (`delete-cert`) or key (`delete-key`, on YubiKey
5.7+), moving a key between slots (`move-key`, on YubiKey 5.7+), writing a
fresh CHUID with a random GUID (`new-chuid`) so Windows re-reads a
reprovisioned card, and PIN / PUK / management-key changes and applet reset. Every slot-taking command addresses
reprovisioned card, and PIN / PUK / management-key changes and applet reset.
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.
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
Expand All @@ -93,7 +97,11 @@ a short, vendor-neutral tour of what FIDO2, OATH, OpenPGP, and PIV actually do.
On R3.4+ keys the codes can be put behind an OTP PIN (`otp set-pin`,
`otp verify`, `otp change-pin`, `otp remove-pin`, `otp pin-status`); note
there is no PIN reset — a blocked PIN is recoverable only by erasing every
OTP entry.
OTP entry. On Bio3 keys with a fingerprint enrolled, a fingerprint touch can
release protected codes as an alternative to the PIN (`otp fp-status`,
`otp fp-enable`, `otp fp-disable`, `otp fp-list`, and `otp unlock-list`,
which tries the fingerprint and falls back to the PIN); fingerprint
enrollment itself is done through the key's FIDO2 fingerprint setup.
- **One-shot factory reset** — `keyroostctl factory-reset --yes` (and a card on
the GUI device Overview tab) resets every resettable applet on a key in turn:
OATH, OpenPGP, PIV, Token2 OTP, then FIDO2. On a USB key the FIDO2 step ends
Expand Down Expand Up @@ -670,6 +678,7 @@ old script.
| `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 | `zeroize` |
| `keyroost-pivtest` | Host-side round-trip self-test for a PIV slot key (decrypt / key-agree / sign), checked against the slot certificate's public key | `rsa`, `p256`, `p384`, `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`); serial matching | `serde`, `serde_json` |
Expand Down
4 changes: 3 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ What keyroost does **not** defend against:
under `forbid(unsafe_code)` — RustCrypto (`sha2`, `hmac`, `aes`, `des`,
`cbc`, `cipher`, `p256`, `aes-gcm`), `getrandom`, `zeroize`, `scrypt`
(Aegis vaults), `rsa`/`rand` (host RSA keygen, confined to
`keyroost-rsakey`), and `miniz_oxide` (CTAP large-blob deflate). QR and
`keyroost-rsakey`), `p384`/`ed25519-dalek`/`x25519-dalek` (host-side
verification of the PIV slot self-test, confined to `keyroost-pivtest`),
and `miniz_oxide` (CTAP large-blob deflate). QR and
image decoding (`rqrr`, `png`, `jpeg-decoder`) is confined to
`keyroost-qr`. The README's "Workspace layout" table lists them per
crate. No new dependency lands without that justification.
Expand Down
22 changes: 11 additions & 11 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ decision to "Standing decisions" at the bottom so it is not re-litigated.
Deliberately unversioned: the previous `TODO-v0.7.5.md` / `TODO-hardening.md`
pair rotted because version-named files accumulate layers nobody rereads.

Current work: **v0.9.0** — OpenPGP ECC key algorithms
([#106](https://github.com/framefilter/keyroost/issues/106)), Token2 OTP PIN
protection ([#107](https://github.com/framefilter/keyroost/issues/107)), PIV
any-length GET VERSION ([#110](https://github.com/framefilter/keyroost/pull/110)),
`--generate-key` on self-sign/request-cert
([#116](https://github.com/framefilter/keyroost/pull/116)), the PIV activity
log + APDU trace ([#114](https://github.com/framefilter/keyroost/pull/114)),
status-word meanings in errors
([#118](https://github.com/framefilter/keyroost/pull/118)), the T=0 chaining
fix ([#103](https://github.com/framefilter/keyroost/issues/103)) and the
`changelog.d/` fragment system. The release run is under way.
Current work: **v0.10.0** — the Nix flake
([#109](https://github.com/framefilter/keyroost/pull/109)) and its cross-build
CI ([#144](https://github.com/framefilter/keyroost/pull/144)), the PIV slot
key self-test ([#127](https://github.com/framefilter/keyroost/pull/127)),
Token2 OTP fingerprint unlock for Bio3 keys
([#130](https://github.com/framefilter/keyroost/pull/130)), the PIV
management-key algorithm probe
([#124](https://github.com/framefilter/keyroost/pull/124)), and OTP PIN-material
redaction from the debug trace
([#131](https://github.com/framefilter/keyroost/pull/131)). The release run is
under way.

---

Expand Down
4 changes: 0 additions & 4 deletions changelog.d/added-109-nix-flake.md

This file was deleted.

10 changes: 0 additions & 10 deletions changelog.d/added-127-piv-slot-self-test.md

This file was deleted.

8 changes: 0 additions & 8 deletions changelog.d/added-130-otp-fingerprint-unlock.md

This file was deleted.

11 changes: 0 additions & 11 deletions changelog.d/fixed-124-piv-mgmt-key-algorithm-probe.md

This file was deleted.

6 changes: 0 additions & 6 deletions changelog.d/security-131-otp-pin-trace-redaction.md

This file was deleted.

4 changes: 2 additions & 2 deletions crates/keyroost-ctap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ workspace = true
hidapi-backend = ["dep:hidapi", "keyroost-hid/hidapi-backend"]

[dependencies]
keyroost-hid = { path = "../keyroost-hid", version = "0.9.0" }
keyroost-proto = { path = "../keyroost-proto", version = "0.9.0" }
keyroost-hid = { path = "../keyroost-hid", version = "0.10.0" }
keyroost-proto = { path = "../keyroost-proto", version = "0.10.0" }
# Optional so a default Linux build pulls nothing (the hidraw File backend is
# dependency-free); enabled by `hidapi-backend` for testing.
hidapi = { version = "2.6", optional = true, default-features = false, features = [
Expand Down
2 changes: 1 addition & 1 deletion crates/keyroost-import/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ bulk = ["dep:serde", "dep:serde_json"]
encrypted = ["bulk", "dep:scrypt", "dep:aes-gcm", "dep:base64"]

[dependencies]
keyroost-proto = { path = "../keyroost-proto", version = "0.9.0" }
keyroost-proto = { path = "../keyroost-proto", version = "0.10.0" }
# Same scoped exception as keyroost-ctap/keyroost-rsakey: compiler-fence-
# backed wiping of secret buffers is not something to hand-roll.
zeroize = { version = "1", default-features = false, features = ["alloc"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/keyroost-oath/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ description = "Yubico/Trussed OATH (TOTP/HOTP) applet protocol over APDU."
workspace = true

[dependencies]
keyroost-proto = { path = "../keyroost-proto", version = "0.9.0" }
keyroost-proto = { path = "../keyroost-proto", version = "0.10.0" }
zeroize = { version = "1", default-features = false, features = ["alloc"] }
Loading
Loading