BLE relay — threat model

What an attacker can do knowing the firmware source but having no access to the device's secrets.

Attack surface

The firmware is public, so packet formats, lengths and algorithms (X25519, HMAC-SHA256, HKDF) are known. BLE is open air, so advertisements and GATT writes can be listened to. No secret is present either in the firmware or on the air.

Where the keys live:

What an attacker cannot do

AttackDefence
Forge a FIREHMAC-SHA256. Without owner_secret or guest_key that is a 2128 brute force — not feasible.
Forge a TIME writeThe same HMAC over bleId + epochMs.
Replay sniffed advertiseThe counter is monotonic; on a repeat or a decrease the HMAC does not match.
Replay sniffed fireThe nonce ring invalidates nonces that have been used.
Turn the ESP's clock backThe ratchet: any TIME < ratchet is rejected. The ratchet lives in NVS and survives a power cut.
Use an expired bundleOnce the ESP has been running, the ratchet is at least the last real time. An expired bundle's validUntil is below the ratchet, so the fire fails on TTL.
Move the clock a year forward and lock every guest outA cap of +24 h per guest TIME write. The owner has no cap — the owner is trusted.
Pair as a second owner on top of the firstAfter pairing the ESP stores owner_secret in NVS and never re-enters pairing mode without a factory reset.

What an attacker can do, and what it costs

1. Pushing time forward — denial of service for legitimate guests

Requires: a valid, unexpired guest bundle.

Action: every time in range, write a TIME that jumps 24 hours ahead.

Limit: +24 h per session, and a fresh fire is required if the last sync was under an hour ago. After ten visits the ratchet is ten days ahead, and the other legitimate guests' tokens look expired.

The flip side: the attacker's own token is judged against the same fake time and expires early because of it.

What to do: the owner comes by and writes the real time. The ratchet does not move, since it cannot go back, but new guest tokens are issued with a current validUntil and work for everyone afterwards. Recovery needs nothing more than refreshing validity on the server.

Severity: a temporary denial of service until the owner visits. Not critical.

2. Man in the middle during pairing

Requires: being physically within range of the ESP during the 90-second window after the owner presses the pairing button.

Action: substitute the attacker's own X25519 public key in the pairing handshake.

Result: the ESP pairs with the attacker rather than the owner. The owner believes pairing succeeded but cannot open anything from their phone.

Current defence: only the time window and the owner's physical presence — the owner sees that pairing was not confirmed and repeats it.

Hardening: add a simple verification code — four digits on the ESP's display or in Serial — that the owner confirms in the app. There is no such code today.

Severity: the window is narrow and demands being both nearby and within those 90 seconds. Hard to pull off in practice.

3. Physical factory reset

Requires: physical access to the ESP and a long press of the button.

Action: NVS is wiped, owner_secret is deleted and the ESP returns to pairing mode.

Result: the attacker can pair the ESP to themselves and the owner loses access.

Defence: mount the ESP somewhere protected — inside a box, under a cover.

Severity: the same as tearing an intercom off a door — not a cryptographic problem.

4. Extracting owner_secret over UART or JTAG

Requires: physical access and a flash dump, with secure boot and flash encryption disabled — which they currently are.

Action: read the NVS holding owner_secret.

Result: full compromise of the device and of every guest token.

Defence: enable ESP32 secure boot and flash encryption (efuse, irreversible). Not enabled today.

Severity: acceptable for domestic use. Responsible sites need secure boot.

5. Squatting GATT connections — denial of service

Requires: being within BLE range.

Action: connect and never disconnect. NimBLE supports only a limited number of simultaneous connections.

Result: legitimate clients cannot connect while the attacker holds a slot.

Current defence: only NimBLE's timeouts.

Hardening: add an idle timeout: if a client has not performed a valid FIRE within 5 seconds, drop the connection.

Severity: a temporary denial of service while the attacker is nearby.

6. A flood of invalid writes (connection spoofing)

Action: bombard the ESP with junk writes.

Result: the ESP discards them and logs to Serial. Nothing changes in its state, and once the attacker leaves everything works as usual.

In summary

Standard hardening for industrial use:

  1. Secure boot and flash encryption (protection against JTAG and dumps).
  2. A verification code in the pairing flow (protection against a man in the middle).
  3. An idle timeout on GATT connections (protection against squatting).

None of this is critical for today's scenarios, but responsible sites deserve the hardening.