CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
ble_chalresp.cpp
Go to the documentation of this file.
2#include "mod_2fa/OathStore.h"
4#include "cdc_core/Raii.h"
6#include "cdc_ui/I18n.h"
8#include "cdc_log.h"
9#include "esp_attr.h"
10#include "freertos/FreeRTOS.h"
11#include "freertos/semphr.h"
12#include <cstring>
13#include <cstdio>
14
15using namespace cdc::hal;
16
17static const char* TAG = "2FA_CR_BLE";
18
19namespace cdc::mod_2fa {
20
24static const uint8_t CR_SVC_UUID[16] = {
25 0x01, 0x1A, 0x8B, 0x6A, 0x9D, 0x4C, 0x6E, 0x9A,
26 0x7A, 0x4D, 0x5D, 0x8B, 0x30, 0x1F, 0x2F, 0x8E
27};
28
32static const uint8_t CR_CHALLENGE_UUID[16] = {
33 0x01, 0x1A, 0x8B, 0x6A, 0x9D, 0x4C, 0x6E, 0x9A,
34 0x7A, 0x4D, 0x5D, 0x8B, 0x31, 0x1F, 0x2F, 0x8E
35};
36
40static const uint8_t CR_RESPONSE_UUID[16] = {
41 0x01, 0x1A, 0x8B, 0x6A, 0x9D, 0x4C, 0x6E, 0x9A,
42 0x7A, 0x4D, 0x5D, 0x8B, 0x32, 0x1F, 0x2F, 0x8E
43};
44
45static constexpr uint16_t INVALID_HANDLE = 0xFFFF;
46
48static constexpr size_t MAX_NAME_LEN = 16;
49static constexpr size_t MAX_CHALLENGE_LEN = 128;
50static constexpr size_t MAX_FRAME_LEN = MAX_NAME_LEN + 1 + MAX_CHALLENGE_LEN;
51
52static bool s_initialized = false;
53
54static uint16_t s_challenge_handle = 0;
55static uint16_t s_response_handle = 0;
56
59
61
62static SemaphoreHandle_t s_mutex = nullptr;
63
71 bool valid = false;
73 char name[MAX_NAME_LEN + 1] = {};
74 size_t challengeLen = 0;
75};
76EXT_RAM_BSS_ATTR static uint8_t s_challengeBuf[MAX_CHALLENGE_LEN] = {};
78
89
97
104static void notifyResponse(uint16_t connHandle, const uint8_t* data, size_t len) {
105 auto* ble = getBle();
106 if (!ble || s_response_handle == 0 || connHandle == INVALID_HANDLE) return;
107 ble->sendNotification(connHandle, s_response_handle, data, static_cast<uint16_t>(len));
108}
109
117 if (!s_confirm.awaiting) return;
118 notifyResponse(s_confirm.connHandle, s_confirm.response, s_confirm.responseLen);
119 LOG_I(TAG, "CR response notified (%u bytes)", static_cast<unsigned>(s_confirm.responseLen));
120 s_confirm = {};
121}
122
127static void onTouchConfirm(void* userData) {
128 (void)userData;
130}
131
136static void onTouchCancel(void* userData) {
137 (void)userData;
139 LOG_W(TAG, "CR request declined by user");
140 s_confirm = {};
141}
142
154static int onChallengeWrite(uint16_t connHandle, uint16_t, const uint8_t* data, uint16_t len) {
155 if (!data || len == 0 || len > MAX_FRAME_LEN) return 0;
156
157 // Split the frame into a NUL-terminated name prefix and the challenge tail.
158 size_t nameLen = 0;
159 while (nameLen < len && data[nameLen] != '\0') nameLen++;
160 if (nameLen == 0 || nameLen > MAX_NAME_LEN || nameLen >= len) {
161 LOG_W(TAG, "Malformed CR frame");
162 return 0;
163 }
164 const uint8_t* challenge = data + nameLen + 1;
165 size_t challengeLen = len - nameLen - 1;
166 if (challengeLen > MAX_CHALLENGE_LEN) return 0;
167
169 if (s_pending.valid || s_confirm.awaiting) {
170 LOG_W(TAG, "CR request already in progress");
171 return 0;
172 }
173 memcpy(s_pending.name, data, nameLen);
174 s_pending.name[nameLen] = '\0';
175 memcpy(s_challengeBuf, challenge, challengeLen);
176 s_pending.challengeLen = challengeLen;
177 s_pending.connHandle = connHandle;
178 s_pending.valid = true;
179 return 0;
180}
181
186static bool registerGattService() {
187 auto* ble = getBle();
188 if (!ble) return false;
189
191 s_gattChars[0].properties = GattProp::WRITE;
192 s_gattChars[0].permissions = GattPerm::WRITE;
193 s_gattChars[0].valueHandle = &s_challenge_handle;
194 s_gattChars[0].onWrite = onChallengeWrite;
195
198 s_gattChars[1].permissions = GattPerm::READ;
199 s_gattChars[1].valueHandle = &s_response_handle;
200 s_gattChars[1].onRead = [](uint16_t, uint16_t, uint8_t* buf, uint16_t* len) -> int {
201 // The response is delivered by notification; a plain read returns empty.
202 if (buf && len) *len = 0;
203 return 0;
204 };
205
207 s_gattSvcDef.characteristics = s_gattChars;
208 s_gattSvcDef.numCharacteristics = 2;
209
210 if (!ble->registerGattService(s_gattSvcDef)) {
211 LOG_E(TAG, "Failed to register CR GATT service");
212 return false;
213 }
214 LOG_I(TAG, "CR GATT service registered");
215 return true;
216}
217
223static void onDisconnect(uint16_t connHandle, int reason) {
224 (void)reason;
226 if (s_pending.valid && s_pending.connHandle == connHandle) {
227 s_pending = {};
228 }
229 if (s_confirm.awaiting && s_confirm.connHandle == connHandle) {
230 s_confirm = {};
231 }
232}
233
239 if (s_initialized) return true;
240
241 if (!s_mutex) {
242 s_mutex = xSemaphoreCreateMutex();
243 if (!s_mutex) {
244 LOG_E(TAG, "Failed to create mutex");
245 return false;
246 }
247 }
248
249 auto* ble = getBle();
250 if (!ble) {
251 LOG_E(TAG, "Bluetooth controller not available");
252 return false;
253 }
254
255 if (!ble->isEnabled()) {
256 if (!ble->enable()) {
257 LOG_E(TAG, "Bluetooth enable failed");
258 return false;
259 }
260 }
261
262 for (int i = 0; i < 50 && !ble->isEnabled(); i++) {
263 vTaskDelay(pdMS_TO_TICKS(100));
264 }
265
266 if (!registerGattService()) {
267 return false;
268 }
269
270 s_tokDisconn = ble->addDisconnectionCallback(onDisconnect);
271
272 s_initialized = true;
273 LOG_I(TAG, "BLE CR service initialized");
274 return true;
275}
276
281 auto* ble = getBle();
282 if (ble) {
283 ble->unregisterGattService(BleUuid::from128(CR_SVC_UUID));
284 ble->removeDisconnectionCallback(s_tokDisconn);
285 }
287
289 s_pending = {};
290 s_confirm = {};
291 s_initialized = false;
292}
293
298void ble_chalresp_tick(uint32_t nowMs) {
299 (void)nowMs;
300 if (!s_initialized) return;
301
302 char name[MAX_NAME_LEN + 1] = {};
303 uint8_t challenge[MAX_CHALLENGE_LEN] = {};
304 size_t challengeLen = 0;
305 uint16_t connHandle = INVALID_HANDLE;
306 {
308 if (!s_pending.valid || s_confirm.awaiting) return;
309 strncpy(name, s_pending.name, sizeof(name) - 1);
310 challengeLen = s_pending.challengeLen;
311 memcpy(challenge, s_challengeBuf, challengeLen);
312 connHandle = s_pending.connHandle;
313 s_pending.valid = false;
314 }
315
316 // Compute via the store directly: it lives in this module and is the only
317 // path that also reports the per-entry touch flag in a single lookup. The
318 // IChallengeResponder service is the contract for external transports.
319 uint8_t response[core::IChallengeResponder::MAX_RESPONSE_LEN] = {};
320 bool touchRequired = true;
321 int rc = OathStore::instance().challengeResponse(name, challenge, challengeLen,
322 response, &touchRequired);
323 if (rc <= 0) {
324 LOG_W(TAG, "CR compute failed for '%s'", name);
325 return;
326 }
327
328 {
330 s_confirm.awaiting = true;
331 s_confirm.connHandle = connHandle;
332 memcpy(s_confirm.response, response, static_cast<size_t>(rc));
333 s_confirm.responseLen = static_cast<size_t>(rc);
334 }
335
336 if (touchRequired) {
337 ui::showConfirm(ui::tr("mod_2fa.cr_confirm"), onTouchConfirm, onTouchCancel,
339 } else {
341 }
342}
343
344} // namespace cdc::mod_2fa
static const char * TAG
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
Shared RAII wrappers for firmware resources.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
static constexpr ListenerToken INVALID_LISTENER
static constexpr size_t MAX_RESPONSE_LEN
Largest possible raw HMAC response (SHA256). Callers size out to this.
RAII wrapper for a FreeRTOS semaphore / mutex.
Definition Raii.h:181
int challengeResponse(const char *entryName, const uint8_t *challenge, size_t clen, uint8_t *out, bool *touchRequiredOut=nullptr)
Computes the raw HMAC challenge-response for a CR entry by name.
static OathStore & instance()
Returns singleton OATH store instance.
constexpr uint8_t WRITE
constexpr uint8_t READ
constexpr uint8_t NOTIFY
constexpr uint8_t READ
constexpr uint8_t WRITE
IBluetoothController * getBluetoothControllerInstance()
Returns singleton Bluetooth stub when NimBLE is unavailable.
static const uint8_t CR_RESPONSE_UUID[16]
Response characteristic UUID (...1F32...), read + notify.
static GattCharacteristic s_gattChars[2]
static void onTouchConfirm(void *userData)
Touch-confirm accepted: notify the prepared response.
static void deliverConfirmedResponse()
Computes and notifies the response stashed in s_confirm.
static constexpr size_t MAX_CHALLENGE_LEN
static const uint8_t CR_CHALLENGE_UUID[16]
Challenge characteristic UUID (...1F31...), write.
void ble_chalresp_deinit()
Tears down the BLE CR subsystem and removes GATT callbacks.
static SemaphoreHandle_t s_mutex
static PendingChallenge s_pending
static constexpr size_t MAX_NAME_LEN
Max challenge payload (entry name + NUL + challenge bytes) accepted per write.
bool ble_chalresp_init()
BLE GATT challenge-response transport.
static IBluetoothController * getBle()
Returns shared Bluetooth controller instance.
static constexpr size_t MAX_FRAME_LEN
static void onDisconnect(uint16_t connHandle, int reason)
Clears pending request state if the requesting peer disconnects.
static IBluetoothController::ListenerToken s_tokDisconn
static uint8_t s_challengeBuf[MAX_CHALLENGE_LEN]
static bool s_initialized
static uint16_t s_response_handle
static const uint8_t CR_SVC_UUID[16]
CR service UUID: 8E2F1F30-8B5D-4D7A-9A6E-4C9D6A8B1A01 (little-endian).
static uint16_t s_challenge_handle
static void notifyResponse(uint16_t connHandle, const uint8_t *data, size_t len)
Sends a response notification to the requesting connection.
static bool registerGattService()
Registers the CR GATT service and characteristics.
static int onChallengeWrite(uint16_t connHandle, uint16_t, const uint8_t *data, uint16_t len)
GATT challenge write callback (BLE host task).
static PendingConfirm s_confirm
static void onTouchCancel(void *userData)
Touch-confirm declined: discard the prepared response.
static GattServiceDef s_gattSvcDef
static constexpr uint16_t INVALID_HANDLE
void ble_chalresp_tick(uint32_t nowMs)
Main-task tick: processes a pending challenge (confirm + notify).
const char * tr(const char *key)
Look up a translation by string key.
Definition I18n.h:208
void showConfirm(const char *message, ConfirmView::ConfirmCallback onConfirm, ConfirmView::CancelCallback onCancel=nullptr, ConfirmView::Icon icon=ConfirmView::Icon::QUESTION, void *userData=nullptr)
Shows a shared modal confirmation dialog instance.
static BleUuid from128(const uint8_t v[16])
Pending challenge handed from the BLE host task to the main task.
Response awaiting an on-device touch confirmation.
uint8_t response[core::IChallengeResponder::MAX_RESPONSE_LEN]