Skip to content

UC-009: Allow-List Sync

Actor: System (ESP32) Priority: Must Status: Implemented (protocol definition)

Summary

The ESP32 periodically syncs with the backend to fetch the latest allow-list and report queued events.

Preconditions

  • ESP32 has network credentials stored in NVS (WiFi SSID/password or Ethernet configured)
  • Backend is reachable via HTTPS
  • ESP32 has the Ed25519 public key for signature verification
  • 5-minute sync interval has elapsed since last sync

Main Flow

  1. Sync timer fires (every 5 minutes)
  2. ESP32 pauses BLE (NUKI temporarily unreachable)
  3. ESP32 connects to the network
  4. ESP32 sends GET /api/v1/device/allowlist?current_version=N
  5. Backend responds with 200 + signed allow-list payload (if newer version exists)
  6. ESP32 verifies Ed25519 signature over header + entries
  7. ESP32 checks version is strictly greater than current version (replay protection)
  8. ESP32 stores new allow-list in LittleFS and updates RAM cache
  9. ESP32 sends queued events: POST /api/v1/device/events
  10. ESP32 sends heartbeat: POST /api/v1/device/heartbeat
  11. ESP32 disconnects from the network, resumes BLE (WiFi only)

Alternative Flows

A1: No update available (304)

  1. At step 5, backend responds with 304 Not Modified
  2. ESP32 skips steps 6–8, proceeds to send events and heartbeat
  3. Network disconnects (WiFi only), BLE resumes

A2: First boot (no existing allow-list)

  1. ESP32 has no stored allow-list
  2. Sends GET /api/v1/device/allowlist?current_version=0
  3. Backend always responds with the latest allow-list
  4. Normal verification and storage proceeds

Error Flows

E1: Network connection fails

  1. At step 3, network connection times out (10 seconds)
  2. ESP32 keeps the existing allow-list (if any)
  3. BLE resumes, retry at next sync interval
  4. All access decisions use the cached allow-list

E2: Signature verification fails

  1. At step 6, Ed25519 signature does not match
  2. New allow-list is rejected
  3. Existing allow-list remains active
  4. Error event logged for next successful sync

E3: Version rollback detected

  1. At step 7, new version <= current version
  2. Payload rejected (possible replay attack)
  3. Existing allow-list remains active

E4: Backend unreachable

  1. HTTP request fails or times out
  2. Same as E1 — cached allow-list continues to work
  3. If allow-list has been stale for >24 hours, SCHEDULED entries start being denied

Postconditions

  • ESP32 has the latest allow-list (or keeps the previous valid one)
  • Queued events have been delivered to the backend
  • Backend knows the device is alive (heartbeat)

Access Rule

  • N/A (system process, not tied to an NFC card)

Notes

  • The network connection is only active for ~5 seconds per sync cycle. When using WiFi, BLE (NUKI) is unavailable during this window. With Ethernet, BLE remains available at all times.
  • The 5-minute interval is configurable via SYNC_INTERVAL_MS in config.h.
  • If the allow-list becomes stale (valid_until passed), UNRESTRICTED and EMERGENCY entries still work, but SCHEDULED and CONDITIONAL entries are denied for safety.
  • The monotonic version number prevents replay attacks — an attacker cannot feed an older list to the device.