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
199 lines
4.6 KiB
C
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
|
|
);
|
|
}
|