github TheMaxMur/RS-Key v0.4.11
RS-Key v0.4.11

27 days ago

[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::read answers the same
    None for "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 unauthenticated SELECT; 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_read and
    its three siblings answer Err for 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, encCredStoreState with 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.0 now, not 5.7.4. One default in
    crates/rsk-sdk/build.rs feeds getInfo, CTAPHID INIT, the management
    DeviceInfo, the PIV, OATH and OTP applets and OpenPGP's vendor VERSION
    command, so they move together; FW_VERSION still overrides it. The reference
    key RS-Key is measured against is a YubiKey 5.8.0 now, and ykman reads the
    newer DeviceInfo fields 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 is bcdDevice, 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's kdf-setup ran both OpenPGP references down to blocked
    (#104).
    The 00F9 DO 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
    gpg issues no CHANGE REFERENCE DATA of 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 answered 6A82 on 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 form ykman sends. 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's vendorPrototypeConfigCommands failed 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 runs clk_sys at 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 of j cost 14630 bytes where the mixed-ASCII label the
    capacity census used costs 10683 — and two of the 95 printable glyphs
    Label::clamp passes were already over the 12 KiB buffer, an unauthenticated
    makeCredential away 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 resident no longer opens a PIN pad on a display board
    (#107).
    Before enrolling,
    OpenSSH looks for an existing credential with a silent up:false probe,
    adding uv: true when it was given no PIN and the key advertises uv. 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 with FIDO_ERR_RX, so ssh-keygen
    stopped there. uv is dropped on such a probe now rather than refused:
    OpenSSH's sk_enroll goes on only when the probe answers NO_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
    leave EF_HARDENED latched 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 rsa crate 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.
  • age has a route that needs no smart card, and now a guide that says so
    (docs/guides/age.md). age-plugin-fido2-hmac goes over
    CTAPHID and asks only for hmac-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.md and held by a gate. Absence is
    the answer, not "unknown".
  • bcdDevice is 0x09D6. 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. The display
and 16mb flavors and the abrobot-16m / waveshare-touch-lcd presets 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

Don't miss a new RS-Key release

NewReleases is sending notifications on new releases.