onlyn00bs-badge

OnlyN00bs: a DEF CON 34 friend-finder badge. ESP32 firmware, Web Bluetooth setup app, printable case
git clone https://git.virtualshack.io/onlyn00bs-badge.git
Log | Files | Refs | README | LICENSE

beacon.h (3478B)


      1 #pragma once
      2 #include <Arduino.h>
      3 #include <string.h>
      4 #include <stddef.h>
      5 #include "mbedtls/md.h"      // mbedTLS ships inside the ESP32 Arduino core
      6 #include "config.h"
      7 #include "pet.h"             // PET_FACE_MAX — beacons carry the sender's pet face
      8 
      9 // ─────────────────────────────────────────────────────────────────────────
     10 //  Beacon wire format (fixed size, broadcast over ESP-NOW ~1/sec).
     11 //
     12 //  The signed region is everything before `tag`. The receiver recomputes
     13 //  HMAC-SHA256(PSK, signed_region) and constant-time-compares against `tag`.
     14 //  Unverified beacons are dropped before they ever touch the peer table.
     15 //
     16 //  `counter` is a freshness hint (replay deterrent). With a symmetric group
     17 //  key the real guarantee is "this came from a holder of our key" — which is
     18 //  exactly the friends-vs-the-con threat model. Upgrade path to per-badge
     19 //  Ed25519 identities is noted in the README.
     20 // ─────────────────────────────────────────────────────────────────────────
     21 
     22 #define BEACON_MAGIC    0xB1
     23 #define BEACON_VERSION  2           // v2: added pet + pet_face (v1 badges won't verify)
     24 #define TAG_LEN         16          // HMAC-SHA256 truncated to 128 bits
     25 
     26 #pragma pack(push, 1)
     27 struct Beacon {
     28   uint8_t  magic;                   // BEACON_MAGIC — cheap pre-filter
     29   uint8_t  version;                 // BEACON_VERSION
     30   uint32_t counter;                 // monotonic-ish freshness hint
     31   uint8_t  handle_len;              // 1..HANDLE_MAX_LEN
     32   char     handle[HANDLE_MAX_LEN];  // zero-padded; padding is part of the MAC
     33   uint8_t  pet;                     // sender's pet index (0..PET_BUILTIN_N; ==N means custom)
     34   char     pet_face[PET_FACE_MAX];  // sender's pet idle face, zero-padded (renders any pet)
     35   uint8_t  tag[TAG_LEN];            // signature over the bytes above
     36 };
     37 #pragma pack(pop)
     38 
     39 // Bytes covered by the signature = everything up to (not including) `tag`.
     40 static inline size_t beacon_signed_len() { return offsetof(Beacon, tag); }
     41 
     42 static inline void beacon_hmac(const Beacon* b, uint8_t out[32]) {
     43   const mbedtls_md_info_t* info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
     44   mbedtls_md_hmac(info, GROUP_PSK, GROUP_PSK_LEN,
     45                   (const uint8_t*)b, beacon_signed_len(), out);
     46 }
     47 
     48 // Fill `tag` for an outgoing beacon. Caller must zero-pad `handle` first.
     49 static inline void beacon_sign(Beacon* b) {
     50   uint8_t full[32];
     51   beacon_hmac(b, full);
     52   memcpy(b->tag, full, TAG_LEN);
     53 }
     54 
     55 // Constant-time compare — don't leak the tag via early-exit timing.
     56 static inline bool ct_equal(const uint8_t* a, const uint8_t* b, size_t n) {
     57   uint8_t diff = 0;
     58   for (size_t i = 0; i < n; i++) diff |= a[i] ^ b[i];
     59   return diff == 0;
     60 }
     61 
     62 // Validate structure + signature. `len` is the received byte count.
     63 static inline bool beacon_verify(const Beacon* b, size_t len) {
     64   if (len < sizeof(Beacon))                       return false;  // runt
     65   if (b->magic   != BEACON_MAGIC)                 return false;
     66   if (b->version != BEACON_VERSION)               return false;
     67   if (b->handle_len == 0 || b->handle_len > HANDLE_MAX_LEN) return false;
     68   uint8_t full[32];
     69   beacon_hmac(b, full);
     70   return ct_equal(full, b->tag, TAG_LEN);
     71 }