espilon-source/espilon_bot/components/core/messages.c
Eun0us 8b6c1cd53d ε - ChaCha20-Poly1305 AEAD + HKDF crypto upgrade + C3PO rewrite + docs
Crypto:
- Replace broken ChaCha20 (static nonce) with ChaCha20-Poly1305 AEAD
- HKDF-SHA256 key derivation from per-device factory NVS master keys
- Random 12-byte nonce per message (ESP32 hardware RNG)
- crypto_init/encrypt/decrypt API with mbedtls legacy (ESP-IDF v5.3.2)
- Custom partition table with factory NVS (fctry at 0x10000)

Firmware:
- crypto.c full rewrite, messages.c device_id prefix + AEAD encrypt
- crypto_init() at boot with esp_restart() on failure
- Fix command_t initializations across all modules (sub/help fields)
- Clean CMakeLists dependencies for ESP-IDF v5.3.2

C3PO (C2):
- Rename tools/c2 + tools/c3po -> tools/C3PO
- Per-device CryptoContext with HKDF key derivation
- KeyStore (keys.json) for master key management
- Transport parses device_id:base64(...) wire format

Tools:
- New tools/provisioning/provision.py for factory NVS key generation
- Updated flasher with mbedtls config for v5.3.2

Docs:
- Update all READMEs for new crypto, C3PO paths, provisioning
- Update roadmap, architecture diagrams, security sections
- Update CONTRIBUTING.md project structure
2026-02-10 21:28:45 +01:00

199 lines
4.6 KiB
C

#include <stdio.h>
#include <string.h>
#include <time.h>
#include "esp_log.h"
#include "lwip/sockets.h"
#include "pb_encode.h"
#include "c2.pb.h"
#include "freertos/semphr.h"
#include "utils.h" /* crypto_encrypt, base64_encode, CONFIG_DEVICE_ID */
#define TAG "AGENT_MSG"
#define MAX_PROTOBUF_SIZE 512
extern int sock;
extern SemaphoreHandle_t sock_mutex;
/* ============================================================
* TCP helpers
* ============================================================ */
static bool tcp_send_all(const void *buf, size_t len)
{
#ifdef CONFIG_NETWORK_WIFI
xSemaphoreTake(sock_mutex, portMAX_DELAY);
int current_sock = sock;
xSemaphoreGive(sock_mutex);
if (current_sock < 0) {
ESP_LOGE(TAG, "socket not connected");
return false;
}
const uint8_t *p = (const uint8_t *)buf;
while (len > 0) {
int sent = lwip_write(current_sock, p, len);
if (sent <= 0) {
ESP_LOGE(TAG, "lwip_write failed");
return false;
}
p += sent;
len -= sent;
}
return true;
#elif defined(CONFIG_NETWORK_GPRS)
return gprs_send(buf, len);
#else
#error "No network backend selected"
#endif
}
static bool send_base64_frame(const uint8_t *data, size_t len)
{
char *b64 = base64_encode(data, len);
if (!b64) {
ESP_LOGE(TAG, "base64_encode failed");
return false;
}
/* Prepend "device_id:" so the C2 can identify which key to use */
bool ok = tcp_send_all(CONFIG_DEVICE_ID, strlen(CONFIG_DEVICE_ID))
&& tcp_send_all(":", 1)
&& tcp_send_all(b64, strlen(b64))
&& tcp_send_all("\n", 1);
free(b64);
return ok;
}
/* ============================================================
* Encode → encrypt → base64 → send
* ============================================================ */
static bool encode_encrypt_send(c2_AgentMessage *msg)
{
uint8_t pb_buf[MAX_PROTOBUF_SIZE];
pb_ostream_t stream =
pb_ostream_from_buffer(pb_buf, sizeof(pb_buf));
if (!pb_encode(&stream, c2_AgentMessage_fields, msg)) {
ESP_LOGE(TAG, "pb_encode failed: %s",
PB_GET_ERROR(&stream));
return false;
}
size_t proto_len = stream.bytes_written;
/* nonce[12] + ciphertext + tag[16] */
uint8_t enc_buf[MAX_PROTOBUF_SIZE + 12 + 16];
int enc_len = crypto_encrypt(pb_buf, proto_len,
enc_buf, sizeof(enc_buf));
if (enc_len < 0) {
ESP_LOGE(TAG, "crypto_encrypt failed");
return false;
}
return send_base64_frame(enc_buf, (size_t)enc_len);
}
/* ============================================================
* Core send API
* ============================================================ */
bool agent_send(c2_AgentMsgType type,
const char *source,
const char *request_id,
const void *data,
size_t len,
bool eof)
{
c2_AgentMessage msg = c2_AgentMessage_init_zero;
/* mandatory */
strncpy(msg.device_id, CONFIG_DEVICE_ID,
sizeof(msg.device_id) - 1);
msg.type = type;
msg.eof = eof;
/* optional */
if (source) {
strncpy(msg.source, source,
sizeof(msg.source) - 1);
}
if (request_id) {
strncpy(msg.request_id, request_id,
sizeof(msg.request_id) - 1);
}
if (data && len > 0) {
if (len > sizeof(msg.payload.bytes))
len = sizeof(msg.payload.bytes);
msg.payload.size = len;
memcpy(msg.payload.bytes, data, len);
}
return encode_encrypt_send(&msg);
}
/* ============================================================
* High-level helpers (USED EVERYWHERE)
* ============================================================ */
bool msg_info(const char *src,
const char *msg,
const char *req)
{
return agent_send(
c2_AgentMsgType_AGENT_INFO,
src,
req,
msg,
msg ? strlen(msg) : 0,
true
);
}
bool msg_error(const char *src,
const char *msg,
const char *req)
{
return agent_send(
c2_AgentMsgType_AGENT_ERROR,
src,
req,
msg,
msg ? strlen(msg) : 0,
true
);
}
bool msg_data(const char *src,
const void *data,
size_t len,
bool eof,
const char *req)
{
if (!data || len == 0)
return false;
return agent_send(
c2_AgentMsgType_AGENT_DATA,
src,
req,
data,
len,
eof
);
}