Relé BLE — modelo de amenazas

Qué puede hacer un atacante que conoce el código de la firmware pero no tiene acceso a los secretos del dispositivo.

Superficie de ataque

La firmware es pública, así que los formatos de paquete, las longitudes y los algoritmos (X25519, HMAC-SHA256, HKDF) son conocidos. BLE es aire abierto, así que los anuncios y las escrituras GATT pueden escucharse. Ni en la firmware ni en el aire hay un solo secreto.

Dónde viven las claves:

Qué no puede hacer un atacante

AttackDefence
Falsificar un FIREHMAC-SHA256. Sin owner_secret ni guest_key eso es un 2128 de fuerza bruta: inviable.
Falsificar una escritura TIMEEl mismo HMAC sobre bleId + epochMs.
Repetir un anuncio interceptadoEl contador es monótono: ante una repetición o una disminución el HMAC no cuadra.
Repetir un fire interceptadoEl anillo de nonces invalida los ya usados.
Atrasar el reloj del controladorEl trinquete: cualquier TIME < trinquete se rechaza. El trinquete vive en la NVS y sobrevive a un corte de corriente.
Usar un paquete caducadoTras el funcionamiento del controlador, el trinquete es al menos la última hora real. En un paquete caducado validUntil queda por debajo del trinquete, así que el fire falla por caducidad.
Adelantar el reloj un año y dejar fuera a todos los invitadosUn tope de +24 h por cada escritura TIME de invitado. El propietario no tiene tope: se confía en él.
Emparejarse como segundo propietario sobre el primeroTras el emparejamiento el controlador guarda owner_secret en la NVS y no vuelve al modo de emparejamiento sin un reinicio de fábrica.

Qué puede hacer un atacante y qué cuesta

1. Adelantar la hora: denegación de servicio a los invitados legítimos

Requires: un paquete de invitado válido y no caducado.

Action: cada vez que esté en alcance, escribir un TIME que salte 24 horas hacia delante.

Limit: +24 h por sesión, y hace falta un fire reciente si la última sincronización fue hace menos de una hora. Tras diez visitas el trinquete va diez días por delante y los tokens del resto de invitados legítimos parecen caducados.

La otra cara: el token del propio atacante se juzga con esa misma hora falsa y por eso caduca antes.

Qué hacer: el propietario se acerca y escribe la hora real. El trinquete no se mueve, porque no puede retroceder, pero los nuevos tokens de invitado se emiten con un validUntil actual y a partir de ahí funcionan para todos. Recuperarse no exige más que refrescar la validez en el servidor.

Severity: una denegación de servicio temporal hasta que pase el propietario. No es crítico.

2. Hombre en el medio durante el emparejamiento

Requires: estar físicamente al alcance del controlador durante la ventana de 90 segundos posterior a iniciar el emparejamiento.

Action: sustituir en el saludo de emparejamiento su propia clave pública X25519.

Result: el controlador se empareja con el atacante y no con el propietario. El propietario cree que el emparejamiento salió bien pero no puede abrir nada desde el teléfono.

Defensa actual: solo la ventana de tiempo y la presencia física del propietario: ve que el emparejamiento no se confirmó y lo repite.

Hardening: añadir un código de verificación sencillo —cuatro dígitos en la pantalla del controlador o en Serial— que el propietario confirme en la aplicación. Hoy ese código no existe.

Severity: la ventana es estrecha y exige estar cerca justo en esos 90 segundos. Difícil de lograr en la práctica.

3. Reinicio de fábrica físico

Requires: acceso físico al controlador y una pulsación larga del botón.

Action: La NVS se borra, owner_secret se elimina y el controlador vuelve al modo de emparejamiento.

Result: el atacante empareja el controlador consigo mismo y el propietario pierde el acceso.

Defence: montar el controlador en un lugar protegido: dentro de una caja, bajo una tapa.

Severity: lo mismo que arrancar un portero automático de la puerta: no es un problema criptográfico.

4. Extraer owner_secret por UART o JTAG

Requires: acceso físico y un volcado de la flash con el arranque seguro y el cifrado de flash desactivados: hoy lo están.

Action: leer la NVS donde está owner_secret.

Result: compromiso total del dispositivo y de todos los tokens de invitado.

Defence: activar el arranque seguro y el cifrado de flash del ESP32 (efuse, irreversible). Hoy no está activado.

Severity: aceptable para uso doméstico. En instalaciones críticas hace falta arranque seguro.

5. Ocupar conexiones GATT: denegación de servicio

Requires: estar al alcance del BLE.

Action: conectarse y no desconectarse. NimBLE admite solo un número limitado de conexiones simultáneas.

Result: los clientes legítimos no pueden conectarse mientras el atacante ocupa una ranura.

Defensa actual: solo los tiempos de espera de NimBLE.

Hardening: añadir un tiempo de inactividad: si el cliente no realiza un FIRE válido en 5 segundos, cortar la conexión.

Severity: una denegación de servicio temporal mientras el atacante esté cerca.

6. Una avalancha de escrituras inválidas (suplantación de conexión)

Action: bombardear el controlador con escrituras basura.

Result: el controlador las descarta y las anota en Serial. Su estado no cambia y, en cuanto el atacante se va, todo funciona como siempre.

Qué no es una defensa, a propósito

Tres puntos parecen protección y no lo son. Mejor nombrarlos que dejar que alguien se apoye en ellos.

El byte de estado del anuncio lleva una etiqueta de 16 bits. Basta contra una corrupción accidental en el aire y nada más: un falsificador decidido prueba 16 bits en segundos. El byte solo gobierna el indicador de abierto/cerrado en la tarjeta y no concede ningún permiso, por eso la etiqueta sigue siendo corta: cada byte de más cuesta batería en cada despertar.

Las claves solo para la aplicación se apoyan en un secreto compilado dentro del APK. Quien desempaquete el archivo obtiene el secreto y falsifica la atestación. Eso sube el listón frente a un cliente web ocasional, nada más. El control real de una clave así es atarla a la huella de un dispositivo: esa comprobación corre en el servidor y no depende de que la aplicación guarde un secreto.

La caducidad de una clave de invitado la impone el propio controlador en BLE: el token lleva un plazo y el controlador tiene reloj. En los demás tipos de objeto no hay plazo alguno: la clave vive hasta que el propietario la revoca. La aplicación no afirma lo contrario, pero si algún día aparece un límite de tiempo en la interfaz también para ellos, habrá que imponerlo en el servidor y no dibujarlo en la tarjeta.

En resumen

Endurecimiento estándar para uso industrial:

  1. Arranque seguro y cifrado de flash (protección frente a JTAG y volcados).
  2. Un código de verificación en el flujo de emparejamiento (protección frente al hombre en el medio).
  3. Un tiempo de inactividad en las conexiones GATT (protección frente a la ocupación).
  4. Comparar firmas en tiempo constante: hecho en la firmware y en la aplicación; la anterior comparación byte a byte revelaba, por el tiempo de respuesta, cuántos bytes de una firma se habían acertado.

Nada de esto es crítico para los escenarios actuales, pero las instalaciones críticas merecen ese refuerzo.