diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-27 01:01:18 +0100 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-27 01:01:18 +0100 |
| commit | bc4cc19df5f53519ca1aabd3cf161b6d78ffc16c (patch) | |
| tree | 3242a63f603882d723bd0d718d043c30a9f11784 /source/storage/Record.mc | |
| parent | 34daecaa25e5bdd995179a2b4b9d922f8bbb3b54 (diff) | |
fix(pin): the work after a PIN and the change queue run in steps
After a correct or new PIN, promoting pending entries and applying waiting
"Require PIN" changes ran in the event of the last PBKDF2 step; 16 waiting
changes tripped the watchdog on the Forerunner 70. ChangeQueue.LockJob now
does it in steps while the PIN screen shows "Checking...", saves what it did
to the results and refreshes the glance after Set PIN too.
The list title decrypted every waiting change at each start. Queue items
are now [action, box] (aad OTPW-Q-2 + action code), so they are counted
without decrypting; 0.9.0 items are still read. A replacement is checked
when it arrives and its material is sealed apart (qm.<n>). Large buffers
are wiped and UTF-8-checked in pieces (Jobs.Wiper, Bytes.utf8Prefix,
SealedBox.openKeep/sealKeep). "Phone changes" reads behind "Reading...",
and Confirm all / Reject all act only on the changes shown. Storage full
no longer stops the app at the PIN.
Found by the review of b8addc8/096b5a4 and a new make watchdog test; the
diff was reviewed again and its findings fixed. fr70: make lint, make test
(176), make watchdog (19 cases at two steps per event), make screenshots
pass; list_locked and list_reset goldens show "Results ready (menu)".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'source/storage/Record.mc')
| -rw-r--r-- | source/storage/Record.mc | 33 |
1 files changed, 29 insertions, 4 deletions
diff --git a/source/storage/Record.mc b/source/storage/Record.mc index b26a4ba..2cfc046 100644 --- a/source/storage/Record.mc +++ b/source/storage/Record.mc @@ -63,12 +63,24 @@ module Record { // Seal an entry into record bytes. keyRef is KEY_DEVICE or KEY_MASTER and // must match `keys`. The plaintext TLV is wiped before returning. function seal(entry as Entry, keyRef as Number, keys as SealedBox.Keys) as ByteArray { + return sealWith(entry, keyRef, keys, null); + } + + // As seal(), but the plaintext TLV goes to `wiper` when given: wiping a + // large code's contents is a step of its own (the watchdog). + function sealWith(entry as Entry, keyRef as Number, keys as SealedBox.Keys, wiper as Jobs.Wiper?) as ByteArray { var header = encodeHeader(entry); var record = [VERSION, keyRef, (header.size() >> 8) & 0xFF, header.size() & 0xFF]b; record.addAll(header); var plain = encodeSecret(entry); - var box = SealedBox.seal(keys, aad(entry.id, record), plain); - Bytes.wipe(plain); + var leftovers = wiper != null ? [] as Array<ByteArray> : null; + var box = SealedBox.sealKeep(keys, aad(entry.id, record), plain, leftovers); + if (wiper != null) { + wiper.add(plain); + wiper.addAll(leftovers as Array<ByteArray>); + } else { + Bytes.wipe(plain); + } record.addAll(box); return record; } @@ -119,17 +131,30 @@ module Record { // keys, stored under another ID, or changed in any way. The decrypted // TLV is wiped; the caller must call entry.wipeSecrets() when done. function open(record as ByteArray, id as String, keys as SealedBox.Keys) as Entry? { + return openWith(record, id, keys, null); + } + + // As open(), but the decrypted TLV goes to `wiper` when given. + function openWith(record as ByteArray, id as String, keys as SealedBox.Keys, wiper as Jobs.Wiper?) as Entry? { var entry = decodeHeader(record); var end = headerEnd(record); if (entry == null || end == null || !entry.id.equals(id)) { return null; } - var plain = SealedBox.open(keys, aad(id, record.slice(0, end)), record.slice(end, record.size())); + var leftovers = wiper != null ? [] as Array<ByteArray> : null; + var plain = SealedBox.openKeep(keys, aad(id, record.slice(0, end)), record.slice(end, record.size()), leftovers); + if (wiper != null) { + wiper.addAll(leftovers as Array<ByteArray>); + } if (plain == null) { return null; } var map = Codec.decode(plain); - Bytes.wipe(plain); + if (wiper != null) { + wiper.add(plain); + } else { + Bytes.wipe(plain); + } if (map == null) { return null; } |
