[0.4.11] - 2026-09-08
The catch-up release, and the one where the instruments were audited harder than
the firmware — then it kept going for two more weeks, and the second half is one
defect repeated. Everything CTAP 2.2 and 2.3 added that RS-Key was missing is
here — including FIDO over the card interface, so ykman and python-fido2 can
drive the same device the browser does — along with ML-DSA-87, a trusted display
that antialiases, and a PIN policy that finally refuses the shapes it always
claimed to. Then the store's own spelling of absent was read as a class rather
than as the site it was reported at: a flash read that failed and a record that
was never written answer the same None, and every gate whose absent arm means
not provisioned yet is in the sweep below. The rest is the verification layer:
the formal model replays against a real device's recorded state, and the gates
that hold it honest found that several earlier instruments had been proving
nothing at all.
RS-Key is not formally verified. Entries below say what evidence a change
added; none of them says a whole-system theorem exists. scripts/claims_gate.py
holds every page naming three or more registered properties to that sentence,
and to the statuses it quotes.
TL;DR
If you read nothing else:
- A flash read that FAILED was spelled "nothing is stored there", and closing
that is the largest thing in this release.Storage::readanswers the same
Nonefor "no such record" and for "that read failed", and an absent record is
how this firmware says not provisioned yet and no gate configured — so the
two collapse where it costs most. One faulted probe re-seeded the factory PIV
PIN, PUK and management key at an unauthenticatedSELECT; another minted a new
FIDO device seed at boot, over every credential derived from the old one;
others handed a host every OATH secret, erased the tamper-evident audit trail
and left it looking freshly initialised, and re-opened a sealed seed-export
window. Swept as a class, not fixed where it was reported:Fs::try_readand
its three siblings answerErrfor a probe the backend could not complete, and
50 guards in 25 functions across four crates take the fallible form. - The CTAP 2.2/2.3 gap against a YubiKey 5.8 is closed.
encIdentifier,
transportsForReset,longTouchForReset,pinComplexityPolicy,
attestationFormatsPreference,encCredStoreStatewith conditional mediation,
and an enterprise-attestation RP-ID list you can actually aim. Two members are
deliberately absent and say why at the skip. - Host tools read firmware
5.8.0now, not5.7.4. One default in
crates/rsk-sdk/build.rsfeeds getInfo, CTAPHIDINIT, the management
DeviceInfo, the PIV, OATH and OTP applets and OpenPGP's vendorVERSION
command, so they move together;FW_VERSIONstill overrides it. The reference
key RS-Key is measured against is a YubiKey 5.8.0 now, andykmanreads the
newerDeviceInfofields through defaults rather than version gates, so it
needs no tag RS-Key does not emit. It is a compatibility constant, not a build
identity — that isbcdDevice, below. - FIDO now answers on the card interface too. CTAP 2.x as ISO 7816 APDUs over
CCID, so tools that never learned CTAPHID reach the same authenticator. gpg'skdf-setupran both OpenPGP references down to blocked
(#104). The00F9DO went
down the generic PUT DATA arm and was stored as opaque bytes, so the two
password hashes it carries for exactly this purpose were never adopted — and
gpgissues noCHANGE REFERENCE DATAof its own, so the card was the only
party that could move them. The only way back was a factory reset. Sixteen
questions run against a real YubiKey 5.7.4, sixteen identical answers.- Two defects broke OATH and Passkeys in Yubico Authenticator for Android
(#111). On a
Yubico-identity key, OATH answered6A82on every connection: Yubico's
Android SDK selects by the full 8-byte instance AID, and since 0.4.10 a SELECT
must name a prefix of a registered AID, while OATH and OTP were registered by
the 7-byte formykmansends. Passkeys never loaded either: the SDK's CBOR
decoder takes no integer above 2³¹−1, so the 64-bit ids 0.4.10 put in
getInfo'svendorPrototypeConfigCommandsfailed the whole response. Both
applets take either form now, as a YubiKey 5.8.0 does. getInfo sends that
member empty, where a YubiKey omits it; the ids still work. Checked against
YubiKit's own AIDs and CBOR decoder, not yet on a phone. - ML-DSA-87 (COSE
-50) joins -65 and -44, byte-exact against the ACVP
vectors. Still not advertised by default: shipping Firefoxes reject a getInfo
carrying an unknown COSE id, and that is measured, not assumed. - The trusted display got a retained, DMA-driven compositor — and it
antialiases, and its PIN pad can scramble. One scene build records the
laid-out frame, per-boot keyed tags keep unchanged 32×32 tiles on the panel, and
a TX-only PIO link at 80 MHz takes a full frame's wire time to 15.36 ms. The
DMA bands live on the active stack, so that flavor has a gate row of its own for
the half the linker cannot see. It also runsclk_sysat 160 MHz, past the
RP2350's rated 150, because the PIO transport takes its wire rate from
clk_sys / 2— the trade and what it does not cover are in
limitations.md. Scrambling the pad is off by default: it
costs muscle memory, and that trade is the owner's. - A relying party could pick a name that halted that display. What a passkey
list costs in drawing commands is a property of the glyphs, not of the byte
count — 48 copies ofjcost 14630 bytes where the mixed-ASCII label the
capacity census used costs 10683 — and two of the 95 printable glyphs
Label::clamppasses were already over the 12 KiB buffer, an unauthenticated
makeCredentialaway from a panic on the screen whose whole job is to be
trustworthy. The buffer is sized to the measured worst glyph now, and the census
sweeps all 95 against every full-frame renderer instead of the one hand-picked
label that let it certify a ceiling it never reached. ssh-keygen -O residentno longer opens a PIN pad on a display board
(#107). Before enrolling,
OpenSSH looks for an existing credential with a silentup:falseprobe,
addinguv: truewhen it was given no PIN and the key advertisesuv. On a
display board with a PIN that ran built-in UV — a modal PIN pad inside a probe
the user never sees — and libfido2 gave up withFIDO_ERR_RX, sossh-keygen
stopped there.uvis dropped on such a probe now rather than refused:
OpenSSH'ssk_enrollgoes on only when the probe answersNO_CREDENTIALS.- Every applet reset re-arms the at-rest scrub now; none did before. FIDO,
PIV, OATH and OpenPGP — five wipe-sweep sites, measured at zero — so a factory
reset could tombstone a verifier still rooted in the public chip serial and
leaveEF_HARDENEDlatched over it, with no later boot ever lapping. The
re-arm is deliberately best-effort, because on a wipe "leave the record in
force" means leave the secrets live: it cannot refuse the reset, so a wipe that
could not re-arm says so out of band and never in its own answer, and the
re-arm survives a wipe that faults on the way. A tombstone is not an erase
either — a dropped record's bytes wait in the ring exactly as a superseded
one's do — so the two commands that revoke a pre-OTP credential by deleting it
re-arm the lap too. - The
rsacrate is out of the trust base, and RUSTSEC-2023-0071 with it. - Three more fixes you may have felt. An OpenPGP password charged its retry
after the comparison, so a decrement lost to a power cut made the guess free;
an RSA-3072/4096 PIV key is usable again under Windows' own minidriver; and a
registration that failed part-way left an RP entry — and the discoverable-
credential slot under it — that nothing ever reclaimed. agehas a route that needs no smart card, and now a guide that says so
(docs/guides/age.md).age-plugin-fido2-hmacgoes over
CTAPHID and asks only forhmac-secret, which every shipped build advertises —
no PKCS#11, no reader name, no PIV slot spent. What it trades away in exchange
is on the page.- A downgrade-fix release says so in a form a flasher can read
(#100). The project signs no
images and assigns no epochs, so all it can do is flag which releases fix a
downgrade-exploitable bug — and that flag was a sentence in a changelog. It is a
machine-readable line in the release body now, spelled in
docs/anti-rollback.mdand held by a gate. Absence is
the answer, not "unknown". bcdDeviceis0x09D6. A firmware-behaviour change bumps it; the counter
counts builds, not features. The row that holds it could not tell an entry
recording a bump from a file that merely moved, and three shipped builds went
through that hole.
Everything else is grouped below in the usual sections, Security and
Internal last — 311 entries, most of them in Fixed and Security, because
the sweep above is written out site by site. The Internal one is where the
instruments that could not fail are written down, each with what it missed.
This release is a downgrade-fix, and carries the marker that says so. Every
image the project has published before it reads a flash probe that failed as a
record that was never written, and the entries below measure what that buys
somebody who can produce one — the factory PIV PIN, PUK and management key
re-seeded at an unauthenticated SELECT among them. So if you run secure boot,
raise your own floor by one after flashing: seal with --rollback <your counter + 1>. The project assigns no epoch and a tool must not burn one on your behalf —
the release carries the flag, the decision stays yours
(anti-rollback).
⚠️ Upgrading a 16 MB key provisioned before 0.4.8 still wipes it
Export your seed first (seed backup). 0.4.8
moved the store 4 KB down on 16 MB parts to clear the RP2350-E10 block, so a
key provisioned by an older 16 MB build comes up factory-empty. Thedisplay
and16mbflavors and theabrobot-16m/waveshare-touch-lcdpresets are
the affected ones; 4 MB and 2 MB keys upgrade in place.
Shortened to fit GitHub's release-body limit.
Every entry is in CHANGELOG.md at this tag.
Verify a download: https://github.com/TheMaxMur/RS-Key/blob/v0.4.11/docs/supply-chain.md