Move command registry from components/command/ into components/core/. New modules: mod_canbus, mod_honeypot, mod_fallback, mod_redteam, mod_ota. Replace mod_proxy with tun_core (multiplexed SOCKS5 tunnel). Kconfig extended with per-module settings and async worker config.
357 lines
9.6 KiB
C
357 lines
9.6 KiB
C
// crypto.c – ChaCha20-Poly1305 AEAD with HKDF key derivation
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
#include "esp_log.h"
|
||
#include "esp_random.h"
|
||
#include "nvs_flash.h"
|
||
#include "nvs.h"
|
||
|
||
#include "mbedtls/chachapoly.h"
|
||
#include "mbedtls/hkdf.h"
|
||
#include "mbedtls/md.h"
|
||
#include "mbedtls/base64.h"
|
||
#include "mbedtls/platform_util.h"
|
||
|
||
#include "pb_decode.h"
|
||
#include "c2.pb.h"
|
||
|
||
#include "utils.h"
|
||
|
||
static const char *TAG = "CRYPTO";
|
||
|
||
#define NONCE_LEN 12
|
||
#define TAG_LEN 16
|
||
#define KEY_LEN 32
|
||
#define OVERHEAD (NONCE_LEN + TAG_LEN) /* 28 bytes */
|
||
|
||
static uint8_t derived_key[KEY_LEN];
|
||
static bool crypto_ready = false;
|
||
|
||
/* ============================================================
|
||
* crypto_init – read master key from factory NVS, derive via HKDF
|
||
* ============================================================ */
|
||
bool crypto_init(void)
|
||
{
|
||
esp_err_t err;
|
||
|
||
/* 1) Init the factory NVS partition */
|
||
err = nvs_flash_init_partition("fctry");
|
||
if (err != ESP_OK) {
|
||
ESP_LOGE(TAG, "nvs_flash_init_partition(fctry) failed: %s",
|
||
esp_err_to_name(err));
|
||
return false;
|
||
}
|
||
|
||
/* 2) Open the crypto namespace (read-only) */
|
||
nvs_handle_t handle;
|
||
err = nvs_open_from_partition(
|
||
"fctry",
|
||
CONFIG_CRYPTO_FCTRY_NS,
|
||
NVS_READONLY,
|
||
&handle
|
||
);
|
||
if (err != ESP_OK) {
|
||
ESP_LOGE(TAG, "nvs_open_from_partition(fctry/%s) failed: %s",
|
||
CONFIG_CRYPTO_FCTRY_NS, esp_err_to_name(err));
|
||
return false;
|
||
}
|
||
|
||
/* 3) Read the 32-byte master key blob */
|
||
uint8_t master_key[KEY_LEN];
|
||
size_t mk_len = sizeof(master_key);
|
||
|
||
err = nvs_get_blob(handle, CONFIG_CRYPTO_FCTRY_KEY, master_key, &mk_len);
|
||
nvs_close(handle);
|
||
|
||
if (err != ESP_OK || mk_len != KEY_LEN) {
|
||
ESP_LOGE(TAG, "nvs_get_blob(%s) failed: %s (len=%u)",
|
||
CONFIG_CRYPTO_FCTRY_KEY, esp_err_to_name(err),
|
||
(unsigned)mk_len);
|
||
mbedtls_platform_zeroize(master_key, sizeof(master_key));
|
||
return false;
|
||
}
|
||
|
||
/* 4) HKDF-SHA256: derive the encryption key */
|
||
const char *info = "espilon-c2-v1";
|
||
const char *salt = CONFIG_DEVICE_ID;
|
||
|
||
int ret = mbedtls_hkdf(
|
||
mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
|
||
(const uint8_t *)salt, strlen(salt),
|
||
master_key, KEY_LEN,
|
||
(const uint8_t *)info, strlen(info),
|
||
derived_key, KEY_LEN
|
||
);
|
||
|
||
/* Wipe master key from RAM immediately */
|
||
mbedtls_platform_zeroize(master_key, sizeof(master_key));
|
||
|
||
if (ret != 0) {
|
||
ESP_LOGE(TAG, "HKDF failed (%d)", ret);
|
||
return false;
|
||
}
|
||
|
||
crypto_ready = true;
|
||
ESP_LOGI(TAG, "Crypto ready (ChaCha20-Poly1305 + HKDF)");
|
||
return true;
|
||
}
|
||
|
||
/* ============================================================
|
||
* crypto_encrypt – ChaCha20-Poly1305 AEAD
|
||
*
|
||
* Output layout: nonce[12] || ciphertext[plain_len] || tag[16]
|
||
* Returns total output length, or -1 on error.
|
||
* ============================================================ */
|
||
int crypto_encrypt(const uint8_t *plain, size_t plain_len,
|
||
uint8_t *out, size_t out_cap)
|
||
{
|
||
if (!crypto_ready) {
|
||
ESP_LOGE(TAG, "crypto_encrypt: not initialized");
|
||
return -1;
|
||
}
|
||
if (!plain || plain_len == 0 || !out) {
|
||
ESP_LOGE(TAG, "crypto_encrypt: invalid args");
|
||
return -1;
|
||
}
|
||
|
||
size_t needed = plain_len + OVERHEAD;
|
||
if (out_cap < needed) {
|
||
ESP_LOGE(TAG, "crypto_encrypt: buffer too small (%u < %u)",
|
||
(unsigned)out_cap, (unsigned)needed);
|
||
return -1;
|
||
}
|
||
|
||
/* Random nonce in the first 12 bytes */
|
||
esp_fill_random(out, NONCE_LEN);
|
||
|
||
mbedtls_chachapoly_context ctx;
|
||
mbedtls_chachapoly_init(&ctx);
|
||
mbedtls_chachapoly_setkey(&ctx, derived_key);
|
||
|
||
int ret = mbedtls_chachapoly_encrypt_and_tag(
|
||
&ctx,
|
||
plain_len,
|
||
out, /* nonce */
|
||
NULL, 0, /* no AAD */
|
||
plain, /* input */
|
||
out + NONCE_LEN, /* output (ciphertext) */
|
||
out + NONCE_LEN + plain_len /* tag */
|
||
);
|
||
|
||
mbedtls_chachapoly_free(&ctx);
|
||
|
||
if (ret != 0) {
|
||
ESP_LOGE(TAG, "chachapoly encrypt failed (%d)", ret);
|
||
return -1;
|
||
}
|
||
|
||
return (int)needed;
|
||
}
|
||
|
||
/* ============================================================
|
||
* crypto_decrypt – ChaCha20-Poly1305 AEAD
|
||
*
|
||
* Input layout: nonce[12] || ciphertext[N] || tag[16]
|
||
* Returns plaintext length, or -1 on error / auth failure.
|
||
* ============================================================ */
|
||
int crypto_decrypt(const uint8_t *in, size_t in_len,
|
||
uint8_t *out, size_t out_cap)
|
||
{
|
||
if (!crypto_ready) {
|
||
ESP_LOGE(TAG, "crypto_decrypt: not initialized");
|
||
return -1;
|
||
}
|
||
if (!in || in_len < OVERHEAD || !out) {
|
||
ESP_LOGE(TAG, "crypto_decrypt: invalid args (in_len=%u)",
|
||
(unsigned)in_len);
|
||
return -1;
|
||
}
|
||
|
||
size_t ct_len = in_len - OVERHEAD;
|
||
if (out_cap < ct_len) {
|
||
ESP_LOGE(TAG, "crypto_decrypt: buffer too small");
|
||
return -1;
|
||
}
|
||
|
||
const uint8_t *nonce = in;
|
||
const uint8_t *ct = in + NONCE_LEN;
|
||
const uint8_t *tag = in + NONCE_LEN + ct_len;
|
||
|
||
mbedtls_chachapoly_context ctx;
|
||
mbedtls_chachapoly_init(&ctx);
|
||
mbedtls_chachapoly_setkey(&ctx, derived_key);
|
||
|
||
int ret = mbedtls_chachapoly_auth_decrypt(
|
||
&ctx,
|
||
ct_len,
|
||
nonce,
|
||
NULL, 0, /* no AAD */
|
||
tag,
|
||
ct,
|
||
out
|
||
);
|
||
|
||
mbedtls_chachapoly_free(&ctx);
|
||
|
||
if (ret != 0) {
|
||
ESP_LOGE(TAG, "AEAD auth/decrypt failed (%d)", ret);
|
||
return -1;
|
||
}
|
||
|
||
return (int)ct_len;
|
||
}
|
||
|
||
/* ============================================================
|
||
* Base64 encode
|
||
* ============================================================ */
|
||
char *base64_encode(const unsigned char *input, size_t input_len)
|
||
{
|
||
if (!input || input_len == 0) {
|
||
ESP_LOGE(TAG, "Invalid input to base64_encode");
|
||
return NULL;
|
||
}
|
||
|
||
size_t out_len = 4 * ((input_len + 2) / 3);
|
||
char *out = (char *)malloc(out_len + 1);
|
||
if (!out) {
|
||
ESP_LOGE(TAG, "malloc failed in base64_encode");
|
||
return NULL;
|
||
}
|
||
|
||
size_t written = 0;
|
||
int ret = mbedtls_base64_encode(
|
||
(unsigned char *)out,
|
||
out_len + 1,
|
||
&written,
|
||
input,
|
||
input_len
|
||
);
|
||
|
||
if (ret != 0) {
|
||
ESP_LOGE(TAG, "base64 encode failed (%d)", ret);
|
||
free(out);
|
||
return NULL;
|
||
}
|
||
|
||
out[written] = '\0';
|
||
return out;
|
||
}
|
||
|
||
/* ============================================================
|
||
* Base64 decode
|
||
* ============================================================ */
|
||
char *base64_decode(const char *input, size_t *output_len)
|
||
{
|
||
if (!input || !output_len) {
|
||
ESP_LOGE(TAG, "Invalid input to base64_decode");
|
||
return NULL;
|
||
}
|
||
|
||
size_t in_len = strlen(input);
|
||
size_t est_len = (in_len * 3) / 4;
|
||
|
||
unsigned char *out = (unsigned char *)malloc(est_len + 1);
|
||
if (!out) {
|
||
ESP_LOGE(TAG, "malloc failed in base64_decode");
|
||
return NULL;
|
||
}
|
||
|
||
int ret = mbedtls_base64_decode(
|
||
out,
|
||
est_len + 1,
|
||
output_len,
|
||
(const unsigned char *)input,
|
||
in_len
|
||
);
|
||
|
||
if (ret != 0) {
|
||
ESP_LOGE(TAG, "base64 decode failed (%d)", ret);
|
||
free(out);
|
||
return NULL;
|
||
}
|
||
|
||
out[*output_len] = '\0';
|
||
return (char *)out;
|
||
}
|
||
|
||
/* ============================================================
|
||
* C2: Decode + decrypt + protobuf + exec (COMMON WIFI/GPRS)
|
||
* ============================================================ */
|
||
bool c2_decode_and_exec(const char *frame)
|
||
{
|
||
if (!frame || !frame[0]) {
|
||
ESP_LOGW(TAG, "Empty C2 frame");
|
||
return false;
|
||
}
|
||
|
||
/* Trim CR/LF/spaces at end (SIM800 sometimes adds \r) */
|
||
char tmp[1024];
|
||
size_t n = strnlen(frame, sizeof(tmp) - 2);
|
||
memcpy(tmp, frame, n);
|
||
tmp[n] = '\0';
|
||
while (n > 0 && (tmp[n - 1] == '\r' || tmp[n - 1] == '\n' || tmp[n - 1] == ' ')) {
|
||
tmp[--n] = '\0';
|
||
}
|
||
|
||
ESP_LOGD(TAG, "C2 RX b64 (%u bytes)", (unsigned)n);
|
||
|
||
/* 1) Base64 decode */
|
||
size_t decoded_len = 0;
|
||
char *decoded = base64_decode(tmp, &decoded_len);
|
||
if (!decoded || decoded_len == 0) {
|
||
ESP_LOGE(TAG, "Base64 decode failed");
|
||
free(decoded);
|
||
return false;
|
||
}
|
||
|
||
/* 2) Decrypt + authenticate (AEAD) */
|
||
uint8_t plain[1024];
|
||
int plain_len = crypto_decrypt(
|
||
(const uint8_t *)decoded, decoded_len,
|
||
plain, sizeof(plain)
|
||
);
|
||
free(decoded);
|
||
|
||
if (plain_len < 0) {
|
||
ESP_LOGE(TAG, "Decrypt/auth failed – tampered or wrong key");
|
||
return false;
|
||
}
|
||
|
||
/* 3) Protobuf decode -> c2_Command */
|
||
c2_Command cmd = c2_Command_init_zero;
|
||
pb_istream_t is = pb_istream_from_buffer(plain, (size_t)plain_len);
|
||
|
||
if (!pb_decode(&is, c2_Command_fields, &cmd)) {
|
||
ESP_LOGE(TAG, "PB decode error: %s", PB_GET_ERROR(&is));
|
||
return false;
|
||
}
|
||
|
||
/* 4) Log + dispatch */
|
||
#ifdef CONFIG_ESPILON_LOG_C2_VERBOSE
|
||
ESP_LOGI(TAG, "==== C2 COMMAND ====");
|
||
ESP_LOGI(TAG, "name: %s", cmd.command_name);
|
||
ESP_LOGI(TAG, "argc: %d", cmd.argv_count);
|
||
if (cmd.request_id[0]) ESP_LOGI(TAG, "req : %s", cmd.request_id);
|
||
for (int i = 0; i < cmd.argv_count; i++) {
|
||
ESP_LOGI(TAG, "arg[%d]=%s", i, cmd.argv[i]);
|
||
}
|
||
ESP_LOGI(TAG, "====================");
|
||
#else
|
||
ESP_LOGI(
|
||
TAG,
|
||
"C2 CMD: %s argc=%d req=%s",
|
||
cmd.command_name,
|
||
cmd.argv_count,
|
||
cmd.request_id[0] ? cmd.request_id : "-"
|
||
);
|
||
for (int i = 0; i < cmd.argv_count; i++) {
|
||
ESP_LOGD(TAG, "arg[%d]=%s", i, cmd.argv[i]);
|
||
}
|
||
#endif
|
||
|
||
process_command(&cmd);
|
||
return true;
|
||
}
|