hmac_sign() previously trusted whatever secret_hex came out of NVS:
- Lengths >128 chars overflowed the fixed 64-byte stack buffer in
hex_to_bytes (out_len was unbounded).
- Non-hex characters were silently decoded to 0 via strtol with no
end-pointer check, producing signatures under a corrupted key.
- Empty secrets fell through to mbedtls_md_hmac_starts with len=0.
flash_device.py now rejects malformed --hmac-secret at provision time,
but hmac_sign should also refuse to sign under a malformed key regardless
of how it ended up in NVS (legacy provisioning, partial flash, etc.).
Add length, hex-charset, and even-length validation; make hex_to_bytes
return bool and have hmac_sign return empty HString on any failure
(callers already treat empty as failure via post_json_once).
Found via adversarial review (run 2026-05-01-202910, both reviewers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- hmac_sign now takes method+path instead of device_id; builds message as
method\npath\ntimestamp\nhex(sha256(body)) per server verify_device_hmac
- reporter: header renamed X-HMAC-Signature → X-Signature; passes "POST"+path
- test vector regenerated against new message format; timestamp-diff test updated
- .size() → .length() throughout (Arduino String has no size())
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>