CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
Fido2Ui.cpp
Go to the documentation of this file.
1
5
6#include "mod_fido2/Fido2Ui.h"
7#include "mod_fido2/ctap2.h"
8#include "mod_fido2/ctaphid.h"
9#include "mod_fido2/fido2.h"
12#include "cdc_core/PinManager.h"
13#include "cdc_core/Raii.h"
14#include "cdc_hal/IDisplay.h"
15#include "cdc_log.h"
16#include "cdc_ui/I18n.h"
17#include "cdc_ui/ViewStack.h"
20#include "cdc_views/InfoView.h"
21#include "cdc_views/ListView.h"
23#include "cdc_views/ToastView.h"
24#include <esp_attr.h>
25#include <freertos/FreeRTOS.h>
26#include <freertos/semphr.h>
27#include <algorithm>
28#include <cstdio>
29#include <cstring>
30#include <strings.h>
31
32static const char* TAG = "FIDO2_UI";
33
34namespace cdc::mod_fido2 {
35
36constexpr ui::I18nEntry kStrings[] = {
37 {"mod_fido2.title", "WebAuthn"},
38 {"mod_fido2.details", "Details"},
39 {"mod_fido2.fido2_key", "FIDO2 Key"},
40 {"mod_fido2.sign_in_to", "Sign in to"},
41 {"mod_fido2.register_key", "Register Key"},
42 {"mod_fido2.sign_in", "Sign In"},
43 {"mod_fido2.use_device", "Use this device?"},
44 {"mod_fido2.no_entries", "No entries"},
45 {"mod_fido2.overwrite_key", "OVERWRITE KEY!"},
46 {"mod_fido2.overwrite_warning", "Overwrite existing key?"},
47};
48
52
54static ui::ListView* s_listView = nullptr;
55static ui::InfoView* s_detailView = nullptr;
56static ui::InfoView* s_promptView = nullptr;
57static ui::PinEntryView* s_pinEntry = nullptr;
58
60EXT_RAM_BSS_ATTR static char s_labels[FIDO2_MAX_CREDENTIALS][100];
62static uint8_t s_listCount = 0;
63
65static SemaphoreHandle_t s_promptSem = nullptr;
66static SemaphoreHandle_t s_promptMutex = nullptr;
70static uint8_t s_promptReturnDepth = 0;
71static ui::IView* s_promptReturnView = nullptr;
72static bool s_promptWasLocked = false;
73static bool s_promptBacklightWasOn = false;
74static volatile bool s_promptActive = false;
75
76static void ensurePromptMutex() {
77 if (!s_promptMutex) {
78 s_promptMutex = xSemaphoreCreateMutex();
79 }
80}
81
83static SemaphoreHandle_t s_overwriteSem = nullptr;
84static volatile bool s_overwriteApproved = false;
85
86static void onOverwriteConfirm(void* /*ud*/) {
88 if (s_overwriteSem) xSemaphoreGive(s_overwriteSem);
89}
90static void onOverwriteCancel(void* /*ud*/) {
91 s_overwriteApproved = false;
92 if (s_overwriteSem) xSemaphoreGive(s_overwriteSem);
93}
94
101static int strcasecmp_safe(const char* a, const char* b) {
102 if (!a && !b) return 0;
103 if (!a) return -1;
104 if (!b) return 1;
105 return strcasecmp(a, b);
106}
107
111static void rebuildList() {
112 s_listCount = 0;
113 uint8_t count = fido2_get_credential_count();
114 if (count == 0) {
115 // Show "No entries" placeholder
116 s_listItems[0].label = ui::tr("mod_fido2.no_entries");
117 s_listItems[0].userData = nullptr;
118 s_listItems[0].icon = 0;
119 s_listItems[0].iconDisabled = true;
120 if (s_listView) {
121 s_listView->init(ui::tr("mod_fido2.title"), s_listItems, 1);
122 s_listView->setHint(ui::tr("core.hint_back"));
123 }
124 return;
125 }
126
127 for (uint8_t i = 0; i < count && i < FIDO2_MAX_CREDENTIALS; i++) {
128 s_sortMap[i] = i;
129 fido2_credential_info_t info = {};
130 if (fido2_get_credential_info(i, &info)) {
131 if (strlen(info.user_name) > 0) {
132 snprintf(s_labels[i], sizeof(s_labels[i]),
133 "%.45s (%.45s)", info.rp_id, info.user_name);
134 } else {
135 snprintf(s_labels[i], sizeof(s_labels[i]),
136 "%.45s", info.rp_id);
137 }
138 } else {
139 s_labels[i][0] = '\0';
140 }
141 }
142
143 std::sort(s_sortMap, s_sortMap + count, [](uint8_t a, uint8_t b) {
144 return strcasecmp_safe(s_labels[a], s_labels[b]) < 0;
145 });
146
147 for (uint8_t display = 0; display < count; display++) {
148 uint8_t store_idx = s_sortMap[display];
149 s_listItems[display].label = s_labels[store_idx];
150 s_listItems[display].userData = reinterpret_cast<void*>(static_cast<uintptr_t>(display));
151 s_listItems[display].icon = 0;
152 s_listItems[display].iconDisabled = false;
153 s_listCount++;
154 }
155
156 if (s_listView) {
157 s_listView->init(ui::tr("mod_fido2.title"), s_listItems, s_listCount);
158 s_listView->setHint(ui::tr("core.hint_list_menu"));
159 }
160}
161
166static void showDetail(uint16_t display_index) {
167 uint8_t count = fido2_get_credential_count();
168 if (display_index >= count) return;
169
170 uint8_t store_index = s_sortMap[display_index];
171 fido2_credential_info_t info = {};
172 if (!fido2_get_credential_info(store_index, &info)) {
173 return;
174 }
175
176 const char* key_type = (strncmp(info.rp_id, "ssh:", 4) == 0) ? "SSH" : "WebAuthn";
177 const char* algo_name = (info.curve == CDC_CURVE_ED25519) ? "Ed25519" : "P-256";
178
179 char fingerprint[KEY_FINGERPRINT_MAX_LEN] = {};
180 uint8_t pubkey[64] = {};
181 size_t pubkey_len = (info.curve == CDC_CURVE_ED25519) ? 32 : 64;
182 if (fido2_storage_get_pubkey(info.slot, pubkey)) {
183 key_fingerprint_from_pubkey(pubkey, pubkey_len, fingerprint, sizeof(fingerprint));
184 } else {
185 strlcpy(fingerprint, "(no key)", sizeof(fingerprint));
186 }
187
188 static char detail_text[384];
189 uint16_t slot_phys = static_cast<uint16_t>(fido2_storage_ecc_start() + info.slot);
190 snprintf(detail_text, sizeof(detail_text),
191 "Relying Party:\n%s\n\n"
192 "Type: %s Algo: %s\n"
193 "User: %s\n"
194 "Slot: %d\n"
195 "Sign count: %lu\n"
196 "Resident: %s\n\n"
197 "Fingerprint:\n%s\n\n"
198 "%s",
199 info.rp_id,
200 key_type, algo_name,
201 strlen(info.user_name) > 0 ? info.user_name : "(none)",
202 slot_phys,
203 info.sign_count,
204 info.resident_key ? ui::tr("core.yes") : ui::tr("core.no"),
205 fingerprint,
206 ui::tr("core.hint_back"));
207
208 if (!s_detailView) {
210 }
211 s_detailView->init(ui::tr("mod_fido2.fido2_key"), detail_text);
213}
214
219static void handleDelete(uint16_t display_index) {
220 uint8_t count = fido2_get_credential_count();
221 if (display_index >= count) return;
222
223 uint8_t store_index = s_sortMap[display_index];
224 fido2_credential_info_t info = {};
225 if (!fido2_get_credential_info(store_index, &info)) {
226 return;
227 }
228
229 if (fido2_delete_credential(info.slot)) {
230 rebuildList();
231 if (s_listView) {
232 s_listView->init(ui::tr("mod_fido2.title"), s_listItems, s_listCount);
233 s_listView->setHint(ui::tr("core.hint_list_menu"));
234 }
235 }
236}
237
243static void onListSelect(uint16_t index, void* userData) {
244 (void)userData;
245 showDetail(index);
246}
247
253static void onListMenu(uint16_t index, void* userData) {
254 (void)userData;
255 if (s_listCount == 0) return;
256
257 static ui::ContextMenuItem items[3];
258 uint16_t sel = s_listView ? s_listView->getSelection() : index;
259 if (sel >= s_listCount) {
260 sel = 0;
261 }
262 items[0] = {ui::tr("mod_fido2.details"), []() { showDetail(s_listView ? s_listView->getSelection() : 0); }};
263 items[1] = {ui::tr("core.delete"), []() { handleDelete(s_listView ? s_listView->getSelection() : 0); }};
264 items[2] = {ui::tr("core.cancel"), []() {}};
265 static char s_contextTitle[FIDO2_RP_ID_MAX_LEN] = {};
266 const uint8_t store_idx = s_sortMap[sel];
267 fido2_credential_info_t info = {};
268 const char* title = s_labels[store_idx];
269 if (fido2_get_credential_info(store_idx, &info)) {
270 strncpy(s_contextTitle, info.rp_id, sizeof(s_contextTitle) - 1);
271 s_contextTitle[sizeof(s_contextTitle) - 1] = '\0';
272 title = s_contextTitle;
273 }
274 showContextMenu(title, items, 3);
275}
276
280static void restoreView() {
281 auto& stack = ui::ViewStack::instance();
282 while (stack.depth() > s_promptReturnDepth) {
283 stack.pop();
284 }
285}
286
292 s_promptActive = false;
293
294 if (result == FIDO2_UP_APPROVED) {
295 ui::showToastSuccess(ui::tr("core.ok"), 2000);
296 } else {
297 ui::showToastError(ui::tr("core.failed"), 2000);
298 }
299
300 auto& stack = ui::ViewStack::instance();
301 stack.releaseExclusive(s_promptView);
302
303 restoreView();
304
305 if (result == FIDO2_UP_APPROVED &&
308 rebuildList();
309 }
310
311 auto* display = cdc::hal::getDisplayInstance();
312 if (display && !s_promptBacklightWasOn) {
313 display->backlightOff();
314 }
315
316 stack.render();
317
318 s_promptResult = result;
319 if (s_promptSem) {
320 xSemaphoreGive(s_promptSem);
321 }
322}
323
329static bool onPinVerify(const char* pin) {
331}
332
336static void onPinSuccess() {
337 auto& stack = ui::ViewStack::instance();
338 stack.releaseExclusive(s_pinEntry);
339 stack.acquireExclusive(s_promptView);
341}
342
346static void onPinCancel() {
347 auto& stack = ui::ViewStack::instance();
348 stack.releaseExclusive(s_pinEntry);
349 stack.acquireExclusive(s_promptView);
351}
352
357static void onPinFailure(bool lockedOut) {
358 if (lockedOut) {
359 ui::showToastError(ui::tr("core.too_many_attempts"), 2000);
360 auto& stack = ui::ViewStack::instance();
361 stack.releaseExclusive(s_pinEntry);
362 stack.acquireExclusive(s_promptView);
364 } else {
365 ui::showToastError(ui::tr("core.wrong_pin"), 1000);
366 }
367}
368
373static void onPromptApprove(void* userData) {
374 (void)userData;
375 if (fido2_is_pin_verified()) {
377 return;
378 }
379
381 if (!s_pinEntry) {
383 s_pinEntry->init(ui::tr("core.enter_pin"),
386 s_pinEntry->setOnVerify(onPinVerify);
387 s_pinEntry->setOnSuccess(onPinSuccess);
388 s_pinEntry->setOnCancel(onPinCancel);
389 s_pinEntry->setOnFailure(onPinFailure);
390 s_pinEntry->setShowMessages(false);
391 } else {
392 s_pinEntry->init(ui::tr("core.enter_pin"),
395 s_pinEntry->setShowMessages(false);
396 }
397 {
398 auto& stack = ui::ViewStack::instance();
399 stack.releaseExclusive(s_promptView);
400 stack.acquireExclusive(s_pinEntry);
401 stack.push(s_pinEntry);
402 }
403 return;
404 }
405
407}
408
413static void onPromptDeny(void* userData) {
414 (void)userData;
417}
418
424 if (!s_listView) {
425 s_listView = new ui::ListView();
426 s_listView->setOnSelect(onListSelect);
427 s_listView->setOnMenu(onListMenu);
428 }
429 rebuildList();
430
431 if (!s_promptSem) {
432 s_promptSem = xSemaphoreCreateBinary();
433 }
434}
435
441 if (!s_listView) {
443 }
444 rebuildList();
445 return s_listView;
446}
447
452const char* fido2_ui_get_label() {
453 return ui::tr("mod_fido2.title");
454}
455
464 const char* rp_id,
465 fido2_action_t action,
466 const char* user_name
467) {
468 (void)user_name;
469
470 const char* actionStr = (action == FIDO2_ACTION_SELECT) ? "SELECT" :
471 (action == FIDO2_ACTION_REGISTER) ? "REGISTER" :
472 (action == FIDO2_ACTION_OVERWRITE) ? "OVERWRITE" : "AUTH";
473 LOG_I(TAG, "User presence: action=%s, rp='%s', promptActive=%d",
474 actionStr, rp_id ? rp_id : "(null)", s_promptActive ? 1 : 0);
475
476 // Browser-discovery probes never touch the UI: they are protocol-only
477 // pings to ask "is a device there?" and must not influence presence state.
478 if (action == FIDO2_ACTION_SELECT && rp_id &&
479 (strcmp(rp_id, ".dummy") == 0 || strcmp(rp_id, "make.me.blink") == 0)) {
480 LOG_I(TAG, "Auto-approving browser probe '%s'", rp_id);
481 return FIDO2_UP_APPROVED;
482 }
483
485
486 if (s_promptActive) {
487 LOG_W(TAG, "Presence request supersedes active prompt -> cancel old");
489 }
490
492
493 if (!s_promptSem) {
494 s_promptSem = xSemaphoreCreateBinary();
495 if (!s_promptSem) {
496 return FIDO2_UP_DENIED;
497 }
498 }
499
500 if (action == FIDO2_ACTION_OVERWRITE) {
501 if (!s_overwriteSem) {
502 s_overwriteSem = xSemaphoreCreateBinary();
503 if (!s_overwriteSem) {
504 return FIDO2_UP_DENIED;
505 }
506 }
507 while (xSemaphoreTake(s_overwriteSem, 0) == pdTRUE) {}
508
509 auto* display = cdc::hal::getDisplayInstance();
510 bool backlightWasOn = display && display->isBacklightOn();
511 if (display && !backlightWasOn) {
512 display->backlightOn();
513 }
514
515 const char* confirm_msg = ui::tr("mod_fido2.overwrite_warning");
516
517 s_overwriteApproved = false;
520
521 const uint32_t cid = ctaphid_get_current_cid();
522 const TickType_t poll = pdMS_TO_TICKS(100);
523 TickType_t remaining = pdMS_TO_TICKS(30000);
524 bool approved = false;
525 bool timedOut = true;
526 while (remaining > 0) {
527 TickType_t wait = remaining < poll ? remaining : poll;
528 if (xSemaphoreTake(s_overwriteSem, wait) == pdTRUE) {
529 approved = s_overwriteApproved;
530 timedOut = false;
531 break;
532 }
534 remaining -= wait;
535 }
536 if (timedOut) {
537 LOG_W(TAG, "Overwrite confirm timeout");
538 }
539
540 if (!approved) {
541 if (display && !backlightWasOn) {
542 display->backlightOff();
543 }
544 LOG_I(TAG, "Overwrite denied/timeout - aborting registration");
545 return FIDO2_UP_DENIED;
546 }
547 LOG_I(TAG, "Overwrite acknowledged, proceeding to user-presence prompt");
548 action = FIDO2_ACTION_REGISTER;
549 }
550
551 strncpy(s_promptRpId, rp_id ? rp_id : "Unknown", sizeof(s_promptRpId) - 1);
552 s_promptRpId[sizeof(s_promptRpId) - 1] = '\0';
553 s_promptAction = action;
555 s_promptActive = true; // Mark prompt as active for race condition detection
556
557 while (xSemaphoreTake(s_promptSem, 0) == pdTRUE) {
558 LOG_W(TAG, "Drained stale semaphore");
559 }
560
561 if (!s_promptView) {
563 }
564
565 auto& stack = ui::ViewStack::instance();
566 if (!stack.acquireExclusive(s_promptView)) {
567 LOG_W(TAG, "Could not acquire ViewStack lock for FIDO2 prompt");
568 s_promptActive = false;
569 return FIDO2_UP_DENIED;
570 }
571 s_promptReturnDepth = stack.depth();
572 s_promptReturnView = stack.current();
574
575 // A WebAuthn request takes priority: dismiss any open modal (e.g. the
576 // lock-screen actions menu), which would otherwise overlay the prompt and
577 // swallow all key input.
578 stack.hideModal();
579
580 auto* display = cdc::hal::getDisplayInstance();
581 if (display) {
582 s_promptBacklightWasOn = display->isBacklightOn();
584 display->backlightOn();
585 }
586 }
587
588 const char* headline = nullptr;
589 if (action == FIDO2_ACTION_SELECT) {
590 headline = ui::tr("mod_fido2.use_device");
591 } else if (action == FIDO2_ACTION_REGISTER) {
592 headline = ui::tr("mod_fido2.register_key");
593 } else if (action == FIDO2_ACTION_OVERWRITE) {
594 headline = ui::tr("mod_fido2.overwrite_key");
595 } else {
596 headline = ui::tr("mod_fido2.sign_in");
597 }
598
599 static char prompt_text[260];
600 if (action == FIDO2_ACTION_SELECT) {
601 snprintf(prompt_text, sizeof(prompt_text),
602 "%s\n\n%s",
603 headline,
604 ui::tr("core.hint_approve_deny"));
605 } else if (action == FIDO2_ACTION_OVERWRITE) {
606 snprintf(prompt_text, sizeof(prompt_text),
607 "!!! %s !!!\n\n%s\n\n%s\n\n%s",
608 headline,
610 ui::tr("mod_fido2.overwrite_warning"),
611 ui::tr("core.hint_approve_deny"));
612 } else {
613 snprintf(prompt_text, sizeof(prompt_text),
614 "%s\n\n%s\n\n%s",
615 headline,
617 ui::tr("core.hint_approve_deny"));
618 }
619
620 const char* title = ui::tr("mod_fido2.title");
621 LOG_I(TAG, "Prompt title='%s', text='%.50s...'", title ? title : "(null)", prompt_text);
622
623 s_promptView->init(title, prompt_text);
624 s_promptView->setYesNoCallbacks(onPromptApprove, onPromptDeny, nullptr);
625
626 stack.push(s_promptView);
627 stack.resetInactivityTimer();
628 stack.render();
629
631
632 const uint32_t up_cid = ctaphid_get_current_cid();
633 const TickType_t up_poll = pdMS_TO_TICKS(100);
634 TickType_t up_remaining = pdMS_TO_TICKS(30000);
635 bool up_done = false;
636 bool up_cancelled = false;
637 while (up_remaining > 0) {
638 TickType_t wait = up_remaining < up_poll ? up_remaining : up_poll;
639 if (xSemaphoreTake(s_promptSem, wait) == pdTRUE) {
640 up_done = true;
641 break;
642 }
643 if (ctap2_is_cancelled()) {
644 up_cancelled = true;
645 break;
646 }
648 up_remaining -= wait;
649 }
650 if (up_done) {
652 s_promptActive = false;
653 if (result == FIDO2_UP_PENDING) {
654 LOG_W(TAG, "Semaphore signalled with PENDING result -> deny");
655 result = FIDO2_UP_DENIED;
656 }
657 return result;
658 }
659
660 if (up_cancelled) {
661 LOG_W(TAG, "User presence cancelled by CTAP host");
662 } else {
663 LOG_W(TAG, "User presence timeout");
664 }
665 s_promptActive = false;
666 stack.releaseExclusive(s_promptView);
667 restoreView();
668
669 if (display && !s_promptBacklightWasOn) {
670 display->backlightOff();
671 }
672
673 stack.render();
674 return up_cancelled ? FIDO2_UP_DENIED : FIDO2_UP_TIMEOUT;
675}
676
678 if (!s_promptActive) {
679 return false;
680 }
681 LOG_W(TAG, "Prompt aborted externally");
683 return true;
684}
685
686} // namespace cdc::mod_fido2
static const char * TAG
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
bool key_fingerprint_from_pubkey(const uint8_t *pubkey, size_t pubkey_len, char *buf, size_t len)
Generates human-readable fingerprint from public key bytes.
#define KEY_FINGERPRINT_MAX_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
RAII wrapper for a FreeRTOS semaphore / mutex.
Definition Raii.h:181
static constexpr uint8_t BADGE_PIN_MAX
Definition PinManager.h:50
bool verifyBadgePin(const char *pin)
Verifies badge PIN, updates retries, and handles lockout transitions.
static constexpr uint8_t BADGE_PIN_MIN
Definition PinManager.h:49
static PinManager & instance()
Returns singleton PIN manager instance.
static I18n & instance()
Singleton accessor.
Definition I18n.cpp:287
void registerEnglishTable(const I18nEntry *entries, std::size_t count)
Append English entries to the lookup table.
Definition I18n.cpp:307
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:34
void push(IView *view, void *context=nullptr)
void ctap2_clear_cancel(void)
Clears the cancel flag. Called when a new CTAPHID channel is opened so a cancel from a previous chann...
Definition ctap2.cpp:3634
bool ctap2_is_cancelled(void)
Returns true if the current CTAP2 operation has been cancelled.
Definition ctap2.cpp:3641
uint32_t ctaphid_get_current_cid(void)
Returns the channel identifier of the currently processed request.
Definition ctaphid.cpp:753
#define CTAPHID_STATUS_UPNEEDED
Definition ctaphid.h:48
void ctaphid_send_keepalive(uint32_t cid, uint8_t status)
Sends a CTAPHID KEEPALIVE packet immediately over USB.
Definition ctaphid.cpp:689
#define CDC_CURVE_ED25519
Definition fido2.h:23
#define FIDO2_MAX_CREDENTIALS
Definition fido2.h:16
void fido2_set_pin_verified(bool verified)
Stores whether PIN verification was completed via ClientPIN.
Definition fido2.cpp:194
#define FIDO2_RP_ID_MAX_LEN
Definition fido2.h:17
bool fido2_get_credential_info(uint8_t index, fido2_credential_info_t *info)
bool fido2_delete_credential(uint8_t slot)
Deletes credential in given slot.
Definition fido2.cpp:257
fido2_user_presence_result_t
Definition fido2.h:30
@ FIDO2_UP_DENIED
Definition fido2.h:33
@ FIDO2_UP_TIMEOUT
Definition fido2.h:34
@ FIDO2_UP_PENDING
Definition fido2.h:31
@ FIDO2_UP_APPROVED
Definition fido2.h:32
bool fido2_is_pin_verified(void)
Returns current PIN-verified state.
Definition fido2.cpp:205
uint8_t fido2_get_credential_count(void)
Returns number of stored credentials.
Definition fido2.cpp:213
fido2_action_t
Definition fido2.h:37
@ FIDO2_ACTION_SELECT
Definition fido2.h:40
@ FIDO2_ACTION_REGISTER
Definition fido2.h:38
@ FIDO2_ACTION_OVERWRITE
Definition fido2.h:41
@ FIDO2_ACTION_AUTHENTICATE
Definition fido2.h:39
char rp_id[FIDO2_RP_ID_MAX_LEN]
char user_name[FIDO2_USER_NAME_MAX_LEN]
uint8_t fido2_storage_ecc_start(void)
Returns configured ECC start slot.
bool fido2_storage_get_pubkey(uint8_t slot, uint8_t *pubkey)
Reads public key from secure-element slot.
IDisplay * getDisplayInstance()
Returns lazily created singleton display instance.
static ui::PinEntryView * s_pinEntry
Definition Fido2Ui.cpp:57
static ui::InfoView * s_detailView
Definition Fido2Ui.cpp:55
static void onPinFailure(bool lockedOut)
PIN failure callback handling lockout vs retry messaging.
Definition Fido2Ui.cpp:357
cdc::ui::IView * fido2_ui_get_list_view()
Returns FIDO2 credential list view.
Definition Fido2Ui.cpp:440
static void onOverwriteConfirm(void *)
Definition Fido2Ui.cpp:86
static ui::ListItem s_listItems[FIDO2_MAX_CREDENTIALS]
Definition Fido2Ui.cpp:59
static bool onPinVerify(const char *pin)
PIN verification callback for locked-screen approval flow.
Definition Fido2Ui.cpp:329
static void ensurePromptMutex()
Definition Fido2Ui.cpp:76
static ui::IView * s_promptReturnView
Definition Fido2Ui.cpp:71
static void onPinCancel()
PIN cancel callback denying user presence.
Definition Fido2Ui.cpp:346
static SemaphoreHandle_t s_promptSem
User-presence prompt state shared across callback and UI flow.
Definition Fido2Ui.cpp:65
static volatile fido2_user_presence_result_t s_promptResult
Definition Fido2Ui.cpp:67
static uint8_t s_listCount
Definition Fido2Ui.cpp:62
static volatile bool s_overwriteApproved
Definition Fido2Ui.cpp:84
static void onListSelect(uint16_t index, void *userData)
List selection callback opening credential detail view.
Definition Fido2Ui.cpp:243
static char s_promptRpId[FIDO2_RP_ID_MAX_LEN]
Definition Fido2Ui.cpp:68
static void handleDelete(uint16_t display_index)
Deletes selected credential and refreshes list.
Definition Fido2Ui.cpp:219
static bool s_promptBacklightWasOn
Definition Fido2Ui.cpp:73
bool fido2_ui_abort_prompt()
Forcibly denies any in-flight user-presence prompt.
Definition Fido2Ui.cpp:677
static SemaphoreHandle_t s_overwriteSem
Pre-confirm modal state for overwrite warning.
Definition Fido2Ui.cpp:83
void fido2_ui_init()
Initializes FIDO2 UI resources and list views.
Definition Fido2Ui.cpp:422
static void rebuildList()
Rebuilds credential list view from current storage contents.
Definition Fido2Ui.cpp:111
static void showDetail(uint16_t display_index)
Shows detailed view for selected credential.
Definition Fido2Ui.cpp:166
constexpr ui::I18nEntry kStrings[]
Definition Fido2Ui.cpp:36
static void promptComplete(fido2_user_presence_result_t result)
Completes user-presence prompt flow with result handling.
Definition Fido2Ui.cpp:291
static void onPromptApprove(void *userData)
Prompt approve callback; optionally triggers PIN entry on lock screen.
Definition Fido2Ui.cpp:373
static void onOverwriteCancel(void *)
Definition Fido2Ui.cpp:90
static uint8_t s_promptReturnDepth
Definition Fido2Ui.cpp:70
static ui::ListView * s_listView
FIDO2 UI view and list state.
Definition Fido2Ui.cpp:54
static int strcasecmp_safe(const char *a, const char *b)
Null-safe ASCII case-insensitive comparison.
Definition Fido2Ui.cpp:101
static void onListMenu(uint16_t index, void *userData)
List menu callback opening context actions for selected credential.
Definition Fido2Ui.cpp:253
static SemaphoreHandle_t s_promptMutex
Definition Fido2Ui.cpp:66
static volatile bool s_promptActive
Definition Fido2Ui.cpp:74
static void registerStrings()
Definition Fido2Ui.cpp:49
static void onPromptDeny(void *userData)
Prompt deny callback.
Definition Fido2Ui.cpp:413
static fido2_action_t s_promptAction
Definition Fido2Ui.cpp:69
static uint8_t s_sortMap[FIDO2_MAX_CREDENTIALS]
Definition Fido2Ui.cpp:61
static char s_labels[FIDO2_MAX_CREDENTIALS][100]
Definition Fido2Ui.cpp:60
static ui::InfoView * s_promptView
Definition Fido2Ui.cpp:56
fido2_user_presence_result_t fido2_ui_user_presence_callback(const char *rp_id, fido2_action_t action, const char *user_name)
User-presence callback used by FIDO2 core for approval prompts.
Definition Fido2Ui.cpp:463
static bool s_promptWasLocked
Definition Fido2Ui.cpp:72
static void onPinSuccess()
PIN success callback approving user presence.
Definition Fido2Ui.cpp:336
const char * fido2_ui_get_label()
Returns localized module label for menus.
Definition Fido2Ui.cpp:452
static void restoreView()
Restores view stack to pre-prompt depth.
Definition Fido2Ui.cpp:280
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.
void showToastSuccess(const char *message, uint16_t durationMs=1500)
Shows a success toast message.
void showToastError(const char *message, uint16_t durationMs=1500)
Shows an error toast message.
Single English translation entry.
Definition I18n.h:44