157 lines
6.2 KiB
C
157 lines
6.2 KiB
C
#include "vl_protocol.h"
|
|||
|
|
#include "mbedtls/base64.h"
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|