// firmware/src/hmac.cpp #include "hmac.h" #include "mbedtls/md.h" #include #include static HString bytes_to_hex(const uint8_t* bytes, size_t len) { HString out; char buf[3]; for (size_t i = 0; i < len; i++) { snprintf(buf, sizeof(buf), "%02x", bytes[i]); out += buf; } return out; } static bool is_hex_char(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } static bool hex_to_bytes(const HString& hex, uint8_t* out, size_t out_len) { if (hex.length() != out_len * 2) return false; for (size_t i = 0; i < out_len; i++) { char a = hex[i*2], b = hex[i*2+1]; if (!is_hex_char(a) || !is_hex_char(b)) return false; char byte_str[3] = {a, b, 0}; out[i] = (uint8_t)strtol(byte_str, nullptr, 16); } return true; } static bool sha256(const uint8_t* data, size_t len, uint8_t out[32]) { mbedtls_md_context_t ctx; const mbedtls_md_info_t* info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); mbedtls_md_init(&ctx); int ret = mbedtls_md_setup(&ctx, info, 0); if (ret != 0) { mbedtls_md_free(&ctx); return false; } mbedtls_md_starts(&ctx); mbedtls_md_update(&ctx, data, len); mbedtls_md_finish(&ctx, out); mbedtls_md_free(&ctx); return true; } HString hmac_sign(const HString& secret_hex, const HString& method, const HString& path, uint32_t timestamp, const HString& body) { // 1. SHA256(body) uint8_t body_hash[32] = {}; if (!sha256((const uint8_t*)body.c_str(), body.length(), body_hash)) { return HString{}; } HString body_hash_hex = bytes_to_hex(body_hash, 32); // 2. Build message: method + "\n" + path + "\n" + timestamp + "\n" + sha256(body) char ts_buf[12]; snprintf(ts_buf, sizeof(ts_buf), "%u", (unsigned)timestamp); HString message = method + "\n" + path + "\n" + ts_buf + "\n" + body_hash_hex; // 3. Decode secret from hex. Reject empty / odd-length / oversized / // non-hex inputs — flash_device.py validates at provision time, but // hmac_sign refuses to sign under a malformed key regardless of how it // ended up in NVS (legacy provisioning, NVS corruption, etc.). if (secret_hex.length() == 0 || secret_hex.length() > 128 || secret_hex.length() % 2 != 0) { return HString{}; } size_t secret_len = secret_hex.length() / 2; uint8_t secret[64] = {}; if (!hex_to_bytes(secret_hex, secret, secret_len)) { return HString{}; } // 4. HMAC-SHA256(secret, message) uint8_t hmac_result[32]; mbedtls_md_context_t ctx; const mbedtls_md_info_t* info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); mbedtls_md_init(&ctx); int ret = mbedtls_md_setup(&ctx, info, 1); if (ret != 0) { mbedtls_md_free(&ctx); return HString{}; } mbedtls_md_hmac_starts(&ctx, secret, secret_len); mbedtls_md_hmac_update(&ctx, (const uint8_t*)message.c_str(), message.length()); mbedtls_md_hmac_finish(&ctx, hmac_result); mbedtls_md_free(&ctx); return bytes_to_hex(hmac_result, 32); }