#include "vl_protocol.h" #include "mbedtls/base64.h" #include static int hex_digit(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -1; } static void to_hex(const uint8_t *in, size_t n, char *out) { const char *hex = "0123456789abcdef"; for (size_t i = 0; i < n; i++) { out[i * 2] = hex[in[i] >> 4]; out[i * 2 + 1] = hex[in[i] & 15]; } out[n * 2] = '\0'; } int vl_key_from_hex(const char *hex, uint8_t key[32]) { if (!hex || !key || strlen(hex) != 64) return -1; for (size_t i = 0; i < 64; i++) if (hex_digit(hex[i]) < 0) return -1; for (size_t i = 0; i < 32; i++) key[i] = (uint8_t)((hex_digit(hex[i * 2]) << 4) | hex_digit(hex[i * 2 + 1])); return 0; } int vl_nonce(vl_rng_fn rng, void *context, char out[23]) { uint8_t random[16], base64[25]; size_t n = 0; if (!rng || !out) return -1; out[0] = '\0'; if (rng(context, random, sizeof(random)) != 0) return -1; if (mbedtls_base64_encode(base64, sizeof(base64), &n, random, sizeof(random))) return -1; if (n != 24) return -1; for (size_t i = 0; i < 22; i++) out[i] = base64[i] == '+' ? '-' : base64[i] == '/' ? '_' : (char)base64[i]; out[22] = '\0'; return 0; } int vl_uuid4(vl_rng_fn rng, void *context, char out[37]) { uint8_t random[16]; char hex[33]; size_t p = 0; if (!rng || !out) return -1; out[0] = '\0'; if (rng(context, random, sizeof(random)) != 0) return -1; random[6] = (uint8_t)((random[6] & 15) | 0x40); random[8] = (uint8_t)((random[8] & 63) | 0x80); to_hex(random, 16, hex); for (size_t i = 0; i < 32; i++) { if (i == 8 || i == 12 || i == 16 || i == 20) out[p++] = '-'; out[p++] = hex[i]; } out[p] = '\0'; return 0; } int vl_sha256_begin(mbedtls_md_context_t *context) { if (!context) return -1; mbedtls_md_init(context); const mbedtls_md_info_t *info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); if (!info) return -1; int rc = mbedtls_md_setup(context, info, 0); if (rc == 0) rc = mbedtls_md_starts(context); return rc; } int vl_sha256_update(mbedtls_md_context_t *context, const void *bytes, size_t n) { if (!context || (!bytes && n)) return -1; return n ? mbedtls_md_update(context, bytes, n) : 0; } int vl_sha256_finish(mbedtls_md_context_t *context, char out[65]) { uint8_t digest[32]; if (!context || !out) return -1; out[0] = '\0'; int rc = mbedtls_md_finish(context, digest); if (rc == 0) to_hex(digest, 32, out); return rc; } int vl_sha256_hex(const void *bytes, size_t length, char out[65]) { if (!out || (!bytes && length)) return -1; out[0] = '\0'; mbedtls_md_context_t context; int rc = vl_sha256_begin(&context); if (rc == 0) rc = vl_sha256_update(&context, bytes, length); if (rc == 0) rc = vl_sha256_finish(&context, out); mbedtls_md_free(&context); return rc; } /* Hash the exact LF-joined fields without building a large intermediate string. */ static int hmac_fields(const uint8_t key[32], const char **fields, size_t count, char out[65]) { if (!key || !fields || !out) return -1; out[0] = '\0'; for (size_t i = 0; i < count; i++) if (!fields[i] || strchr(fields[i], '\n') || strchr(fields[i], '\r')) return -1; const mbedtls_md_info_t *info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); if (!info) return -1; mbedtls_md_context_t context; uint8_t digest[32]; mbedtls_md_init(&context); int rc = mbedtls_md_setup(&context, info, 1); if (rc == 0) rc = mbedtls_md_hmac_starts(&context, key, 32); for (size_t i = 0; rc == 0 && i < count; i++) { if (i) rc = mbedtls_md_hmac_update(&context, (const uint8_t *)"\n", 1); if (rc == 0) rc = mbedtls_md_hmac_update(&context, (const uint8_t *)fields[i], strlen(fields[i])); } if (rc == 0) rc = mbedtls_md_hmac_finish(&context, digest); mbedtls_md_free(&context); if (rc == 0) to_hex(digest, 32, out); return rc; } int vl_http_sign(const uint8_t key[32], const char *method, const char *path, const char *device_id, const char *key_version, const char *timestamp_ms, const char *nonce, const char *request_id, const void *body, size_t body_length, char signature_hex[65]) { if (!signature_hex) return -1; signature_hex[0] = '\0'; if (!method || strcmp(method, "POST") || !path || path[0] != '/') return -1; char body_sha[65]; if (vl_sha256_hex(body, body_length, body_sha)) return -1; const char *fields[] = {"DEVREQ1", method, path, device_id, key_version, timestamp_ms, nonce, request_id, body_sha}; return hmac_fields(key, fields, 9, signature_hex); } int vl_ble_pack(const void *json_utf8, size_t length, uint8_t *frame, size_t capacity, size_t *frame_length) { if (!frame_length) return -1; *frame_length = 0; if (!json_utf8 || !frame || length == 0 || length > 2048 || capacity < length + 2) return -1; frame[0] = (uint8_t)(length & 255); frame[1] = (uint8_t)(length >> 8); memcpy(frame + 2, json_utf8, length); *frame_length = length + 2; return 0; } int vl_ota_verify(vl_read_fn read, void *context, uint64_t expected_size, const char *expected_sha256) { uint8_t buffer[1024], expected[32], actual[32]; char hash[65]; uint64_t total = 0; if (!read || !expected_size || vl_key_from_hex(expected_sha256, expected)) return -1; mbedtls_md_context_t sha; int rc = vl_sha256_begin(&sha); while (rc == 0) { size_t n = 0; if (read(context, buffer, sizeof(buffer), &n) != 0 || n > sizeof(buffer)) { rc = -1; break; } if (n == 0) break; if ((uint64_t)n > expected_size - total) { rc = -1; break; } total += n; rc = vl_sha256_update(&sha, buffer, n); } if (rc == 0 && total != expected_size) rc = -1; if (rc == 0) rc = vl_sha256_finish(&sha, hash); mbedtls_md_free(&sha); if (rc != 0 || vl_key_from_hex(hash, actual)) return -1; return memcmp(actual, expected, 32) == 0 ? 0 : -1; }