Para que la máquina se conecte al Wi-Fi de la gasera, el ESP32 suele levantar un "Captive Portal". El técnico se conecta a la red "MOBAGAS_001" y usa una interfaz web local para meter la contraseña. Muchos ingenieros dejan este portal encendido para siempre, o habilitan WebSockets locales (ws://) para calibrar la máquina con el celular.
Si este WebSocket local no usa cifrado SSL/TLS (wss://) porque el ESP32 no tiene suficiente RAM, los comandos viajan en texto plano. Un atacante en el estacionamiento de la gasera, corriendo *Wireshark*, puede leer el comando {"cmd": "TEST_VALVE_OPEN"}.
Incluso si ciframos el canal o ponemos una contraseña en el portal, el atacante puede usar un Ataque de Replay. Si el atacante intercepta los bytes exactos (encriptados o no) de un comando válido de "Despacho Gratuito de Prueba", no necesita saber qué dicen esos bytes. Solo necesita enviarle esos mismos bytes de vuelta al ESP32. El ESP32 se los pasará por UART al STM32, y la máquina obedecerá abriendo la válvula.
Para evitar que un comando grabado ayer se use hoy, la comunicación UART entre el ESP32 y el STM32 (y por ende el WebSocket) debe estar firmada (HMAC) e incluir un Nonce (Number Used Once) o un Timestamp de época. Si el STM32 recibe un comando válido pero cuyo Timestamp tiene más de 5 segundos de antigüedad, deduce que es un ataque de Replay y lo rechaza.
Paso 1: TTL en el Access Point del ESP32.
La primera barrera es no tener la puerta abierta. El AP del ESP32 (modo configuración) debe tener un temporizador. A los 5 minutos de encender la máquina, si nadie se conectó, el ESP32 debe ejecutar WiFi.softAPdisconnect(true); y apagar su radio transmisor local, dejando solo la conexión STA (Cliente hacia el router de la gasera).
Paso 2: Compartir el Secreto (Shared Secret).
En la fábrica, a través del Jig de inyección (Día 330), quemamos una llave secreta de 16 bytes (ej. K_SECRET) en la EEPROM tanto del ESP32 como del STM32. Esta llave nunca viaja por el aire ni por los cables. Se usa internamente para "firmar" los mensajes localmente antes de que viajen por el bus UART cruzando la placa.
El código a continuación procesa tramas crudas UART en el lado del STM32. Eliminamos sprintf y parsing pesado. Asumimos que el ESP32 envía un struct binario (o un arreglo formateado). El STM32 valida el tiempo y el Hash. Usaremos un hash simplificado para ilustrar la mecánica sin ahogar la memoria.
/**
* @file secure_uart_comms.c
* @brief Validación Criptográfica Ligera para Comandos Locales (Prevención de Replay)
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
// Secreto guardado en la ROM interna del STM32 (Protegido por RDP Nivel 1)
const uint8_t HARDWARE_SECRET_KEY[8] = {0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6, 0x11, 0x22};
// Estructura de Comando Seguro empacada (UART Payload)
#pragma pack(push, 1)
typedef struct {
uint8_t cmd_id; // Ej. 0x05 para "Abrir Válvula Test"
uint32_t payload_val; // Ej. Duración del test en ms
uint32_t timestamp; // Tiempo UNIX de emisión
uint32_t signature; // Firma de validación
} SecureCommand_t;
#pragma pack(pop)
// Reloj interno sincronizado por NTP a través del ESP32
extern uint32_t current_unix_time;
// =========================================================================
// HASH LIGERO (FNV-1a / Custom Hash en lugar de SHA-256 para ahorrar CPU)
// =========================================================================
uint32_t Calculate_Payload_Signature(uint8_t cmd, uint32_t val, uint32_t ts) {
uint32_t hash = 2166136261u; // Valor semilla FNV
// Hasheamos los datos
hash = (hash ^ cmd) * 16777619;
hash = (hash ^ (val & 0xFF)) * 16777619;
hash = (hash ^ (ts & 0xFF)) * 16777619;
// Hasheamos el secreto (El atacante no puede hacer esto porque no conoce la llave)
for(uint8_t i = 0; i < 8; i++) {
hash = (hash ^ HARDWARE_SECRET_KEY[i]) * 16777619;
}
return hash;
}
// =========================================================================
// MOTOR DE AUTENTICACIÓN
// =========================================================================
/**
* @brief Evalúa un paquete entrante antes de pasarlo a la Máquina de Estados.
*/
bool Validate_And_Execute_Command(SecureCommand_t* rx_cmd) {
// 1. DEFENSA ANTI-REPLAY (Validación de Tiempo)
// Si el comando tiene más de 10 segundos de antigüedad (o viaja del pasado), rechazar.
int32_t time_diff = (int32_t)current_unix_time - (int32_t)rx_cmd->timestamp;
if (time_diff < 0 || time_diff > 10) {
// Alerta de Ciberseguridad Local!
// Posible atacante intentando reproducir un comando de ayer.
return false;
}
// 2. DEFENSA DE AUTENTICIDAD (Cálculo de Firma)
uint32_t expected_sig = Calculate_Payload_Signature(rx_cmd->cmd_id, rx_cmd->payload_val, rx_cmd->timestamp);
if (expected_sig != rx_cmd->signature) {
// El atacante intentó forjar un nuevo comando cambiando el ID o el Timestamp,
// pero como no tiene el HARDWARE_SECRET_KEY, el hash no coincide.
return false;
}
// 3. COMANDO VALIDADO -> EJECUTAR
// Solo si pasamos ambas defensas, ejecutamos el actuador.
switch (rx_cmd->cmd_id) {
case 0x05:
// Ejecutar_Prueba_Valvula();
break;
}
return true;
}
Objetivo del día: Seguridad Lógica de Canal Físico e Inmunidad a Interceptación.
El diseño del circuito (Día 313) donde aislamos el ESP32 del STM32 nos dio inmensa estabilidad contra bloqueos de Wi-Fi, pero creó un vector de ataque: el bus serial (UART) que une a ambos chips es un cable de cobre. En los ataques de campo, un criminal podría destornillar el frente del equipo, conectar dos agujas a las pistas de cobre del UART e inyectar sus propios comandos seriales directamente al procesador principal para robar gas, simulando ser el módulo ESP32.
Al implementar **Autenticación por Firma (HMAC-lite)**, has convertido un simple cable serial en una bóveda impenetrable. La Deuda Técnica que limpiamos ayer (Día 336) nos dio la memoria y la velocidad necesarias para ejecutar este Hash FNV en tiempo real utilizando estrictamente `stdint.h`. Incluso si el ladrón tiene un osciloscopio y logra capturar los bytes perfectos que dicen "Abrir Válvula", esos bytes incluyen un *Timestamp* incrustado y sellado matemáticamente por una llave quemada en la ROM protegida por el RDP Nivel 1 (Día 329). Si intenta enviar ese comando de nuevo, el reloj del STM32 habrá avanzado, el *Timestamp* habrá caducado, y el comando rebotará inútilmente contra la muralla criptográfica. Tu máquina acaba de alcanzar el Grado Bancario.