Conversation
Kern armors KEF as base64 on SD but base43 in a QR, and the shared detector only knew base64 — so the home Scan page rejected encrypted QRs it had just exported. Teaching kef_envelope_from_bytes the third armor also lets the login and load-mnemonic scanners drop their open-coded base43 probes.
Both KEF pages kept the worker's TaskHandle_t and vTaskDelete()d it on destroy. The worker self-deletes once done, so that handle goes stale for a poll interval; killing it mid-PBKDF2 also skipped the esp_task_wdt_add() that re-subscribes IDLE1, silently disabling the core-1 idle watchdog for the rest of the boot. worker_task brackets the WDT in a trampoline and owns the task's lifetime, matching what firmware_update.c already does.
The 3-byte iterations field encodes up to 100,000,000 — 1000x the app's own setting — and nothing checked it, so a scanned envelope could park the device in PBKDF2 for minutes with the idle watchdog deliberately off, or declare 0 and skip key stretching entirely. Bound it in kef_parse_header, the single read choke point, and in kef_encrypt so the two agree; the floor matches Krux's unwrap, the ceiling is 20x its UI cap. A zero-length payload now reports an auth failure instead of a malloc(0) surfacing as KEF_ERR_ALLOC.
The truncated-SHA256 tail check was written out three times in kef_decrypt's unpad branches. Exposed and GCM versions append no hidden auth, so they can pad `work` directly instead of allocating and copying pre_pad first — the encrypt page only ever writes V20_GCM_E4, so every encryption was paying for that copy.
Versions 0, 1, 5, 10, 15 and 20 are the envelopes Krux's own suite pins down. The other six had no published vectors, so they come from an independent Python reference that reproduces those six byte-for-byte first — every version now decrypts against externally-derived bytes rather than Kern's own output. Also covers round-trips, Krux's NUL-suffix plaintexts for the padding-recovery loop, wrong keys, truncations, malformed headers, the iteration bounds at each edge, and armor detection.
PSA builds a fresh HMAC per iteration and mbedTLS's ESP port takes the crypto mutex, enables the SHA bus clock and pulses the peripheral reset around every hash update that crosses a block — four ~6800-cycle acquire/reset/release cycles wrapped around four ~1150-cycle compressions, for work the P4 does in 1153. Holding the peripheral across a batch of iterations and reloading precomputed ipad/opad midstates leaves two block compressions per iteration and nothing else. U_1 still comes from PSA at cost=1: everything awkward about PBKDF2 lives there, and it is one iteration in a hundred thousand. ESP-IDF's own fastpsk.c does the same for WPA's PBKDF2-HMAC-SHA1. The peripheral is released every CONFIG_KERN_PBKDF2_HW_SHA_CHUNK iterations so AES and SHA stay available to the rest of the device, and every call first runs a known-answer vector through the accelerated code and falls back to PSA if it disagrees — pin_verify() counts a failed unlock before it derives, so a byte-wrong key would wipe every device with a PIN. Off by default until the on-device check confirms it.
The host suite cannot reach any of this — the simulator and main/core/test both build crypto_utils.c with SIMULATOR, which routes PBKDF2 through mbedTLS's pkcs5 API and never compiles pbkdf2.c. Known-answer vectors first, then a differential sweep of the accelerated path against PSA across the boundaries it actually has (the 64-byte HMAC key switch, salts that push salt||counter across block and padding edges, the chunk length, multi-block and truncated output), then timings — withheld if any check failed, since timing a wrong derivation is worthless. Run with: just pbkdf2-check wave_4b
Measured 12.2x on wave_4b across all three shapes: a 100k-iteration KEF decrypt goes 8.07 s to 0.66 s, a PIN check 8.09 s to 0.66 s, and the 10k KEF floor 0.81 s to 0.066 s. Per-iteration cost lands at ~2378 cycles against the ~2306 predicted for two held-hardware compressions. The on-device harness passes all 16 checks, including the differential sweep proving byte-identical output against PSA across every password, salt, iteration and output length boundary.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refactor KEF and add "held-hardware SHA256" acceleration for PBKDF2 key stretching (12x faster).