crypto: harden all crypto-sensitive memcmp + memset sites

Audit-driven sweep found additional compiler-optimization-sensitive
patterns beyond the login password compare just fixed:

P4.F3 (HIGH) — Utils::MACThenDecrypt verified packet MACs with
plain memcmp. Runs on EVERY encrypted-then-MAC'd packet in the
mesh; a timing oracle here lets attackers forge MACs byte-by-byte
across the whole mesh layer. Replaced with constantTimeEqual.

P4.F4 (MEDIUM) — Multiple memset(secret, 0, ...) calls on
stack-resident crypto buffers (Ed25519 seed, ADC noise pool, AES
key derived in extract_via_aes_ctr, HWINFO unique ID) were
subject to dead-store elimination under -Os. GCC/Clang routinely
elide these when the buffer is never read after; the wipe vanishes
and the secret persists on stack until next call overwrites.
Replaced with secureZeroize using volatile pointer writes.

P4.F5 (LOW) — Identity::validatePrivateKey boot self-test compared
shared secrets with plain memcmp. Boot-only, no attacker
observation channel, but hygiene matters and the fix is one line.
Also added secret-wipe for ss1/ss2 on all return paths.

Promoted the local ct_memeq() previously added to RepeaterMesh.cpp
into Utils::constantTimeEqual + Utils::secureZeroize (Utils.h/cpp)
so the login compare and MAC compare share the same audited helper.

Both helpers verified by Thumb-2 disassembly on rak3401_1watt:
- constantTimeEqual: loop branches on iterator, accumulator
  load-modify-stored to stack every iteration, final return uses
  CLZ+LSR (no conditional branch on result).
- secureZeroize: STRB.W to memory in a counted loop, not replaced
  with memset builtin and not eliminated.
This commit is contained in:
liquidraver
2026-05-28 13:26:20 +02:00
parent 88dccf2e24
commit b692ca72ed
7 changed files with 85 additions and 44 deletions
+15
View File
@@ -19,6 +19,21 @@ public:
static int decrypt(const uint8_t *shared_secret, uint8_t *dest, const uint8_t *src, int src_len);
static int encryptThenMAC(const uint8_t *shared_secret, uint8_t *dest, const uint8_t *src, int src_len);
static int MACThenDecrypt(const uint8_t *shared_secret, uint8_t *dest, const uint8_t *src, int src_len);
/* Constant-time byte-equality. Returns true iff every byte of `a`
* matches `b`. No early exit — timing is independent of input,
* defeating timing-leak attacks on MAC/password/secret compares.
* `volatile` accumulator survives `-Os` LTO — disassembly-verified
* on Cortex-M4 (rak3401_1watt). Pattern from rweather/arduinolibs. */
static bool constantTimeEqual(const void *a, const void *b, size_t n);
/* Securely zero a buffer such that the compiler cannot elide the
* writes as dead-store optimization. Uses volatile pointer writes —
* standard idiom for clearing crypto secrets before stack unwind.
* Use this for any buffer holding key material, seeds, or shared
* secrets after their last use. */
static void secureZeroize(void *buf, size_t n);
static void toHex(char *dest, const uint8_t *src, size_t len);
static bool fromHex(uint8_t *dest, int dest_size, const char *src_hex);
static bool isHexChar(char c);