gf_ai_box/src/host_stream/handshake.c

205 lines
6.9 KiB
C

/*
* handshake.c — BLE Challenge-Response Authentication implementation
*
* Key: SHA256("29C310F5")[0..15] XOR 0x5A
* Challenge: R = 4-byte timestamp (big-endian) + 12 random bytes
* Verify: base64(SHA256(R || Key)) == received_response
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include "sha256.h"
#include "base64.h"
#include "handshake.h"
/* Timestamp when challenge was sent (for timeout check) */
static time_t s_challenge_send_time = 0;
/* ── Key derivation ─────────────────────────────────────────────────────── */
void handshake_derive_key(uint8_t out_key[HANDSHAKE_KEY_LEN])
{
uint8_t digest[32];
sha256((const uint8_t *)HANDSHAKE_KN_STR, strlen(HANDSHAKE_KN_STR), digest);
for (int i = 0; i < HANDSHAKE_KEY_LEN; i++)
out_key[i] = digest[i] ^ 0x5A;
}
void handshake_print_key(const uint8_t key[HANDSHAKE_KEY_LEN])
{
for (int i = 0; i < HANDSHAKE_KEY_LEN; i++)
printf("%02x%s", key[i], (i < HANDSHAKE_KEY_LEN - 1) ? " " : "\n");
}
/* ── Init / Reset ────────────────────────────────────────────────────────── */
void handshake_init(handshake_ctx_t *ctx)
{
memset(ctx, 0, sizeof(*ctx));
ctx->state = HS_STATE_IDLE;
handshake_derive_key(ctx->key);
}
void handshake_reset(handshake_ctx_t *ctx)
{
ctx->state = HS_STATE_IDLE;
memset(ctx->R, 0, sizeof(ctx->R));
s_challenge_send_time = 0;
}
handshake_state_t handshake_get_state(const handshake_ctx_t *ctx)
{
return ctx->state;
}
int handshake_check_timeout(handshake_ctx_t *ctx)
{
if (ctx->state != HS_STATE_WAIT_RESPONSE)
return 0;
return (time(NULL) - s_challenge_send_time >= HANDSHAKE_TIMEOUT_SEC) ? 1 : 0;
}
/* ── Challenge generation ────────────────────────────────────────────────── */
int handshake_build_challenge(handshake_ctx_t *ctx,
char *out_json_buf, size_t buf_size)
{
#if 0 /* ── TEST VECTOR (Protocol 5.3): change to #if 0 to restore random R ── */
/* R = 6a4636a0a1b2c3d4e5f60718293a4b5c
* Expected challenge: akY2oKGyw9Tl9gcYKTpLXA==
* Expected response: NISSISwhM59eitBMS/stTnJMSLIU7YWZG7bd+NkQBAU= */
static const uint8_t TEST_R[HANDSHAKE_R_LEN] = {
0x6a, 0x46, 0x36, 0xa0, 0xa1, 0xb2, 0xc3, 0xd4,
0xe5, 0xf6, 0x07, 0x18, 0x29, 0x3a, 0x4b, 0x5c
};
memcpy(ctx->R, TEST_R, HANDSHAKE_R_LEN);
time_t now = time(NULL); /* still needed for s_challenge_send_time */
#else /* Normal operation: random R */
/* R[0..3] = current time big-endian */
time_t now = time(NULL);
ctx->R[0] = (uint8_t)((now >> 24) & 0xFF);
ctx->R[1] = (uint8_t)((now >> 16) & 0xFF);
ctx->R[2] = (uint8_t)((now >> 8) & 0xFF);
ctx->R[3] = (uint8_t)( now & 0xFF);
/* R[4..15] = 12 random bytes from /dev/urandom */
int fd = open("/dev/urandom", O_RDONLY);
if (fd >= 0) {
if (read(fd, ctx->R + 4, 12) != 12) {
/* partial read fallback */
for (int i = 4; i < 16; i++) ctx->R[i] ^= (uint8_t)(rand() ^ i);
}
close(fd);
} else {
/* /dev/urandom unavailable — simple fallback */
srand((unsigned int)now);
for (int i = 4; i < 16; i++)
ctx->R[i] = (uint8_t)(rand() ^ i ^ (int)now);
}
#endif
/* Base64-encode R (16 bytes → 24 chars + NUL) */
char r_b64[28]; /* ceil(16/3)*4+1 = 25 bytes, round up */
base64_encode(ctx->R, HANDSHAKE_R_LEN, r_b64);
/* Build JSON */
int n = snprintf(out_json_buf, buf_size,
"{\"response_type\":\"handshake_challenge\","
"\"content\":{\"challenge\":\"%s\"}}",
r_b64);
if (n < 0 || (size_t)n >= buf_size)
return -1;
/* Record send time and advance state */
s_challenge_send_time = now;
ctx->state = HS_STATE_WAIT_RESPONSE;
return 0;
}
/* Response validation*/
int handshake_process_response(handshake_ctx_t *ctx, const char *json_str)
{
/* 1. Timeout check */
if (handshake_check_timeout(ctx)) {
ctx->state = HS_STATE_FAILED;
return -2;
}
/* 2. Parse "response" field value from JSON
* Looks for: "response":"<base64>" */
const char *p = strstr(json_str, "\"response\"");
if (!p) return -1;
p = strchr(p, ':');
if (!p) return -1;
/* skip whitespace and colon */
while (*p == ':' || *p == ' ' || *p == '\t') p++;
if (*p != '"') return -1;
p++; /* skip opening quote */
const char *end = strchr(p, '"');
if (!end) return -1;
size_t val_len = (size_t)(end - p);
if (val_len == 0 || val_len > 64) return -1;
char received_b64[68];
memcpy(received_b64, p, val_len);
received_b64[val_len] = '\0';
/* 3. Compute expected = base64(SHA256(R || Key)) */
uint8_t input[HANDSHAKE_R_LEN + HANDSHAKE_KEY_LEN]; /* 32 bytes */
memcpy(input, ctx->R, HANDSHAKE_R_LEN);
memcpy(input + HANDSHAKE_R_LEN, ctx->key, HANDSHAKE_KEY_LEN);
uint8_t digest[32];
sha256(input, HANDSHAKE_R_LEN + HANDSHAKE_KEY_LEN, digest);
char expected_b64[48]; /* 32 bytes → 44 chars + NUL */
base64_encode(digest, 32, expected_b64);
printf("[HS DEBUG] R : ");
for (int i = 0; i < HANDSHAKE_R_LEN; i++) printf("%02x", ctx->R[i]);
printf("\n");
printf("[HS DEBUG] Key : ");
for (int i = 0; i < HANDSHAKE_KEY_LEN; i++) printf("%02x", ctx->key[i]);
printf("\n");
printf("[HS DEBUG] expected : %s\n", expected_b64);
printf("[HS DEBUG] received : %s\n", received_b64);
/* 4. Constant-time compare (simple for embedded) */
if (strcmp(received_b64, expected_b64) == 0) {
ctx->state = HS_STATE_SUCCESS;
return 1;
}
ctx->state = HS_STATE_FAILED;
return 0;
}
/* ── Confirm message ─────────────────────────────────────────────────────── */
int handshake_build_confirm(handshake_ctx_t *ctx, int success,
const char *reason_str,
char *out_json_buf, size_t buf_size)
{
(void)ctx;
if (success) {
snprintf(out_json_buf, buf_size,
"{\"response_type\":\"handshake_confirm\","
"\"content\":{\"status\":\"success\"}}");
} else {
const char *reason = reason_str ? reason_str : "invalid_response";
snprintf(out_json_buf, buf_size,
"{\"response_type\":\"handshake_confirm\","
"\"content\":{\"status\":\"failed\",\"reason\":\"%s\"}}",
reason);
}
return 0;
}