HF-TMC51x0 Driver (TMC5130 & TMC5160) 0.1.0-dev
Hardware Agnostic C++ Driver for the TMC51x0 (TMC5130 & TMC5160)
Loading...
Searching...
No Matches
espnow_security.hpp
Go to the documentation of this file.
1
21#pragma once
22
23#include <cstdint>
24#include <cstdio>
25#include <cstring>
26#include "esp_random.h"
27#include "mbedtls/md.h"
28
29// ============================================================================
30// PAIRING SECRET CONFIGURATION
31// ============================================================================
32//
33// The pairing secret is injected at build time via CMake.
34// Configuration sources (in priority order):
35// 1. --secret command line argument to build_app.sh
36// 2. ESPNOW_PAIRING_SECRET environment variable
37// 3. secrets.local.yml file (gitignored)
38// 4. Auto-generate for DEBUG builds (with warning)
39// 5. Build error for RELEASE builds
40//
41// See secrets.template.yml for configuration instructions.
42// ============================================================================
43
44#ifndef ESPNOW_PAIRING_SECRET_HEX
45 #if defined(NDEBUG)
46 // RELEASE build without secret - fail with helpful message
47 #error "ESPNOW_PAIRING_SECRET not configured for RELEASE build. " \
48 "Copy secrets.template.yml to secrets.local.yml and add your secret. " \
49 "Generate with: openssl rand -hex 16"
50 #else
51 // DEBUG build - use placeholder with warning
52 #warning "Using auto-generated pairing secret for DEBUG build. NOT SECURE for production!"
53 #define ESPNOW_PAIRING_SECRET_HEX "00000000deadbeefcafebabedeadbeef"
54 #endif
55#endif
56
57// Validate secret length at compile time
58static_assert(sizeof(ESPNOW_PAIRING_SECRET_HEX) == 33,
59 "ESPNOW_PAIRING_SECRET_HEX must be exactly 32 hex characters");
60
68 constexpr uint8_t HexCharToNibble(char c) noexcept {
69 return (c >= '0' && c <= '9') ? static_cast<uint8_t>(c - '0') :
70 (c >= 'a' && c <= 'f') ? static_cast<uint8_t>(c - 'a' + 10) :
71 (c >= 'A' && c <= 'F') ? static_cast<uint8_t>(c - 'A' + 10) : 0;
72 }
73
75 constexpr uint8_t HexByte(const char* s, size_t i) noexcept {
76 return static_cast<uint8_t>(
77 (HexCharToNibble(s[i * 2]) << 4) | HexCharToNibble(s[i * 2 + 1])
78 );
79 }
80} // namespace PairingSecretParser
81
109
111static constexpr size_t CHALLENGE_SIZE = 8;
112
114static constexpr size_t HMAC_SIZE = 16;
115
117static constexpr size_t MAX_APPROVED_PEERS = 4;
118
120static constexpr size_t MAX_DEVICE_NAME_LEN = 16;
121
123static constexpr uint32_t PAIRING_MODE_TIMEOUT_SEC = 30;
124
126static constexpr uint32_t PAIRING_RESPONSE_TIMEOUT_MS = 10000;
127
129static constexpr uint8_t BROADCAST_MAC[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
130
131// ============================================================================
132// DEVICE TYPES
133// ============================================================================
134
140enum class DeviceType : uint8_t {
141 Unknown = 0,
142 RemoteController = 1,
143 FatigueTester = 2,
144 // Future device types can be added here
145};
146
147// ============================================================================
148// PAIRING MESSAGE TYPES (extend existing MsgType enum)
149// ============================================================================
150
157namespace PairingMsgType {
158 static constexpr uint8_t PairingRequest = 20;
159 static constexpr uint8_t PairingResponse = 21;
160 static constexpr uint8_t PairingConfirm = 22;
161 static constexpr uint8_t PairingReject = 23;
162 static constexpr uint8_t Unpair = 24;
163}
164
165// ============================================================================
166// PAIRING REJECTION REASONS
167// ============================================================================
168
169enum class PairingRejectReason : uint8_t {
170 NotInPairingMode = 0,
171 WrongDeviceType = 1,
172 HmacFailed = 2,
173 AlreadyPaired = 3,
174 ProtocolMismatch = 4,
175};
176
177// ============================================================================
178// PAIRING MESSAGE STRUCTURES
179// ============================================================================
180
181#pragma pack(push, 1)
182
196
210
222
227 uint8_t rejecter_mac[6];
228 uint8_t reason;
229};
230
231#pragma pack(pop)
232
233// ============================================================================
234// APPROVED PEER STORAGE STRUCTURES
235// ============================================================================
236
241 uint8_t mac[6];
242 uint8_t device_type;
245 bool valid;
246};
247
254
255// ============================================================================
256// HMAC COMPUTATION FUNCTIONS
257// ============================================================================
258
266inline void ComputePairingHmac(const uint8_t* challenge, size_t challenge_len,
267 uint8_t out[HMAC_SIZE]) noexcept
268{
269 uint8_t full_hmac[32]; // SHA-256 produces 32 bytes
270
271 mbedtls_md_context_t ctx;
272 mbedtls_md_init(&ctx);
273 mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
274 mbedtls_md_hmac_starts(&ctx, PAIRING_SECRET, sizeof(PAIRING_SECRET));
275 mbedtls_md_hmac_update(&ctx, challenge, challenge_len);
276 mbedtls_md_hmac_finish(&ctx, full_hmac);
277 mbedtls_md_free(&ctx);
278
279 // Truncate to HMAC_SIZE bytes for space efficiency
280 std::memcpy(out, full_hmac, HMAC_SIZE);
281}
282
291inline bool VerifyPairingHmac(const uint8_t* challenge, size_t challenge_len,
292 const uint8_t received_hmac[HMAC_SIZE]) noexcept
293{
294 uint8_t expected[HMAC_SIZE];
295 ComputePairingHmac(challenge, challenge_len, expected);
296
297 // Constant-time comparison to prevent timing attacks
298 uint8_t diff = 0;
299 for (size_t i = 0; i < HMAC_SIZE; ++i) {
300 diff |= (expected[i] ^ received_hmac[i]);
301 }
302 return diff == 0;
303}
304
305// ============================================================================
306// HELPER FUNCTIONS
307// ============================================================================
308
314inline void GenerateChallenge(uint8_t out[CHALLENGE_SIZE]) noexcept
315{
316 esp_fill_random(out, CHALLENGE_SIZE);
317}
318
322inline bool IsZeroMac(const uint8_t mac[6]) noexcept
323{
324 for (int i = 0; i < 6; ++i) {
325 if (mac[i] != 0) return false;
326 }
327 return true;
328}
329
333inline bool IsBroadcastMac(const uint8_t mac[6]) noexcept
334{
335 return std::memcmp(mac, BROADCAST_MAC, 6) == 0;
336}
337
341inline bool MacEquals(const uint8_t a[6], const uint8_t b[6]) noexcept
342{
343 return std::memcmp(a, b, 6) == 0;
344}
345
349inline void FormatMac(const uint8_t mac[6], char* out, size_t out_size) noexcept
350{
351 if (out_size >= 18) {
352 snprintf(out, out_size, "%02X:%02X:%02X:%02X:%02X:%02X",
353 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
354 }
355}
356
static constexpr size_t HMAC_SIZE
HMAC output size (truncated SHA-256)
Definition espnow_security.hpp:114
static constexpr size_t CHALLENGE_SIZE
Challenge nonce size in bytes.
Definition espnow_security.hpp:111
static constexpr uint8_t BROADCAST_MAC[6]
Broadcast MAC address for discovery.
Definition espnow_security.hpp:129
static constexpr uint32_t PAIRING_MODE_TIMEOUT_SEC
Pairing mode timeout in seconds (default)
Definition espnow_security.hpp:123
bool IsBroadcastMac(const uint8_t mac[6]) noexcept
Check if a MAC address is the broadcast address.
Definition espnow_security.hpp:333
void FormatMac(const uint8_t mac[6], char *out, size_t out_size) noexcept
Format MAC address to string buffer (needs 18 bytes: XX:XX:XX:XX:XX:XX\0)
Definition espnow_security.hpp:349
bool IsZeroMac(const uint8_t mac[6]) noexcept
Check if a MAC address is all zeros.
Definition espnow_security.hpp:322
PairingRejectReason
Definition espnow_security.hpp:169
@ ProtocolMismatch
Protocol version mismatch.
@ WrongDeviceType
Requester looking for wrong device type.
@ NotInPairingMode
Device not in pairing mode.
@ AlreadyPaired
Already paired (peer list full)
@ HmacFailed
HMAC verification failed.
bool VerifyPairingHmac(const uint8_t *challenge, size_t challenge_len, const uint8_t received_hmac[HMAC_SIZE]) noexcept
Verify an HMAC response using constant-time comparison.
Definition espnow_security.hpp:291
static constexpr uint8_t PAIRING_SECRET[16]
Pre-shared pairing secret (16 bytes).
Definition espnow_security.hpp:91
static constexpr size_t MAX_DEVICE_NAME_LEN
Maximum device name length.
Definition espnow_security.hpp:120
void GenerateChallenge(uint8_t out[CHALLENGE_SIZE]) noexcept
Generate a random challenge nonce.
Definition espnow_security.hpp:314
static constexpr size_t MAX_APPROVED_PEERS
Maximum number of approved peers to store in NVS.
Definition espnow_security.hpp:117
DeviceType
Device type identifiers for mutual verification.
Definition espnow_security.hpp:140
@ FatigueTester
Fatigue test unit.
@ RemoteController
UI board / M5Dial remote controller.
void ComputePairingHmac(const uint8_t *challenge, size_t challenge_len, uint8_t out[HMAC_SIZE]) noexcept
Compute HMAC-SHA256, truncated to HMAC_SIZE bytes.
Definition espnow_security.hpp:266
#define ESPNOW_PAIRING_SECRET_HEX
Definition espnow_security.hpp:53
static constexpr uint32_t PAIRING_RESPONSE_TIMEOUT_MS
Pairing response timeout in milliseconds.
Definition espnow_security.hpp:126
bool MacEquals(const uint8_t a[6], const uint8_t b[6]) noexcept
Compare two MAC addresses.
Definition espnow_security.hpp:341
Security/Pairing message type values.
Definition espnow_security.hpp:157
static constexpr uint8_t Unpair
Remove a paired device.
Definition espnow_security.hpp:162
static constexpr uint8_t PairingConfirm
Final confirmation.
Definition espnow_security.hpp:160
static constexpr uint8_t PairingRequest
Initiate pairing (broadcast)
Definition espnow_security.hpp:158
static constexpr uint8_t PairingReject
Explicit rejection.
Definition espnow_security.hpp:161
static constexpr uint8_t PairingResponse
Response with HMAC proof.
Definition espnow_security.hpp:159
Compile-time hex string to byte array parser.
Definition espnow_security.hpp:66
constexpr uint8_t HexCharToNibble(char c) noexcept
Convert a single hex character to its numeric value (0-15)
Definition espnow_security.hpp:68
constexpr uint8_t HexByte(const char *s, size_t i) noexcept
Convert two hex characters at position i*2 to a single byte.
Definition espnow_security.hpp:75
Information about an approved (paired) peer.
Definition espnow_security.hpp:240
uint8_t mac[6]
Peer's MAC address.
Definition espnow_security.hpp:241
uint8_t device_type
DeviceType enum value.
Definition espnow_security.hpp:242
uint32_t paired_timestamp
Unix timestamp when paired (or 0)
Definition espnow_security.hpp:244
bool valid
true if this slot is in use
Definition espnow_security.hpp:245
char name[MAX_DEVICE_NAME_LEN]
Friendly name.
Definition espnow_security.hpp:243
Pairing confirmation payload - sent by initiator to complete.
Definition espnow_security.hpp:217
uint8_t success
1 = success, 0 = verification failed
Definition espnow_security.hpp:220
uint8_t hmac_response[HMAC_SIZE]
HMAC(secret, responder_challenge)
Definition espnow_security.hpp:219
uint8_t confirmer_mac[6]
Confirmer's MAC address.
Definition espnow_security.hpp:218
Pairing rejection payload.
Definition espnow_security.hpp:226
uint8_t reason
PairingRejectReason.
Definition espnow_security.hpp:228
uint8_t rejecter_mac[6]
Rejecter's MAC address.
Definition espnow_security.hpp:227
Pairing request payload - sent by initiator (broadcast).
Definition espnow_security.hpp:189
uint8_t requester_mac[6]
Requester's MAC address.
Definition espnow_security.hpp:190
uint8_t challenge[CHALLENGE_SIZE]
Random nonce for verification.
Definition espnow_security.hpp:193
uint8_t device_type
DeviceType - what I am.
Definition espnow_security.hpp:191
uint8_t protocol_version
Protocol version for compatibility.
Definition espnow_security.hpp:194
uint8_t expected_peer_type
DeviceType - what I'm looking for.
Definition espnow_security.hpp:192
Pairing response payload - sent by responder (unicast).
Definition espnow_security.hpp:203
uint8_t device_type
DeviceType - what I am.
Definition espnow_security.hpp:205
char device_name[MAX_DEVICE_NAME_LEN]
Human-readable name.
Definition espnow_security.hpp:208
uint8_t hmac_response[HMAC_SIZE]
HMAC(secret, requester_challenge)
Definition espnow_security.hpp:207
uint8_t responder_mac[6]
Responder's MAC address.
Definition espnow_security.hpp:204
uint8_t challenge[CHALLENGE_SIZE]
My challenge for mutual auth.
Definition espnow_security.hpp:206
Security settings stored in NVS.
Definition espnow_security.hpp:251
ApprovedPeer approved_peers[MAX_APPROVED_PEERS]
Definition espnow_security.hpp:252