Summary
`fsm_msgCipherKeyValue` derives a 64-byte secret via `hmac_sha512(node->private_key, ...)` -- bytes 0-31 become the AES-256 key, bytes 32-47 the CBC IV when the caller doesn't supply one. After the cipher operation completes, the function returns without ever calling `memzero()` on this buffer. `lib/firmware/u2f.c` already follows this exact pattern (`memzero(data, sizeof(data))`) for an identically-shaped buffer, establishing this as the codebase's own convention -- just not applied here.
Impact (medium)
Every completed CipherKeyValue call (encrypt or decrypt, not cancelled) leaves the derived AES key/IV -- bound to the device's per-address private key -- resident in stack memory. Anyone able to read residual stack contents afterward recovers the actual key used for that key-label's traffic and can decrypt any associated ciphertext without the PIN or device.
Fix direction
Add `memzero(data, sizeof(data))` before the function returns on the completion path.
Summary
`fsm_msgCipherKeyValue` derives a 64-byte secret via `hmac_sha512(node->private_key, ...)` -- bytes 0-31 become the AES-256 key, bytes 32-47 the CBC IV when the caller doesn't supply one. After the cipher operation completes, the function returns without ever calling `memzero()` on this buffer. `lib/firmware/u2f.c` already follows this exact pattern (`memzero(data, sizeof(data))`) for an identically-shaped buffer, establishing this as the codebase's own convention -- just not applied here.
Impact (medium)
Every completed CipherKeyValue call (encrypt or decrypt, not cancelled) leaves the derived AES key/IV -- bound to the device's per-address private key -- resident in stack memory. Anyone able to read residual stack contents afterward recovers the actual key used for that key-label's traffic and can decrypt any associated ciphertext without the PIN or device.
Fix direction
Add `memzero(data, sizeof(data))` before the function returns on the completion path.