[0.3.3] — 2026-07-10
Added
- ML-DSA-65 (FIPS 204, COSE
-49) FIDO credentials. A second post-quantum
signature set alongside ML-DSA-44, negotiable viapubKeyCredParamsand — like
-44 — advertised in getInfo only under theadvertise-pqcbuild; under
PREFER_PQCit outranks -44. It is backed by a new in-tree, stack-optimized
ML-DSA implementation (crates/rsk-mldsa,no_std/no-alloc, nounsafe) that
streams the FIPS 204 matrix A on the fly instead of materializing it, so
keygen+signing fit the RP2350's ~222 KiB main stack (~84 KiB host floor) where
the by-valuefips204crate's -65 (~192 KiB) overflowed it — the reason -65
was previously dropped. ML-DSA-44 signing runs on the same crate too, and the
fips204dependency has been dropped from the tree entirely. The
implementation is checked byte-for-byte against NIST ACVP KATs (both parameter
sets) with Kani proofs over the reductions and rounding. ML-DSA-87 (-50)
remains unsupported (its response overrunsmaxMsgSize). Firmware
bcdDevice→0x07FB.
Security
-
CHANGE REFERENCE DATA no longer half-writes the OpenPGP reset code, and
CTAPHID drops short reads (audit run-14 hardening).INS 0x24with
P2=0x82(the resetting code) verified the current RC and rewrote its verifier
before the command's ownP2check rejected it, desyncing the RC verifier
from theEF_DEK_RCseal it unlocks — a self-inflicted, admin-recoverable
state (the caller already needs the current RC), now closed by rejecting the
unsupportedP2before any write. Separately, the CTAPHID frame loop now
requires a full 64-byte report instead of accepting≥5-byte short reads,
whose stale buffer tail would otherwise be parsed as payload. Neither was
exploitable; both were non-findings the run-14 audit flagged for hardening.
FirmwarebcdDevice→0x07FC. -
Host tools neutralise terminal escapes from a counterfeit device on every
path. The earlier escape hardening reached onlyrsk-tui --once, and even
there stripped only C0/C1 controls. The PythonrskCLI had no sanitizer at
all, so a hostile device's USB product descriptor, getInfoversions, or a
resident credential's rpId /user.namecould inject ANSI/OSC sequences
(screen repaint to forge a "genuine device" banner,OSC 0window-title,
OSC 52clipboard write) into the operator's terminal onrsk inventory/
rsk status/rsk fido list-passkeys. And the TUI'schar::is_control()
filter let Unicode bidi/format overrides (U+202E and the isolates) through,
leaving a Trojan-Source reordering of the printed identity line. Both tools now
route every device-controlled string through a shared sanitizer that maps C0/C1
controls and Cf bidi/format characters to U+FFFD. Terminal-display integrity
only — no device secret, PIN, or presence is involved. (tools/rsk0.3.7,
tools/tui0.2.7) -
Trusted display: the passkey manager keeps the registrable-domain suffix on
every screen. The earlier anti-phishing fix reached only the getAssertion/
add-passkey ceremonies and the Confirm-Delete card; the passkey list row and
the service-detail title still head-truncated an over-long relying-party id,
hiding the real domain behind the ellipsis on the very screens used to review and
delete credentials. They now head-ellipsize (...registrable.domain) when showing
the rpId — a look-alike such asaccounts.google.com.attacker.comcan no longer
read as a legitimate Google passkey. A user-set device-local nickname still keeps
its head. bcdDevice0x07F7→0x07F8. -
rsk/rsk-tuican no longer be hung or crashed by a hostile device. The
earlier host-tooling hardening bounded only the withheld-continuation-frame case;
a malicious device could still (a) streamCTAPHID_KEEPALIVEframes forever to
hangrskand freeze the synchronous TUI, (b) send short continuation frames that
made no progress, (c) return over-nested or non-UTF-8 CBOR to crash the decoder,
(d) answerrsk hw --transport fido'sCONFIG_READwith a non-byte value to
crash it, and (e) embed terminal escape sequences in getInfo/identity text that
rsk-tui --onceprinted raw. The keepalive waits are now deadline-bounded, the
CBOR decoder is depth- and UTF-8-hardened, the PHYCONFIG_READpath validates
the value type (matching the LED path), and--oncestrips control bytes from
device-controlled strings. (tools/rsk0.3.6,tools/tui0.2.6) -
OpenPGP: the resetting code is no longer pre-set to the public default
12345678. Initialisation seeded the reset code (EF_RC) to the well-known
admin default with an active retry counter, so an unauthenticated host could
RESET RETRY COUNTER(P1=0) with"12345678" || new-PW1to reset the user PIN
and then sign/decrypt with the victim's OpenPGP keys. The reset code now ships
deactivated (per OpenPGP Card 3.4 §4.3.4) and is enabled only when an admin
sets a real code viaPUT DATA 0xD3; boot also neutralises any already-
provisioned card still carrying the default reset code. -
OATH:
VALIDATEno longer fails open on an unreadable access code. A stored
access code longer than the read buffer madeseal_readfail and (previously)
unlocked the applet without the code. Reading a present-but-unreadable code now
keeps the applet locked, andSET CODEbounds the code length. -
OATH:
VERIFY CODEnow honours a credential's touch flag. A touch-required
primary HOTP credential could be exercised as a presence-free code-guessing
oracle;VERIFY CODEnow requests the same physical press asCALCULATE. -
U2F: a
credProtect=userVerificationRequiredcredential is refused on the
U2F authenticate path, which performs no user verification — only CTAP2
getAssertion(with a PIN/UV) may exercise such a credential. Level 1/2
credentials are unaffected. -
Secure-PIN entry (trusted display): the on-pad PIN can no longer be diverted
into an attacker-chosen command. The CCIDPC_to_RDR_SecureVERIFY template's
class byte is now forced to0x00instead of copied from the host, so a host
cannot set the ISO 7816-4 command-chaining bit to make the dispatcher buffer the
typed PIN as a chain segment; the secure path also resets any incoming chaining
state before dispatch. -
Seed-moving vendor commands now name themselves on the trusted display.
BACKUP_EXPORT/BACKUP_LOADand attestation import/clear were all approved
behind a generic "Vendor config?" prompt; the master-seed export now reads
"Export secret seed to host?" so a host cannot phish the approval for a full
identity export behind a benign-looking touch. -
OpenPGP GET DATA no longer over-reads the scratch buffer for the fingerprint,
CA-fingerprint and timestamp DOs: a present-but-short slot is zero-padded to its
fixed width, so the DO's declared length matches what was written and no stale
bytes from a prior command leak to an unauthenticated reader. -
The trusted-display sign-in and add-passkey ceremonies now keep the
registrable-domain suffix of an over-long relying-party id visible instead of
truncating it head-first. A relying party id is kept from the tail
(Label::clamp_domain) and head-ellipsized (...registrable.domain), so a
look-alike such asaccounts.google.com.attacker.comcan no longer hide the real
domain behind the ellipsis while showing trusted-looking bait in the prefix. -
The on-device passkey manager applies the same domain-suffix rule. The
earlier fix reached only the host-driven ceremonies; the passkey list, service
detail and the destructive Confirm-Delete card still truncated the relying-party
id head-first. They now keep the registrable-domain suffix
(Label::clamp_domain+ suffix-ellipsis), so a look-alike passkey cannot
impersonate a service on the screen used to review and delete credentials.
Fixed
- A crafted phy record can no longer permanently brick USB. The boot interface
guard now falls back to enabling all interfaces unless a management-capable one
(CCID or HID) survives — a keyboard-only mask previously slipped past it and
stranded the device with no software path to rewrite the record. - The boot path no longer panics on a host-written LED pin. A
led_gpiofrom
the phy record that collides with a GPIO presence pin is now ignored (the build
default is used) instead of panicking every boot; a build whose own LED/presence
pins collide is caught at compile time. rskno longer hangs against a hostile device that announces an inflated
CTAPHID response length and then withholds the continuation frames.rsk led --transport fidono longer crashes on a device that answers the
ungated LEDCONFIG_READwith a non-byte-string CBOR value.
Verify a download: https://github.com/TheMaxMur/RS-Key/blob/v0.3.3/docs/supply-chain.md