RS-Key v0.3.0
[0.3.0] — 2026-07-03
Added
-
Trusted-display build (experimental, opt-in). A screen-and-touch RS-Key
variant for the Waveshare RP2350-Touch-LCD-2.8, behind thedisplaycargo
feature (firmware-displaynix flavor). The screen turns the key into a
trusted display — the operations that matter happen on the device's own glass,
not on the host:- Approve / Deny paints the real relying party for every touch-gated
operation, so a signature can't be produced without a physical tap on a screen
showing the truerpId(refuse →OPERATION_DENIED); a registration shows a
Save new passkey? card. A look-alike id too long for the box is clipped with
a truncation marker so its prefix can't masquerade. - On-screen PIN entry — built-in user verification (getInfo
options.uv; a
pinUvAuthTokenminted from the on-screen pad against the sameEF_PIN), and
a CCID pinpad (bPINSupport/PC_to_RDR_Secure) so GnuPG and OpenSC
collect the OpenPGP / PIV PIN on the panel — the PIN never crosses USB. Every
PIN screen names which credential it collects, an eye toggle reveals the
digits, and "N tries remaining" is shown up front. - A dedicated device PIN (separate from the FIDO clientPIN) gating the
on-device UI, with lock / unlock, display sleep (image-retention
guard + wake button), and set / change PIN on the panel. - Passkeys — browse resident credentials, rename (a device-local
nickname that never re-seals the box) and delete on-device. - Apps — a read-only browser of OpenPGP / PIV / OATH state (no PIN, no
secret, no OATH code — the device has no clock), plus on-device PIV key
generation (EC P-256/P-384, Ed25519, X25519, RSA 2048/3072/4096) into empty
retired slots. - Settings — device & FIDO PINs; a PIV PIN / PUK / unblock / protect
management key (ykman--protect) sub-menu; on-screen BIP-39 / SLIP-39
recovery export (derived on-device, never over USB) and backup-window status;
an audit log; factory reset; a Firmware screen that reboots to
BOOTSEL for an over-USB update; and live brightness / display-sleep /
touch-timeout that persist across reboots. - A standard screenless key compiles none of it — the whole UI stack
(rsk-ui,embedded-graphics,u8g2-fonts) isdep:-gated and the build
asserts it absent from the default image, so an ordinary build is
byte-for-byte unaffected. The UI model, geometry and glyphs live in the
host-tested + Kani-provedrsk-uicrate. See
docs/guides/display.md. Built up across bcdDevice
0x0784–0x07D5.
- Approve / Deny paints the real relying party for every touch-gated
-
PIV: RSA-3072 and RSA-4096 keys. Generate, import, sign / decrypt,
attestation and metadata gained RSA-3072/4096 (the applet buffers were lifted
off their RSA-2048 ceiling); on a display build the on-device Generate key
chooser offers RSA via a 2048 / 3072 / 4096 sub-picker. RSA-1024 stays disabled.
bcdDevice0x07C4→0x07C6. -
PIV: Ed25519 and X25519 keys (algorithm ids
0xE0/0xE1, Yubico 5.7
PIV). Generate (Ed25519 with an RFC 8410 self-signed cert; X25519 is
key-agreement-only), import (raw seed / scalar, yubikit tags0x07/0x08),
sign / key-agree, metadata and attestation — interoperating withykman/
yubico-piv-tool(an imported X25519 scalar is byte-flipped to the little-endian
form standard tooling sends, so the slot's public key matches). bcdDevice
0x07C3→0x07C4. -
Configurable multi-LED effects engine. Boards with a chain of addressable
WS2812 LEDs light the whole strip with per-status animated effects (vapor,
bounce,flow,sparkle,legacy) viarsk led --effect/--speed; the
connected count is a runtime phy setting (rsk hw --led-num, TLV tag0x0E)
bounded by theMAX_LEDSbuild ceiling (a value over it saturates, never
panics).EF_LED_CONFgrows to 17 bytes; older blocks still load. Thanks to
@Curious-r. bcdDevice0x0780→0x0783. -
Configurable GPIO presence button (
PRESENCE_PIN). The user-presence input
can move from BOOTSEL to a dedicated GPIO at compile time (PRESENCE_PIN=<0..=29>,
active-low with a pull-up by default, orPRESENCE_ACTIVE_HIGH=1for a touch
sensor / button-to-VCC); the pin is guarded against colliding with the LED and is
rejected on adisplaybuild. One new documentedunsafe. Thanks to @lpiob
(#17). bcdDevice0x0791→0x0793. -
rsk-tuican export the seed as SLIP-39 shares (tools/tui 0.2.4). The Backup
section gains "Export seed (SLIP-39)" beside the BIP-39 export, revealing the seed
as a 2-of-3 Shamir share set (via the in-treersk-slip39crate) that recombines
withrsk backup restore --scheme slip39.
Changed
-
Touch timeout is configurable; phy tag
0x08now follows pico-fido. Tag
0x08(previously an unused presence-button GPIO) now meansPresenceTimeout—
the touch-wait in seconds — matching pico-fido / PicoForge, so a PicoForge config
orrsk hw --touch-timeout <secs>sets it (absent /0keeps the 30 s default).
bcdDevice0x0783→0x0784. -
rsk-tuigets a curated colour theme (tools/tui 0.2.3). On truecolor / 256-
colour terminals the cockpit uses a fixed brand palette with rounded borders and
an explicit selection bar; a 16-colour terminal keeps the adaptive named-ANSI
colours. Override withRSK_TUI_TRUECOLOR=1|0. No--once/--jsonchange. -
rsk-tuistatus labels are single-sourced (tools/tui 0.2.2). The--once
printer and the cockpit now share the model's label mappings, which changes three
--oncelabels (seed lock "… disabled until unlock", secure boot "ENABLED (not
locked)", un-probed applets "not probed").
Fixed
- Maximal credential requests now fit the credential box. A registration
within every advertised limit (a 253-byterpId, a 64-byte user.id, 64-byte
name / displayName and a 127-byte credBlob) could overflow the sealed credential
box or its resident bookkeeping and be rejected (CTAP2_ERR_OTHER/
KEY_STORE_FULL/REQUEST_TOO_LARGE), and a large credential that did register
could then never assert. The three ceilings are now derived from the field
maxima so they can't drift below what the device advertises:CRED_BOX_MAX(748)
sizes create / assert / reseal,RP_REC_MAX(314) the residentEF_RPrecord,
andMAX_RAW_SUBPARA(384) a maximalupdateUserInformation; getInfo's
maxCredentialIdLengthand the published metadata report the real 748, and
over-maximum inputs are rejected explicitly withINVALID_LENGTH. Older records
load unchanged. bcdDevice0x07E7→0x07EC.
Security
-
Additional defense-in-depth hardening (four items, none independently
exploitable; bcdDevice0x07DD→0x07DE):- credProtect is now range-checked. makeCredential rejected nothing for a
credProtect value outside{1,2,3}and stored it verbatim;getAssertion
enforces protection by exact match, so an out-of-range value silently meant
no protection. It now returnsCTAP2_ERR_INVALID_OPTION(§12.1). - hmac-secret-mc empty-salt parity. makeCredential now rejects an
hmac-secret-mc request with an empty salt up front (MissingParameter),
matching the existinggetAssertionhmac-secret guard (previously this was
only caught later by the length check inhmacsecret::eval). - credentialManagement enumeration counters widened to
u16. Theskip/
total/ begin-next counters wereu8and saturated at 255, so on a fully
provisioned store (MAX_RESIDENT_CREDENTIALS = 256) the 256th RP/credential
was invisible to (and undeletable via) enumeration. The wire encoding is
unchanged for ≤255 (canonical CBOR). - RSA-keygen fast path resets the incoming command chain. The CCID keygen
fast path already dropped a stale GET RESPONSE tail (clear_pending); it now
also resets a half-accumulated CLA-0x10command chain (clear_chaining,
scrubbing it) so an interrupted chain cannot prepend onto a later command.
- credProtect is now range-checked. makeCredential rejected nothing for a
-
Missing-authorization fixes in the Yubico-management and rescue applets
(two defects in never-before-audited utility applets; bcdDevice0x07DC→
0x07DD):- Rescue OTP-fuse writes now require an on-device user-presence confirmation.
The two irreversible fuse burns — page-58 access lock (INS 0x1BP1=0x58,
"LOCK58") andROLLBACK_REQUIRED(P1=0x48,"ROLLBK") — were the only
privileged rescue commands without therequire_presencegate every sibling
op (attestation sign, cert/phy write, reboot-to-BOOTSEL) enforces. Their magic
payload is a source-visible constant, not authentication, so an unauthenticated
USB host could permanently burn a fuse with no operator consent. Both now
prompt (6985if declined); idempotent no-ops still returnOKwithout a
prompt. (crates/rsk-rescue/src/lib.rs.) - Management WRITE CONFIG (
INS 0x1C) now requires user presence. It was
entirely unauthenticated and theCONFIG_LOCKbyte it stores was never
enforced, so a USB host could persistently spoof the reported DeviceInfo. The
write now prompts for on-device confirmation (6985if declined), matching
every sibling applet's write path. (crates/rsk-mgmt/src/lib.rs.)
- Rescue OTP-fuse writes now require an on-device user-presence confirmation.
-
PIV and CCID defense-in-depth hardening (no exploitable vulnerability
found; three items; bcdDevice0x07DB→0x07DC):- PIV
GENERAL AUTHENTICATEchallenge is now bound to its issuing
algorithm. A 9B mutual/single-auth challenge issued under one algorithm
(3DESchal_len8 vs AESchal_len16) could structurally be answered under
the other; AES-192 and 3DES share a 24-byte key, so the key-length gate alone
did not separate them. This was not exploitable (the witness always
requires knowledge of the management key, and every replay failed closed with
has_mgmstaying false), but theSessionnow recordschal_algoat issue
and refuses a step-2 whose algorithm differs. - PIV GET DATA / MOVE KEY clamp the stored object length.
get_dataand
move_keysliced aMAX_OBJECT(1900-byte) buffer by the full length
Storage::readreturns, which would panic on a stored value longer than the
buffer. Every host writer already caps atMAX_OBJECT(so this was reachable
only by a raw flash write — a stronger attacker than the USB host), but the
readers now clamp withn.min(MAX_OBJECT), returning the prefix instead of
panicking. Matches the existingEF_PIVMAN_DATAclamp pattern. - CCID RSA-keygen fast path clears the GET RESPONSE remainder. The dual-core
try_rsa_keygen/try_piv_rsa_keygenfast paths bypass
Dispatcher::process, which is what normally drops a stale chained-response
tail, so a host interleavingchained-response → GENERATE → GET RESPONSEwas
re-served its own prior tail. This crossed no trust boundary (same principal;
a SELECT to another applet clears the buffer first), but the fast paths now
callDispatcher::clear_pending()to match ordinary dispatch.
- PIV
-
PIV and OATH authentication fixes (bcdDevice
0x07DA→0x07DB):- PIV management-key authentication bypass via an encryption oracle
(critical).GENERAL AUTHENTICATEhad a symmetric-algorithm tag-0x81
("internal authenticate") branch for slot9Bthat returned
E(mgm_key, caller_bytes)with nohas_mgm, no PIN (9Bis not a key slot,
so the PIN gate was skipped) and no touch (default9Bpolicy is
TOUCHPOLICY_NEVER). Because the management-key cipher is deterministic ECB,
an unauthenticated USB host could chain it with the applet's own single-auth
challenge — request a plaintext challengeR, ask the oracle forE(mgm,R),
submit that as the response — and the card'sD(mgm,·)==Rcheck would pass,
settinghas_mgmwith zero knowledge of the management key. That grants
full, persistent PIV takeover (generate/import/overwrite slot keys,PUT DATA,
rotate the management key, reset PIN/PUK counters). It is a distinct-mechanism
sibling of the earlier mgmt-key bypass, whoseChallengeKindbinding did not
cover it. Fix: the symmetric tag-0x81branch (which has no legitimate PIV
client) is removed, so the only sanctioned9Bflows are mutual-witness
(tag0x80) and single-auth (tag0x81-empty challenge → tag0x82verify).
A class-invariant test asserts noGENERAL AUTHENTICATEpath reachable without
prior auth can sethas_mgm. - OATH
CHANGE PINunlimited OTP-PIN guessing at the retry floor (medium).
cmd_change_otp_pindecremented the OTP-PIN retry counter with a saturating
subtraction but, unlikecmd_verify_otp_pin, did not refuse at the floor —
once the counter reached 0 it stayed 0 and the PIN comparison kept running on
every request, an unlimited online brute-force of the store-unlocking OTP-PIN
(a residual sibling of the earlierCHANGE PINfinding). Fix: bothVERIFY
andCHANGEnow go through a singlespend_and_match_otp_pinchokepoint that
refuses atrec[0]==0; legitimate recovery after lock-out isRESET(which
wipes the store), not more guesses. Behavior change: a correct old-PIN no
longer recovers a locked-out OTP-PIN viaCHANGE; useRESET.
- PIV management-key authentication bypass via an encryption oracle
-
OTP, OpenPGP, U2F and audit-journal hardening (bcdDevice
0x07D9→
0x07DA):- OTP
SLOT_SWAPaccess-code bypass (high).cmd_swapwas the only
slot-mutating OTP command that did not check the per-slot access code that
cmd_configure/cmd_updateenforce: it unsealed both target slots (the seal
read never compares the access code) and relocated/deleted them unconditionally.
An unauthenticated USB host (CCID or the HID keyboard frame, no PIN/code/touch)
couldSLOT_SWAPa programmed, access-code-protected slot to silently delete
or relocate it — persistently breaking a challenge-response credential used
for LUKS / KeePassXC / pam_yubico. An unbounded swap offset could also orphan
the slot at an FID outside the addressable 1..=4 range.cmd_swapnow requires
the access code of every non-empty slot it touches (an unprotected slot's code
is all-zero, so a plainykman otp swapof unprotected slots is unchanged) and
rejects out-of-range offsets; the same offset bound is applied to
cmd_configure/cmd_update/cmd_calculate. Integrity/availability only — the
config stays GCM-sealed (no secret exfiltration). - OpenPGP
read_publicunclamped stored length (hardening).read_public
returned the value's fullFs::readlength withoutn.min(out.len())— the
6th member of the OpenPGP stored-length family. Latent only (EF_PB_*is not
host-writable beyond its bound), now clamped like every other reader. - U2F attestation-chain read (hardening). The org-attestation branch sliced
cert[..n]on the full stored length with only a size margin; now clamps
n.min(cert.len()), matching the siblingEF_EE_DEVbranch. - Audit-journal meta window (hardening).
load_metanow fails closed to
genesis when a persistedEF_AUDIT_METAclaims a window wider than
AUDIT_RING_SLOTS, so a flash-corrupted meta can't overrun the export buffer. BACKUP_EXPORTdocstring corrected to match behavior (only
BACKUP_FINALIZEseals the window; repeat export before finalize is safe).
- OTP
-
FIDO, OpenPGP and OATH fixes (bcdDevice
0x07D8→0x07D9):- FIDO
getNextAssertionuser-presence bypass (high).getAssertionarmed
the multi-credentialgetNextAssertionqueue during resident discovery
before its user-presence gate, and no path tore the queue down when that gate
failed;getNextAssertionperforms no presence check of its own. So on a
device holding ≥2 discoverable credentials for one RP, after the user
declined or ignored the touch, a host could still pull validUP=1
assertions for credentials #2..N with no touch — defeating the test of user
presence.get_assertionnow callsgna.reset()on any error return (CTAP 2.1
§6.3: getNextAssertion only continues a successful getAssertion). - OpenPGP
GENERATEOOB panic on a short algorithm attribute (medium). A
PW3-written 1–2 byteC1/C2/C3DO (PUT DATAcaps no minimum length) made
GENERATE ASYMMETRIC KEY PAIRindex the RSA modulus-size bytes past the slice
→ panic/reset on everyGENERATEfor that slot. The earlier clamp only bounded
the over-long case; bothgenerateandrsa_generate_paramsnow reject an
attribute shorter than 3 bytes, matching the guarded siblinginfo::slot_algo. - OATH OTP-PIN counter glitch-hardening (defense-in-depth).
VERIFY PIN/
CHANGE PINnow persist and read back the retry-counter decrement before the
PIN compare (mirroring the FIDO clientPIN gate), so a fault-injected or failed
flash program can't widen the 3-try OTP-PIN limiter. - FIDO
verify_pin_hashself-guards the retry decrement (defense-in-depth).
Added an in-functionretry == 0check beforepin_data[0] -= 1(matching
verify_pin_at), so no future caller can underflow the PIN retry budget in a
release build without overflow-checks.
- FIDO
-
OpenPGP, OATH and FIDO fixes;
rskreceipt binding (bcdDevice0x07D8;
rsk0.3.1;rsk-tui0.2.1):- OpenPGP
GET DATAunclamped length → OOB brick (high, ×2 sites). Both the
generic top-level Flash DO (login/url/private DOs) and theC1/C2/C3
algorithm-attribute path returned the value's full stored length, so an
over-long PW3-written object panicked the device on every read (persistent
DoS reached bygpg --card-status).get_datanow clampsdata_lento the
scratch buffer at the single chokepoint, plus a defensive clamp at the extend. - OATH access-code / OTP-PIN bypasses (high, ×2).
SET PINnow requires a
validated session (an unauthenticated host could mint the unlock secret on a
locked applet);CHANGE PINnow spends a retry on a wrong old-PIN (it was an
unlimited brute-force oracle that recovered the OTP-PIN and unlocked the store). - FIDO
setMinPINLengthtruncation (medium). AnewMinPINLengthabove the
max PIN length is now rejected before theas u8store, which otherwise
truncated (e.g. 256 → 0) and silently defeated the monotonic enterprise floor. rsk offboardreceipt binding (medium). The signed wipe receipt is now
bound to the journal window it presents (recompute + compare the head, hard-fail
a missing RESET), matchingrsk audit; the verify ceremonies also validate
device-supplied checkpoint fields instead of raising a traceback.- Defense-in-depth (low). Clamped five remaining
Fs::readreaders
(phy/largeblobs/vendorunlock/makeCredentialatt-chain/OpenPGP DEK) to
their buffers; fixed the OpenPGPGET DATA 0x7Astale-scratch over-read;
rejected the 2-byte TLV tag form in OATHPUT; hardened thersk-tuiaudit
view andrsk ledagainst malformed device responses.
- OpenPGP
-
Full-tree audit fixes. Found and fixed:
- PIV management-key authentication bypass (critical).
GENERAL AUTHENTICATEshared one session challenge field between the single-auth
(plaintext challenge) and mutual-auth (encrypted witness) handshakes, so a
host could read the plaintext single-auth challenge and replay it as the
mutual-auth witness to authenticate as the card administrator with no
knowledge of the management key — no PIN, no touch. The challenge is now
tagged with the flow that issued it and can only be consumed by that same
flow. - OpenPGP
GET DATAover-long-DO brick (two more sites). The cardholder
certificate (7F21) read-out and the genericDoWriterflash-DO builder
sliced/advanced a fixed 1024-byte buffer by the value's full stored length;
a PW3 host canPUT DATAan over-long cardholder cert/name, so a laterGET DATA 65/6E/7F21(issued bygpg --card-status) panicked — a persistent
brick. Both are now clamped to the buffer, matching the earlierinfo.rsfix. - OATH
VERIFY CODE(INS0xB1) now honours the access code. It lacked the
validatedgate every other stored-data command has, so a locked applet
answered it — a replayable oracle on the primary credential's current OTP
across the access-code boundary. Now gated. - Trusted-display delete-confirmation clips the identity. The
delete-passkey confirmation drew the untrusted rpId/account unclipped with no
truncation marker, unlike the approve/add ceremonies; a padded look-alike
rpId could overflow the card silently. Now ellipsized + marked to the card. - OpenPGP private keys are AES-256-GCM-sealed with a fresh nonce. The DEK
seal used one fixed (key, IV) AES-CFB across every key slot, so the block-0
keystream repeated and a flash-dump attacker could recover the XOR of two
same-format scalars' first bytes; CFB was also unauthenticated. Sealing now
uses AES-256-GCM under a synthetic per-record nonce (HMAC(dek, fid ‖ key)),
adding authentication and eliminating the reuse. Keys in the old CFB format
still load (trial-decrypt fallback) and are re-sealed to the new format the
first time they are used — no reprovisioning needed. - The release pipeline no longer ships the
no-touchfirmware. The release
workflow built and published fourno-touchflavors (user-presence bypass,
marked "never ship") as signed, SLSA-provenanced public assets. It now builds
and publishes only the four touch-required flavors, with a guard that fails
the release if anyno-touchasset is present.
- PIV management-key authentication bypass (critical).
-
FIDO master seed sealed with authenticated ChaCha20-Poly1305. The device
master seed and the org attestation scalar (EF_KEY_DEV/EF_ATT_KEY) were
sealed with AES-256-CBC under one fixed serial-hash IV shared across both
slots, and carried no MAC — the same fixed-IV / no-authentication class as the
OpenPGP DEK above, but at the root of the FIDO identity. They are now
ChaCha20-Poly1305-sealed (new tags0x02pre-OTP /0x12OTP-arm) under a
synthetic per-record nonce (HMAC(HMAC(nonce_key, fid), value)), so the seed
and the attestation key never share a nonce and a flash fault or tamper is
detected rather than silently decrypting to a corrupted seed. Records in the
legacy CBC (0x01/0x11) and PIN-wrapped (0x03/0x13) formats still load
and are re-sealed forward at boot / the first PIN verify — no reprovisioning,
and every passkey survives the upgrade. -
Pre-release cross-review hardening. An adversarial re-review of the two
unreleased hardening commits (the trusted-display arc and the pico-keys
carry-over below) — the ones that had not yet been cross-reviewed before a
release tag — found and fixed:- OpenPGP over-long-DO brick, two remaining sites.
GENERATE,
rsa_generate_paramsand keyIMPORTread the algorithm-attribute DO into a
fixed 16-byte buffer and sliced it by the value's full stored length; a
PW3 host canPUT DATAan over-16-byteC1/C2/C3, so the slice panicked
(device brick). Clamped to the buffer, matching the earlierinfo.rsfix. - OTP slots and OATH credentials now survive a later OTP-MKEK burn. Both
seal under the device root key, which changes when the fuse MKEK is burned;
neither had the pre-OTP recovery arm the FIDO seed / PIV / attestation key
already use, so a secret provisioned before a burn became unreadable (OTP)
or was double-encrypted and destroyed (OATH) on the first post-burn boot. The
boot migrations now trial-decrypt under the pre-OTP arm and re-seal under the
OTP arm. - OATH OTP-PIN survives an OTP-MKEK burn. The new OTP-rooted verifier gained
the samewithout_otp()match-and-re-store fallback the PIV / OpenPGP / FIDO
PINs use, so a PIN set before a burn still verifies afterwards — restoring the
burn-immunity the legacy serial-only hash happened to have. - The reboot-to-BOOTSEL user-presence gate can no longer be bypassed. The
vendor applet exposes the same reboot verb as the (gated) rescue applet, over
both the CCID and CTAPHID transports; its1F/01(BOOTSEL) is now gated
identically. A warm restart (1F/00) stays ungated. - Trusted-display: the Add-passkey (enrollment) screen marks a truncated
relying-party id. The makeCredential screen dropped the truncation marker
for a clamped look-alike id whose prefix fit the box — the phishing vector the
Approve screen already closed. It now forces the marker like the Approve path. rsk-wiperejects a degenerateFLASH_SIZE.FLASH_SIZE=0passed the
remaining build asserts and made the erase a silent no-op that still signalled
success; a lower bound now rejects it.
bcdDevice 0x07D3 → 0x07D4. Host CLI (
tools/rsk) 0.2.0 → 0.3.0:rsk hwand
rsk reboot bootselnow prompt for the on-device approval the firmware requires
and explain a6985decline instead of failing cryptically. - OpenPGP over-long-DO brick, two remaining sites.
-
Carry-over hardening from a pico-keys upstream audit. A review of the upstream
pico-keys C firmware surfaced design flaws; each was re-verified against the RS-Key
Rust source. The overwhelming majority were already handled by the port (OATH gate,
PIV key sealing + admin-auth gates, parser totality, HMAC-DRBG, constant-time
compares), and this wave closes the remaining gaps:- Yubico OTP slot secrets are now sealed at rest. The 52-byte slot config —
which carries the AES-128 key, private UID and the HMAC-SHA1 / OATH-HOTP secret —
was the one applet still written to flash in the clear. It now goes through the same
KeyFidAES-256-GCM chokepoint as FIDO / PIV / OpenPGP / OATH; a boot pass re-seals
any pre-existing plaintext slot, so a flash-dump thief no longer recovers the token
secrets. - The OATH OTP-PIN verifier is OTP-rooted, not a fast serial-only hash. The
Nitrokey-style OTP PIN now storespin_derive_verifier(rooted in the OTP MKEK,
exactly like the OpenPGP / PIV PINs) instead of the legacydouble_hash_pin; a
legacy record still verifies and is upgraded on the next successful use. - The device attestation key is AEAD-sealed.
EF_DEVCERT_KEYmoved from raw
AES-256-CBC under a public fixed IV with no MAC to AES-256-GCM (random nonce, auth
tag); a bit-flip in the sealed scalar is now detected rather than silently accepted,
and legacy CBC records are re-sealed at boot. - Privileged rescue commands require user presence. Attestation signing over a
host-chosen digest, attestation-cert overwrite, phy/identity write and
reboot-to-BOOTSEL now need an on-device confirmation (a touch, or an on-screen
Approve on the trusted-display build), so a hostile USB host can no longer drive
them silently. Read-only status and a plain restart stay ungated. - OpenPGP MSE touch policy follows the repointed slot. The UIF (touch) check for
PSO:DECIPHER / INTERNAL AUTHENTICATE now follows an MSE key-reference repoint, so a
cross-wired DEC↔AUT key can no longer be used under the wrong slot's touch policy. - FIDO credMgmt
updateUserInformationrequires an exact userId match (CTAP 2.1
§6.8.3), closing a min-length-prefix compare where a prefix (or empty id) matched. rsk-wipeerases the whole target flash. It reads the sameFLASH_SIZEbuild
knob as the firmware instead of assuming 4 MB, so a 16 MiB board is fully wiped.
bcdDevice 0x07D2 → 0x07D3. (
rsk-wipeis a separate binary and carries no bcdDevice.) - Yubico OTP slot secrets are now sealed at rest. The 52-byte slot config —
Verify a download: https://github.com/TheMaxMur/RS-Key/blob/v0.3.0/docs/supply-chain.md