feat: HMAC-SHA256 signing module with native tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
64
firmware/lib/hmac/hmac.cpp
Normal file
64
firmware/lib/hmac/hmac.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
// firmware/src/hmac.cpp
|
||||
#include "hmac.h"
|
||||
#include "mbedtls/md.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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 void hex_to_bytes(const HString& hex, uint8_t* out, size_t out_len) {
|
||||
for (size_t i = 0; i < out_len && (i * 2 + 1) < hex.size(); i++) {
|
||||
char byte_str[3] = {hex[i*2], hex[i*2+1], 0};
|
||||
out[i] = (uint8_t)strtol(byte_str, nullptr, 16);
|
||||
}
|
||||
}
|
||||
|
||||
static void 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);
|
||||
mbedtls_md_setup(&ctx, info, 0);
|
||||
mbedtls_md_starts(&ctx);
|
||||
mbedtls_md_update(&ctx, data, len);
|
||||
mbedtls_md_finish(&ctx, out);
|
||||
mbedtls_md_free(&ctx);
|
||||
}
|
||||
|
||||
HString hmac_sign(const HString& secret_hex, const HString& device_id,
|
||||
uint32_t timestamp, const HString& body) {
|
||||
// 1. SHA256(body)
|
||||
uint8_t body_hash[32];
|
||||
sha256((const uint8_t*)body.c_str(), body.size(), body_hash);
|
||||
HString body_hash_hex = bytes_to_hex(body_hash, 32);
|
||||
|
||||
// 2. Build message
|
||||
char ts_buf[12];
|
||||
snprintf(ts_buf, sizeof(ts_buf), "%u", (unsigned)timestamp);
|
||||
HString message = device_id + ":" + ts_buf + ":" + body_hash_hex;
|
||||
|
||||
// 3. Decode secret from hex
|
||||
size_t secret_len = secret_hex.size() / 2;
|
||||
uint8_t secret[64] = {};
|
||||
hex_to_bytes(secret_hex, secret, secret_len);
|
||||
|
||||
// 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);
|
||||
mbedtls_md_setup(&ctx, info, 1);
|
||||
mbedtls_md_hmac_starts(&ctx, secret, secret_len);
|
||||
mbedtls_md_hmac_update(&ctx, (const uint8_t*)message.c_str(), message.size());
|
||||
mbedtls_md_hmac_finish(&ctx, hmac_result);
|
||||
mbedtls_md_free(&ctx);
|
||||
|
||||
return bytes_to_hex(hmac_result, 32);
|
||||
}
|
||||
16
firmware/lib/hmac/hmac.h
Normal file
16
firmware/lib/hmac/hmac.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// firmware/src/hmac.h
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef NATIVE_TEST
|
||||
#include <string>
|
||||
using HString = std::string;
|
||||
#else
|
||||
#include <Arduino.h>
|
||||
using HString = String;
|
||||
#endif
|
||||
|
||||
// Returns lowercase hex-encoded HMAC-SHA256 signature.
|
||||
// Message signed: device_id + ":" + timestamp_str + ":" + hex(sha256(body))
|
||||
HString hmac_sign(const HString& secret_hex, const HString& device_id,
|
||||
uint32_t timestamp, const HString& body);
|
||||
@@ -26,3 +26,5 @@ test_framework = unity
|
||||
build_flags =
|
||||
-std=c++17
|
||||
-DNATIVE_TEST
|
||||
lib_deps =
|
||||
kochcodes/mbedtls@^3.6.2
|
||||
|
||||
34
firmware/test/test_native/test_hmac.cpp
Normal file
34
firmware/test/test_native/test_hmac.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// firmware/test/test_native/test_hmac.cpp
|
||||
#include <unity.h>
|
||||
#include "hmac.h"
|
||||
|
||||
void setUp(void) {}
|
||||
void tearDown(void) {}
|
||||
|
||||
void test_hmac_known_vector() {
|
||||
HString secret = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
|
||||
HString device = "dc-0042";
|
||||
HString body = "{\"location_id\":\"retailer-123\",\"records\":[]}";
|
||||
uint32_t ts = 1712000000;
|
||||
|
||||
HString result = hmac_sign(secret, device, ts, body);
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING("90f5fa5fdbf7f95e7475791bf5bb90cdef7f16534d9a7d263fc588305bad0525", result.c_str());
|
||||
}
|
||||
|
||||
void test_hmac_different_timestamp_gives_different_sig() {
|
||||
HString secret = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
|
||||
HString device = "dc-0042";
|
||||
HString body = "{}";
|
||||
|
||||
HString sig1 = hmac_sign(secret, device, 1712000000, body);
|
||||
HString sig2 = hmac_sign(secret, device, 1712000001, body);
|
||||
TEST_ASSERT_NOT_EQUAL(0, sig1.compare(sig2));
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_hmac_known_vector);
|
||||
RUN_TEST(test_hmac_different_timestamp_gives_different_sig);
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in New Issue
Block a user