FreeRDP
Loading...
Searching...
No Matches
rdpewa_fido.c
1
19#include <winpr/sysinfo.h>
20
21#include <freerdp/config.h>
22
23#include <string.h>
24#include <stdlib.h>
25
26#include <fido.h>
27#include <fido/credman.h>
28#include <cbor.h>
29
30#include <winpr/assert.h>
31#include <winpr/endian.h>
32#include <winpr/wlog.h>
33
34#include <freerdp/channels/log.h>
35#include <freerdp/channels/rdpewa.h>
36#include <freerdp/event.h>
37
38#include "rdpewa_cbor.h"
39#include "rdpewa_fido.h"
40#include <rdpewa-common.h>
41
42#include <winpr/thread.h>
43#include <winpr/synch.h>
44
45#define TAG CHANNELS_TAG("rdpewa.client")
46
48#define RDPEWA_MAX_DEVICES 64
49
51typedef struct
52{
53 fido_dev_t* dev;
54 fido_cred_t* cred;
55 fido_assert_t* assert;
56 const char* pin;
57 int result;
58} RDPEWA_FIDO_ASYNC;
59
60static bool rdpewa_fido_get_pin(rdpContext* context, char** pin)
61{
62 WINPR_ASSERT(pin);
63 *pin = nullptr;
64
65 freerdp* instance = context->instance;
66 char* u = nullptr;
67 char* p = nullptr;
68 char* d = nullptr;
69 const bool rc = instance && instance->AuthenticateEx &&
70 instance->AuthenticateEx(instance, &u, &p, &d, AUTH_FIDO_PIN) && p;
71 free(u);
72 free(d);
73 if (!rc)
74 {
75 winpr_zfree(p);
76 return false;
77 }
78
79 *pin = p;
80 return true;
81}
82
83static bool rdpewa_fido_uv_failed(int result)
84{
85 return (result == FIDO_ERR_UV_BLOCKED) || (result == FIDO_ERR_UV_INVALID);
86}
87
88static DWORD WINAPI rdpewa_fido_makecred_thread(LPVOID arg)
89{
90 RDPEWA_FIDO_ASYNC* a = (RDPEWA_FIDO_ASYNC*)arg;
91 a->result = fido_dev_make_cred(a->dev, a->cred, a->pin);
92 return 0;
93}
94
95static DWORD WINAPI rdpewa_fido_getassert_thread(LPVOID arg)
96{
97 RDPEWA_FIDO_ASYNC* a = (RDPEWA_FIDO_ASYNC*)arg;
98 a->result = fido_dev_get_assert(a->dev, a->assert, a->pin);
99 return 0;
100}
101
102static bool notify(rdpContext* context, UINT64 id, bool cancel)
103{
104 /* Fire-and-forget notification to the UI via PubSub */
105 UserNotificationEventArgs e = WINPR_C_ARRAY_INIT;
106 EventArgsInit(&e, RDPEWA_CHANNEL_NAME);
107 if (!cancel)
108 {
109 e.timeoutMS = 30000;
110 e.message = "Touch the security key";
111 }
112 e.cancelPreviousNotification = cancel;
113
114 e.messageID = id;
115 const int rc = PubSub_OnUserNotification(context->pubSub, context, &e);
116 return (rc >= 0);
117}
118
119static bool notifyWait(rdpContext* context, HANDLE ft, fido_dev_t* dev)
120{
121 if (!ft)
122 return false;
123
124 HANDLE hdl[] = { ft, freerdp_abort_event(context) };
125 const UINT64 id = winpr_GetTickCount64NS();
126 (void)notify(context, id, false);
127 const DWORD status = WaitForMultipleObjects(ARRAYSIZE(hdl), hdl, FALSE, INFINITE);
128 CloseHandle(ft);
129 (void)notify(context, id, true);
130
131 const bool rc = (status == WAIT_OBJECT_0);
132 if (!rc)
133 fido_dev_cancel(dev);
134 return rc;
135}
141static int rdpewa_fido_select_device(rdpContext* context, fido_dev_t** devs, size_t ndevs,
142 int timeout_sec)
143{
144 if (ndevs == 1)
145 return 0; /* only one device, no selection needed */
146
147 /* Start touch request on all devices */
148 for (size_t i = 0; i < ndevs; i++)
149 {
150 if (devs[i])
151 {
152 int r = fido_dev_get_touch_begin(devs[i]);
153 if (r != FIDO_OK)
154 WLog_DBG(TAG, "Device %" PRIuz ": fido_dev_get_touch_begin: %s", i, fido_strerr(r));
155 }
156 }
157
158 const UINT64 id = winpr_GetTickCount64NS();
159 (void)notify(context, id, false);
160
161 /* Poll for touch with 200ms intervals */
162 int selected = -1;
163 int iterations = timeout_sec * 5; /* 200ms * 5 = 1 second */
164 for (int iter = 0; iter < iterations && selected < 0; iter++)
165 {
166 for (size_t i = 0; i < ndevs; i++)
167 {
168 if (!devs[i])
169 continue;
170
171 int touched = 0;
172 int r = fido_dev_get_touch_status(devs[i], &touched, 200);
173 if (r == FIDO_OK && touched)
174 {
175 WLog_DBG(TAG, "Device %" PRIuz " was touched", i);
176 selected = WINPR_ASSERTING_INT_CAST(int, i);
177 break;
178 }
179 }
180 }
181
182 (void)notify(context, id, true);
183 return selected;
184}
185
189static void rdpewa_fido_fill_device_info(fido_dev_t* dev, const char* path,
190 const char* manufacturer, const char* product,
191 RDPEWA_DEVICE_INFO* info)
192{
193 WINPR_ASSERT(dev);
194 WINPR_ASSERT(info);
195
196 *info = (RDPEWA_DEVICE_INFO){ .maxMsgSize = 1200,
197 .maxSerializedLargeBlobArray = 1024,
198 .providerType = "Hid",
199 .providerName = "FreeRDPFidoProvider",
200 .uvStatus = fido_dev_has_uv(dev) ? 1 : 0,
201 .uvRetries = 3 };
202
203 if (path)
204 strncpy(info->devicePath, path, sizeof(info->devicePath) - 1);
205 if (manufacturer && manufacturer[0])
206 strncpy(info->manufacturer, manufacturer, sizeof(info->manufacturer) - 1);
207 if (product && product[0])
208 strncpy(info->product, product, sizeof(info->product) - 1);
209
210 fido_cbor_info_t* ci = fido_cbor_info_new();
211 if (ci && fido_dev_get_cbor_info(dev, ci) == FIDO_OK)
212 {
213 uint64_t msgsz = fido_cbor_info_maxmsgsiz(ci);
214 if (msgsz > 0 && msgsz <= UINT32_MAX)
215 info->maxMsgSize = (UINT32)msgsz;
216
217 uint64_t lblob = fido_cbor_info_maxlargeblob(ci);
218 if (lblob > 0 && lblob <= UINT32_MAX)
219 info->maxSerializedLargeBlobArray = (UINT32)lblob;
220
221 const unsigned char* aaguid = fido_cbor_info_aaguid_ptr(ci);
222 size_t aaguidLen = fido_cbor_info_aaguid_len(ci);
223 if (aaguid && aaguidLen == 16)
224 memcpy(info->aaGuid, aaguid, 16);
225
226 size_t ntransports = fido_cbor_info_transports_len(ci);
227 char** transports = fido_cbor_info_transports_ptr(ci);
228 for (size_t i = 0; i < ntransports; i++)
229 {
230 if (!transports[i])
231 continue;
232
233 if (strcmp(transports[i], "usb") == 0)
234 info->transports |= 1; /* USB */
235 else if (strcmp(transports[i], "nfc") == 0)
236 info->transports |= 2; /* NFC */
237 else if (strcmp(transports[i], "ble") == 0)
238 info->transports |= 4; /* BLE */
239 }
240 }
241 if (ci)
242 fido_cbor_info_free(&ci);
243}
244
251static fido_dev_t* rdpewa_fido_open_device(RDPEWA_DEVICE_INFO* devInfo, int devIndex,
252 size_t* ndevsOut)
253{
254 WLog_DBG(TAG, "Enumerating FIDO2 devices...");
255
256 fido_dev_t* dev = nullptr;
257 fido_dev_info_t* devlist = fido_dev_info_new(RDPEWA_MAX_DEVICES);
258 if (!devlist)
259 return nullptr;
260
261 size_t ndevs = 0;
262 int r = fido_dev_info_manifest(devlist, RDPEWA_MAX_DEVICES, &ndevs);
263 if (r != FIDO_OK || ndevs == 0)
264 {
265 WLog_WARN(TAG, "No FIDO2 authenticators found (r=%d, ndevs=%" PRIuz ")", r, ndevs);
266 fido_dev_info_free(&devlist, RDPEWA_MAX_DEVICES);
267 return nullptr;
268 }
269
270 if (ndevsOut)
271 *ndevsOut = ndevs;
272
273 size_t idx = (devIndex >= 0 && (size_t)devIndex < ndevs) ? (size_t)devIndex : 0;
274 const fido_dev_info_t* di = fido_dev_info_ptr(devlist, idx);
275 const char* path = fido_dev_info_path(di);
276
277 WLog_DBG(TAG, "Found %" PRIuz " FIDO2 device(s), opening device %" PRIuz ": '%s'", ndevs, idx,
278 path);
279
280 /* Get pointers to device info strings. These remain valid until fido_dev_info_free(). */
281 const char* mfr = fido_dev_info_manufacturer_string(di);
282 const char* prod = fido_dev_info_product_string(di);
283
284 dev = fido_dev_new();
285 if (!dev)
286 {
287 fido_dev_info_free(&devlist, RDPEWA_MAX_DEVICES);
288 return nullptr;
289 }
290
291 r = fido_dev_open(dev, path);
292 if (r != FIDO_OK)
293 {
294 WLog_ERR(TAG, "Failed to open FIDO2 device '%s': %s", path, fido_strerr(r));
295 fido_dev_free(&dev);
296 fido_dev_info_free(&devlist, RDPEWA_MAX_DEVICES);
297 return nullptr;
298 }
299
300 if (devInfo)
301 rdpewa_fido_fill_device_info(dev, path, mfr, prod, devInfo);
302
303 fido_dev_info_free(&devlist, RDPEWA_MAX_DEVICES);
304 return dev;
305}
306
312WINPR_ATTR_MALLOC(Stream_Free, 1)
313static wStream* rdpewa_fido_make_credential(rdpContext* context, const BYTE* ctapData,
314 size_t ctapLen, WINPR_ATTR_UNUSED UINT32 flags)
315{
316 fido_dev_t* dev = nullptr;
317 RDPEWA_DEVICE_INFO devInfo = WINPR_C_ARRAY_INIT;
318 fido_cred_t* cred = nullptr;
319 wStream* ret = nullptr;
320 bool uv = false;
321
322 WINPR_ASSERT(ctapData);
323
324 struct cbor_load_result result = WINPR_C_ARRAY_INIT;
325 cbor_item_t* root = cbor_load(ctapData, ctapLen, &result);
326 if (!root || !cbor_isa_map(root))
327 {
328 WLog_ERR(TAG, "Invalid CTAP MakeCredential CBOR data");
329 goto out;
330 }
331
332 cred = fido_cred_new();
333 if (!cred)
334 goto out;
335
336 /* Default algorithm as fallback, overridden by pubKeyCredParams below if present */
337 fido_cred_set_type(cred, COSE_ES256);
338
339 const size_t mapSize = cbor_map_size(root);
340 struct cbor_pair* pairs = cbor_map_handle(root);
341
342 for (size_t i = 0; i < mapSize; i++)
343 {
344 if (!cbor_isa_uint(pairs[i].key))
345 continue;
346
347 uint64_t k = cbor_get_int(pairs[i].key);
348 cbor_item_t* v = pairs[i].value;
349
350 switch (k)
351 {
352 case CTAP_MAKECRED_CLIENT_DATA_HASH:
353 if (cbor_isa_bytestring(v))
354 fido_cred_set_clientdata_hash(cred, cbor_bytestring_handle(v),
355 cbor_bytestring_length(v));
356 break;
357
358 case CTAP_MAKECRED_RP:
359 if (cbor_isa_map(v))
360 {
361 char* rpId = nullptr;
362 char* rpName = nullptr;
363 const size_t rpSize = cbor_map_size(v);
364 struct cbor_pair* rpPairs = cbor_map_handle(v);
365
366 for (size_t j = 0; j < rpSize; j++)
367 {
368 if (!cbor_isa_string(rpPairs[j].key))
369 continue;
370
371 const char* rk = (const char*)cbor_string_handle(rpPairs[j].key);
372 size_t rkl = cbor_string_length(rpPairs[j].key);
373 if (rkl == 2 && memcmp(rk, "id", 2) == 0 &&
374 cbor_isa_string(rpPairs[j].value))
375 {
376 free(rpId);
377 rpId = strndup((const char*)cbor_string_handle(rpPairs[j].value),
378 cbor_string_length(rpPairs[j].value));
379 }
380 else if (rkl == 4 && memcmp(rk, "name", 4) == 0 &&
381 cbor_isa_string(rpPairs[j].value))
382 {
383 free(rpName);
384 rpName = strndup((const char*)cbor_string_handle(rpPairs[j].value),
385 cbor_string_length(rpPairs[j].value));
386 }
387 }
388
389 if (rpId)
390 fido_cred_set_rp(cred, rpId, rpName);
391
392 free(rpId);
393 free(rpName);
394 }
395 break;
396
397 case CTAP_MAKECRED_USER:
398 if (cbor_isa_map(v))
399 {
400 const unsigned char* userId = nullptr;
401 size_t userIdLen = 0;
402 char* userName = nullptr;
403 char* userDisplayName = nullptr;
404 const size_t uSize = cbor_map_size(v);
405 struct cbor_pair* uPairs = cbor_map_handle(v);
406
407 for (size_t j = 0; j < uSize; j++)
408 {
409 if (!cbor_isa_string(uPairs[j].key))
410 continue;
411
412 const char* uk = (const char*)cbor_string_handle(uPairs[j].key);
413 size_t ukl = cbor_string_length(uPairs[j].key);
414 if (ukl == 2 && memcmp(uk, "id", 2) == 0 &&
415 cbor_isa_bytestring(uPairs[j].value))
416 {
417 userId = cbor_bytestring_handle(uPairs[j].value);
418 userIdLen = cbor_bytestring_length(uPairs[j].value);
419 }
420 else if (ukl == 4 && memcmp(uk, "name", 4) == 0 &&
421 cbor_isa_string(uPairs[j].value))
422 {
423 free(userName);
424 userName = strndup((const char*)cbor_string_handle(uPairs[j].value),
425 cbor_string_length(uPairs[j].value));
426 }
427 else if (ukl == 11 && memcmp(uk, "displayName", 11) == 0 &&
428 cbor_isa_string(uPairs[j].value))
429 {
430 free(userDisplayName);
431 userDisplayName =
432 strndup((const char*)cbor_string_handle(uPairs[j].value),
433 cbor_string_length(uPairs[j].value));
434 }
435 }
436
437 if (userId && userIdLen > 0)
438 fido_cred_set_user(cred, userId, userIdLen, userName, userDisplayName,
439 nullptr);
440
441 free(userName);
442 free(userDisplayName);
443 }
444 break;
445
446 case CTAP_MAKECRED_PUB_KEY_CRED_PARAMS:
447 if (cbor_isa_array(v) && cbor_array_size(v) > 0)
448 {
449 cbor_item_t* first = cbor_array_get(v, 0);
450
451 if (first && cbor_isa_map(first))
452 {
453 const size_t pSize = cbor_map_size(first);
454 struct cbor_pair* pPairs = cbor_map_handle(first);
455
456 for (size_t j = 0; j < pSize; j++)
457 {
458 if (!cbor_isa_string(pPairs[j].key))
459 continue;
460
461 const char* pk = (const char*)cbor_string_handle(pPairs[j].key);
462 if (cbor_string_length(pPairs[j].key) == 3 &&
463 memcmp(pk, "alg", 3) == 0 && cbor_is_int(pPairs[j].value))
464 {
465 int alg =
466 WINPR_ASSERTING_INT_CAST(int, cbor_get_int(pPairs[j].value));
467 if (cbor_isa_negint(pPairs[j].value))
468 alg = -(alg + 1);
469 fido_cred_set_type(cred, alg);
470 }
471 }
472 }
473
474 if (first)
475 cbor_decref(&first);
476 }
477 break;
478
479 case CTAP_MAKECRED_EXCLUDE_LIST:
480 if (cbor_isa_array(v))
481 {
482 for (size_t j = 0; j < cbor_array_size(v); j++)
483 {
484 cbor_item_t* credDesc = cbor_array_get(v, j);
485 if (credDesc && cbor_isa_map(credDesc))
486 {
487 const size_t cdSize = cbor_map_size(credDesc);
488 struct cbor_pair* cdPairs = cbor_map_handle(credDesc);
489
490 for (size_t m = 0; m < cdSize; m++)
491 {
492 if (!cbor_isa_string(cdPairs[m].key))
493 continue;
494
495 const char* ck = (const char*)cbor_string_handle(cdPairs[m].key);
496 if (cbor_string_length(cdPairs[m].key) == 2 &&
497 memcmp(ck, "id", 2) == 0 &&
498 cbor_isa_bytestring(cdPairs[m].value))
499 {
500 fido_cred_exclude(cred,
501 cbor_bytestring_handle(cdPairs[m].value),
502 cbor_bytestring_length(cdPairs[m].value));
503 }
504 }
505 }
506
507 if (credDesc)
508 cbor_decref(&credDesc);
509 }
510 }
511 break;
512
513 case CTAP_MAKECRED_OPTIONS:
514 if (cbor_isa_map(v))
515 {
516 const size_t oSize = cbor_map_size(v);
517 struct cbor_pair* oPairs = cbor_map_handle(v);
518
519 for (size_t j = 0; j < oSize; j++)
520 {
521 if (!cbor_isa_string(oPairs[j].key))
522 continue;
523
524 const char* ok = (const char*)cbor_string_handle(oPairs[j].key);
525 size_t okl = cbor_string_length(oPairs[j].key);
526 if (!cbor_isa_float_ctrl(oPairs[j].value))
527 continue;
528
529 if (okl == 2 && memcmp(ok, "rk", 2) == 0)
530 fido_cred_set_rk(cred, cbor_get_bool(oPairs[j].value) ? FIDO_OPT_TRUE
531 : FIDO_OPT_FALSE);
532 else if (okl == 2 && memcmp(ok, "uv", 2) == 0)
533 {
534 uv = cbor_get_bool(oPairs[j].value);
535 fido_cred_set_uv(cred, uv ? FIDO_OPT_TRUE : FIDO_OPT_FALSE);
536 }
537 }
538 }
539 break;
540
541 default:
542 break;
543 }
544 }
545
546 /* Open all devices, let the user select by touch, then do the operation */
547 size_t ndevs = 0;
548 dev = rdpewa_fido_open_device(&devInfo, 0, &ndevs);
549 if (!dev)
550 {
551 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
552 goto out;
553 }
554
555 int r = FIDO_ERR_INTERNAL;
556 if (ndevs > 1)
557 {
558 /* Multiple devices: open all and let user select by touch. */
559 fido_dev_t** devs = (fido_dev_t**)calloc(ndevs, sizeof(*devs));
560 RDPEWA_DEVICE_INFO* devInfos = calloc(ndevs, sizeof(*devInfos));
561 if (!devs || !devInfos)
562 {
563 free((void*)devs);
564 free(devInfos);
565 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
566 goto out;
567 }
568 devs[0] = dev;
569 devInfos[0] = devInfo;
570
571 for (size_t i = 1; i < ndevs; i++)
572 devs[i] = rdpewa_fido_open_device(&devInfos[i], (int)i, nullptr);
573
574 int sel = rdpewa_fido_select_device(context, devs, ndevs, 30);
575 if (sel >= 0)
576 {
577 dev = devs[sel];
578 devInfo = devInfos[sel];
579 }
580 else
581 dev = nullptr;
582
583 /* Cancel and close non-selected devices */
584 for (size_t i = 0; i < ndevs; i++)
585 {
586 if (devs[i] && (int)i != sel)
587 {
588 fido_dev_cancel(devs[i]);
589 fido_dev_close(devs[i]);
590 fido_dev_free(&devs[i]);
591 }
592 }
593
594 free((void*)devs);
595 free(devInfos);
596
597 if (dev == nullptr)
598 {
599 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
600 goto out;
601 }
602 }
603
604 {
605 char* pin = nullptr;
606 const bool useUv = uv && fido_dev_has_uv(dev);
607 if (fido_dev_has_pin(dev) && !useUv && !rdpewa_fido_get_pin(context, &pin))
608 {
609 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
610 goto out;
611 }
612
613 /* Run fido in background (key starts blinking), notify via PubSub */
614 RDPEWA_FIDO_ASYNC fta = { dev, cred, nullptr, pin, FIDO_ERR_INTERNAL };
615 HANDLE ft = CreateThread(nullptr, 0, rdpewa_fido_makecred_thread, &fta, 0, nullptr);
616 if (!notifyWait(context, ft, dev))
617 {
618 winpr_zfree(pin);
619 goto out;
620 }
621
622 r = fta.result;
623 winpr_zfree(pin);
624
625 if (useUv && rdpewa_fido_uv_failed(r) && fido_dev_has_pin(dev))
626 {
627 if (!rdpewa_fido_get_pin(context, &pin))
628 {
629 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
630 goto out;
631 }
632
633 fta.pin = pin;
634 fta.result = FIDO_ERR_INTERNAL;
635 ft = CreateThread(nullptr, 0, rdpewa_fido_makecred_thread, &fta, 0, nullptr);
636 if (!notifyWait(context, ft, dev))
637 {
638 winpr_zfree(pin);
639 goto out;
640 }
641 r = fta.result;
642 winpr_zfree(pin);
643 }
644 }
645
646 if (r != FIDO_OK)
647 {
648 WLog_ERR(TAG, "fido_dev_make_cred failed: %s", fido_strerr(r));
649 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
650 goto out;
651 }
652
653 /* Encode the CTAP authenticator response (authData + fmt + attStmt) as CBOR.
654 * Use raw data from libfido2 to preserve exact authenticator output. */
655 cbor_item_t* respMap = cbor_new_definite_map(3);
656 if (!respMap)
657 goto out;
658
659 /* key 1: fmt */
660 cbor_item_t* fmtKey = cbor_build_uint8(1);
661 const char* fmt = fido_cred_fmt(cred);
662 cbor_item_t* fmtVal = cbor_build_string(fmt ? fmt : "none");
663 if (!cbor_map_add(respMap, (struct cbor_pair){ .key = fmtKey, .value = fmtVal }))
664 {
665 cbor_decref(&fmtKey);
666 cbor_decref(&fmtVal);
667 cbor_decref(&respMap);
668 goto out;
669 }
670 cbor_decref(&fmtKey);
671 cbor_decref(&fmtVal);
672
673 /* key 2: authData (raw bytes to preserve signature validity) */
674 cbor_item_t* adKey = cbor_build_uint8(2);
675 cbor_item_t* adVal =
676 cbor_build_bytestring(fido_cred_authdata_raw_ptr(cred), fido_cred_authdata_raw_len(cred));
677 if (!cbor_map_add(respMap, (struct cbor_pair){ .key = adKey, .value = adVal }))
678 {
679 cbor_decref(&adKey);
680 cbor_decref(&adVal);
681 cbor_decref(&respMap);
682 goto out;
683 }
684 cbor_decref(&adKey);
685 cbor_decref(&adVal);
686
687 /* key 3: attStmt, use raw attestation statement CBOR from authenticator.
688 * This preserves alg, sig, AND x5c certificate chain for packed attestation. */
689 const unsigned char* rawAttStmt = fido_cred_attstmt_ptr(cred);
690 size_t rawAttStmtLen = fido_cred_attstmt_len(cred);
691 if (rawAttStmt && rawAttStmtLen > 0)
692 {
693 /* The raw attstmt is already a CBOR-encoded map. Parse and re-attach it. */
694 struct cbor_load_result attResult = WINPR_C_ARRAY_INIT;
695 cbor_item_t* asKey = cbor_build_uint8(3);
696 cbor_item_t* asVal = cbor_load(rawAttStmt, rawAttStmtLen, &attResult);
697 if (!asVal || attResult.error.code != CBOR_ERR_NONE)
698 {
699 WLog_ERR(TAG, "Failed to parse raw attStmt CBOR");
700 if (asVal)
701 cbor_decref(&asVal);
702 cbor_decref(&asKey);
703 cbor_decref(&respMap);
704 goto out;
705 }
706
707 if (!cbor_map_add(respMap, (struct cbor_pair){ .key = asKey, .value = asVal }))
708 {
709 cbor_decref(&asKey);
710 cbor_decref(&asVal);
711 cbor_decref(&respMap);
712 goto out;
713 }
714
715 cbor_decref(&asKey);
716 cbor_decref(&asVal);
717 }
718 else
719 {
720 /* No attestation statement - use empty map */
721 cbor_item_t* asKey = cbor_build_uint8(3);
722 cbor_item_t* asVal = cbor_new_definite_map(0);
723 if (!cbor_map_add(respMap, (struct cbor_pair){ .key = asKey, .value = asVal }))
724 {
725 cbor_decref(&asKey);
726 cbor_decref(&asVal);
727 cbor_decref(&respMap);
728 goto out;
729 }
730
731 cbor_decref(&asKey);
732 cbor_decref(&asVal);
733 }
734
735 unsigned char* ctapBuf = nullptr;
736 size_t ctapBufLen = 0;
737 ctapBufLen = cbor_serialize_alloc(respMap, &ctapBuf, &ctapBufLen);
738 cbor_decref(&respMap);
739
740 if (ctapBufLen == 0 || !ctapBuf)
741 goto out;
742
743 ret = rdpewa_cbor_encode_webauthn_response(S_OK, 0x00, ctapBuf, ctapBufLen, &devInfo);
744 free(ctapBuf);
745
746out:
747 if (root)
748 cbor_decref(&root);
749 if (cred)
750 fido_cred_free(&cred);
751 if (dev)
752 {
753 fido_dev_close(dev);
754 fido_dev_free(&dev);
755 }
756 return ret;
757}
758
762WINPR_ATTR_MALLOC(Stream_Free, 1)
763static wStream* rdpewa_fido_get_assertion(rdpContext* context, const BYTE* ctapData, size_t ctapLen,
764 WINPR_ATTR_UNUSED UINT32 flags)
765{
766 fido_dev_t* dev = nullptr;
767 RDPEWA_DEVICE_INFO devInfo = WINPR_C_ARRAY_INIT;
768 fido_assert_t* assert = nullptr;
769 wStream* ret = nullptr;
770 bool uv = false;
771
772 WINPR_ASSERT(ctapData);
773
774 struct cbor_load_result result = WINPR_C_ARRAY_INIT;
775 cbor_item_t* root = cbor_load(ctapData, ctapLen, &result);
776 if (!root || !cbor_isa_map(root))
777 {
778 WLog_ERR(TAG, "Invalid CTAP GetAssertion CBOR data");
779 goto out;
780 }
781
782 assert = fido_assert_new();
783 if (!assert)
784 goto out;
785
786 const size_t mapSize = cbor_map_size(root);
787 struct cbor_pair* pairs = cbor_map_handle(root);
788
789 for (size_t i = 0; i < mapSize; i++)
790 {
791 if (!cbor_isa_uint(pairs[i].key))
792 continue;
793
794 uint64_t k = cbor_get_int(pairs[i].key);
795 cbor_item_t* v = pairs[i].value;
796
797 switch (k)
798 {
799 case CTAP_GETASSERT_RP_ID:
800 if (cbor_isa_string(v))
801 {
802 char* rpId = strndup((const char*)cbor_string_handle(v), cbor_string_length(v));
803 if (rpId)
804 {
805 fido_assert_set_rp(assert, rpId);
806 free(rpId);
807 }
808 }
809 break;
810
811 case CTAP_GETASSERT_CLIENT_DATA_HASH:
812 if (cbor_isa_bytestring(v))
813 fido_assert_set_clientdata_hash(assert, cbor_bytestring_handle(v),
814 cbor_bytestring_length(v));
815 break;
816
817 case CTAP_GETASSERT_ALLOW_LIST:
818 if (cbor_isa_array(v))
819 {
820 for (size_t j = 0; j < cbor_array_size(v); j++)
821 {
822 cbor_item_t* credDesc = cbor_array_get(v, j);
823 if (credDesc && cbor_isa_map(credDesc))
824 {
825 const size_t cdSize = cbor_map_size(credDesc);
826 struct cbor_pair* cdPairs = cbor_map_handle(credDesc);
827
828 for (size_t m = 0; m < cdSize; m++)
829 {
830 if (!cbor_isa_string(cdPairs[m].key))
831 continue;
832
833 const char* ck = (const char*)cbor_string_handle(cdPairs[m].key);
834 if (cbor_string_length(cdPairs[m].key) == 2 &&
835 memcmp(ck, "id", 2) == 0 &&
836 cbor_isa_bytestring(cdPairs[m].value))
837 {
838 fido_assert_allow_cred(
839 assert, cbor_bytestring_handle(cdPairs[m].value),
840 cbor_bytestring_length(cdPairs[m].value));
841 }
842 }
843 }
844
845 if (credDesc)
846 cbor_decref(&credDesc);
847 }
848 }
849 break;
850
851 case CTAP_GETASSERT_OPTIONS:
852 if (cbor_isa_map(v))
853 {
854 const size_t oSize = cbor_map_size(v);
855 struct cbor_pair* oPairs = cbor_map_handle(v);
856
857 for (size_t j = 0; j < oSize; j++)
858 {
859 if (!cbor_isa_string(oPairs[j].key))
860 continue;
861
862 const char* ok = (const char*)cbor_string_handle(oPairs[j].key);
863 size_t okl = cbor_string_length(oPairs[j].key);
864 if (!cbor_isa_float_ctrl(oPairs[j].value))
865 continue;
866
867 if (okl == 2 && memcmp(ok, "up", 2) == 0)
868 fido_assert_set_up(assert, cbor_get_bool(oPairs[j].value)
869 ? FIDO_OPT_TRUE
870 : FIDO_OPT_FALSE);
871 else if (okl == 2 && memcmp(ok, "uv", 2) == 0)
872 {
873 uv = cbor_get_bool(oPairs[j].value);
874 fido_assert_set_uv(assert, uv ? FIDO_OPT_TRUE : FIDO_OPT_FALSE);
875 }
876 }
877 }
878 break;
879
880 default:
881 break;
882 }
883 }
884
885 /* Open all devices, let the user select by touch, then do the operation */
886 size_t ndevs = 0;
887 int r = FIDO_ERR_NO_CREDENTIALS;
888 dev = rdpewa_fido_open_device(&devInfo, 0, &ndevs);
889 if (!dev)
890 {
891 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
892 goto out;
893 }
894
895 if (ndevs > 1)
896 {
897 fido_dev_t** devs = (fido_dev_t**)calloc(ndevs, sizeof(*devs));
898 RDPEWA_DEVICE_INFO* devInfos = calloc(ndevs, sizeof(*devInfos));
899 if (!devs || !devInfos)
900 {
901 free((void*)devs);
902 free(devInfos);
903 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
904 goto out;
905 }
906
907 devs[0] = dev;
908 devInfos[0] = devInfo;
909 for (size_t i = 1; i < ndevs; i++)
910 devs[i] = rdpewa_fido_open_device(&devInfos[i], (int)i, nullptr);
911
912 int sel = rdpewa_fido_select_device(context, devs, ndevs, 30);
913 if (sel >= 0)
914 {
915 dev = devs[sel];
916 devInfo = devInfos[sel];
917 }
918 else
919 dev = nullptr;
920
921 /* Cancel and close non-selected devices */
922 for (size_t i = 0; i < ndevs; i++)
923 {
924 if (devs[i] && (int)i != sel)
925 {
926 fido_dev_cancel(devs[i]);
927 fido_dev_close(devs[i]);
928 fido_dev_free(&devs[i]);
929 }
930 }
931
932 free((void*)devs);
933 free(devInfos);
934
935 if (dev == nullptr)
936 {
937 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
938 goto out;
939 }
940 }
941
942 {
943 char* pin = nullptr;
944 const bool useUv = uv && fido_dev_has_uv(dev);
945 if (fido_dev_has_pin(dev) && !useUv && !rdpewa_fido_get_pin(context, &pin))
946 {
947 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
948 goto out;
949 }
950
951 /* Run fido in background (key starts blinking), show prompt on this thread */
952 RDPEWA_FIDO_ASYNC fta = { dev, nullptr, assert, pin, FIDO_ERR_INTERNAL };
953 HANDLE ft = CreateThread(nullptr, 0, rdpewa_fido_getassert_thread, &fta, 0, nullptr);
954 if (!notifyWait(context, ft, dev))
955 {
956 winpr_zfree(pin);
957 goto out;
958 }
959
960 r = fta.result;
961 winpr_zfree(pin);
962
963 if (useUv && rdpewa_fido_uv_failed(r) && fido_dev_has_pin(dev))
964 {
965 if (!rdpewa_fido_get_pin(context, &pin))
966 {
967 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
968 goto out;
969 }
970
971 fta.pin = pin;
972 fta.result = FIDO_ERR_INTERNAL;
973 ft = CreateThread(nullptr, 0, rdpewa_fido_getassert_thread, &fta, 0, nullptr);
974 if (!notifyWait(context, ft, dev))
975 {
976 winpr_zfree(pin);
977 goto out;
978 }
979 r = fta.result;
980 winpr_zfree(pin);
981 }
982 }
983
984 if (r != FIDO_OK)
985 {
986 WLog_ERR(TAG, "fido_dev_get_assert failed: %s", fido_strerr(r));
987 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
988 goto out;
989 }
990
991 if (fido_assert_count(assert) == 0)
992 {
993 ret = rdpewa_cbor_encode_webauthn_response(E_FAIL, 0x01, nullptr, 0, &devInfo);
994 goto out;
995 }
996
997 /* Encode CTAP assertion response as CBOR map with integer keys per FIDO CTAP section 6.2 */
998 size_t nkeys = 3; /* credential(1) + authData(2) + signature(3) */
999 if (fido_assert_user_id_len(assert, 0) > 0)
1000 nkeys = 4; /* + user(4) */
1001 cbor_item_t* respMap = cbor_new_definite_map(nkeys);
1002 if (!respMap)
1003 goto out;
1004
1005 /* key 1: credential {id, type} */
1006 if (fido_assert_id_len(assert, 0) > 0)
1007 {
1008 cbor_item_t* credKey = cbor_build_uint8(1);
1009 cbor_item_t* credMap = cbor_new_definite_map(2);
1010 cbor_item_t* cidKey = cbor_build_string("id");
1011 cbor_item_t* cidVal =
1012 cbor_build_bytestring(fido_assert_id_ptr(assert, 0), fido_assert_id_len(assert, 0));
1013 cbor_item_t* ctypeKey = cbor_build_string("type");
1014 cbor_item_t* ctypeVal = cbor_build_string("public-key");
1015
1016 if (!cbor_map_add(credMap, (struct cbor_pair){ .key = cidKey, .value = cidVal }) ||
1017 !cbor_map_add(credMap, (struct cbor_pair){ .key = ctypeKey, .value = ctypeVal }) ||
1018 !cbor_map_add(respMap, (struct cbor_pair){ .key = credKey, .value = credMap }))
1019 {
1020 cbor_decref(&cidKey);
1021 cbor_decref(&cidVal);
1022 cbor_decref(&ctypeKey);
1023 cbor_decref(&ctypeVal);
1024 cbor_decref(&credKey);
1025 cbor_decref(&credMap);
1026 cbor_decref(&respMap);
1027 goto out;
1028 }
1029
1030 cbor_decref(&cidKey);
1031 cbor_decref(&cidVal);
1032 cbor_decref(&ctypeKey);
1033 cbor_decref(&ctypeVal);
1034 cbor_decref(&credKey);
1035 cbor_decref(&credMap);
1036 }
1037
1038 /* key 2: authData (raw) */
1039 cbor_item_t* adKey = cbor_build_uint8(2);
1040 cbor_item_t* adVal = cbor_build_bytestring(fido_assert_authdata_raw_ptr(assert, 0),
1041 fido_assert_authdata_raw_len(assert, 0));
1042 if (!cbor_map_add(respMap, (struct cbor_pair){ .key = adKey, .value = adVal }))
1043 {
1044 cbor_decref(&adKey);
1045 cbor_decref(&adVal);
1046 cbor_decref(&respMap);
1047 goto out;
1048 }
1049 cbor_decref(&adKey);
1050 cbor_decref(&adVal);
1051
1052 /* key 3: signature */
1053 cbor_item_t* sigKey = cbor_build_uint8(3);
1054 cbor_item_t* sigVal =
1055 cbor_build_bytestring(fido_assert_sig_ptr(assert, 0), fido_assert_sig_len(assert, 0));
1056 if (!cbor_map_add(respMap, (struct cbor_pair){ .key = sigKey, .value = sigVal }))
1057 {
1058 cbor_decref(&sigKey);
1059 cbor_decref(&sigVal);
1060 cbor_decref(&respMap);
1061 goto out;
1062 }
1063 cbor_decref(&sigKey);
1064 cbor_decref(&sigVal);
1065
1066 /* key 4: user -- id only */
1067 if (fido_assert_user_id_len(assert, 0) > 0)
1068 {
1069 cbor_item_t* uKey = cbor_build_uint8(4);
1070 cbor_item_t* uMap = cbor_new_definite_map(1);
1071 cbor_item_t* uidKey = cbor_build_string("id");
1072 cbor_item_t* uidVal = cbor_build_bytestring(fido_assert_user_id_ptr(assert, 0),
1073 fido_assert_user_id_len(assert, 0));
1074
1075 if (!cbor_map_add(uMap, (struct cbor_pair){ .key = uidKey, .value = uidVal }))
1076 {
1077 cbor_decref(&uidKey);
1078 cbor_decref(&uidVal);
1079 cbor_decref(&uKey);
1080 cbor_decref(&uMap);
1081 cbor_decref(&respMap);
1082 goto out;
1083 }
1084 cbor_decref(&uidKey);
1085 cbor_decref(&uidVal);
1086
1087 if (!cbor_map_add(respMap, (struct cbor_pair){ .key = uKey, .value = uMap }))
1088 {
1089 cbor_decref(&uKey);
1090 cbor_decref(&uMap);
1091 cbor_decref(&respMap);
1092 goto out;
1093 }
1094 cbor_decref(&uKey);
1095 cbor_decref(&uMap);
1096 }
1097
1098 unsigned char* ctapBuf = nullptr;
1099 size_t ctapBufLen = 0;
1100 ctapBufLen = cbor_serialize_alloc(respMap, &ctapBuf, &ctapBufLen);
1101 cbor_decref(&respMap);
1102
1103 if (ctapBufLen == 0 || !ctapBuf)
1104 goto out;
1105
1106 ret = rdpewa_cbor_encode_webauthn_response(S_OK, 0x00, ctapBuf, ctapBufLen, &devInfo);
1107 free(ctapBuf);
1108
1109out:
1110 if (root)
1111 cbor_decref(&root);
1112 if (assert)
1113 fido_assert_free(&assert);
1114 if (dev)
1115 {
1116 fido_dev_close(dev);
1117 fido_dev_free(&dev);
1118 }
1119 return ret;
1120}
1121
1122wStream* rdpewa_fido_webauthn(rdpContext* context, const RDPEWA_REQUEST* request)
1123{
1124 WINPR_ASSERT(request);
1125
1126 if (!request->request || request->requestLen < 1)
1127 {
1128 WLog_ERR(TAG, "Empty request payload for WEB_AUTHN command");
1129 return nullptr;
1130 }
1131
1132 BYTE subCommand = request->request[0];
1133 const BYTE* ctapData = request->request + 1;
1134 size_t ctapLen = request->requestLen - 1;
1135
1136 WLog_DBG(TAG, "WebAuthn sub-command 0x%02" PRIx8 " ctapLen=%" PRIuz, subCommand, ctapLen);
1137
1138 switch (subCommand)
1139 {
1140 case CTAPCBOR_CMD_MAKE_CREDENTIAL:
1141 return rdpewa_fido_make_credential(context, ctapData, ctapLen, request->flags);
1142
1143 case CTAPCBOR_CMD_GET_ASSERTION:
1144 return rdpewa_fido_get_assertion(context, ctapData, ctapLen, request->flags);
1145
1146 default:
1147 WLog_ERR(TAG, "Unknown WebAuthn sub-command 0x%02" PRIx8, subCommand);
1148 return nullptr;
1149 }
1150}
1151
1152wStream* rdpewa_fido_is_uvpaa(void)
1153{
1154 WLog_DBG(TAG, "IUVPAA: checking for platform authenticators");
1155
1156 fido_dev_info_t* devlist = fido_dev_info_new(RDPEWA_MAX_DEVICES);
1157 if (!devlist)
1158 return rdpewa_cbor_encode_simple_response(S_OK, 0);
1159
1160 size_t ndevs = 0;
1161 int r = fido_dev_info_manifest(devlist, RDPEWA_MAX_DEVICES, &ndevs);
1162 fido_dev_info_free(&devlist, RDPEWA_MAX_DEVICES);
1163
1164 /* Report TRUE if at least one authenticator is found */
1165 UINT32 available = (r == FIDO_OK && ndevs > 0) ? 1 : 0;
1166 WLog_DBG(TAG, "IUVPAA: ndevs=%" PRIuz " available=%" PRIu32, ndevs, available);
1167 return rdpewa_cbor_encode_simple_response(S_OK, available);
1168}
1169
1170wStream* rdpewa_fido_api_version(void)
1171{
1172 WLog_DBG(TAG, "API_VERSION: reporting version 4");
1173
1174 /* Report API version 4 (consistent with Windows 11 / Server 2025) */
1175 return rdpewa_cbor_encode_simple_response(S_OK, 4);
1176}
1177
1178wStream* rdpewa_fido_get_authenticator_list(void)
1179{
1180 WLog_DBG(TAG, "GET_AUTHENTICATOR_LIST: enumerating authenticators");
1181
1182 fido_dev_info_t* devlist = fido_dev_info_new(RDPEWA_MAX_DEVICES);
1183 if (!devlist)
1184 return rdpewa_cbor_encode_hresult_response(S_OK);
1185
1186 size_t ndevs = 0;
1187 int r = fido_dev_info_manifest(devlist, RDPEWA_MAX_DEVICES, &ndevs);
1188 if (r != FIDO_OK || ndevs == 0)
1189 {
1190 fido_dev_info_free(&devlist, RDPEWA_MAX_DEVICES);
1191 return rdpewa_cbor_encode_hresult_response(S_OK);
1192 }
1193
1194 /* Build CBOR array of authenticator maps per spec section 2.2.2.3 */
1195 cbor_item_t* arr = cbor_new_definite_array(ndevs);
1196 if (!arr)
1197 {
1198 fido_dev_info_free(&devlist, RDPEWA_MAX_DEVICES);
1199 return nullptr;
1200 }
1201
1202 for (size_t i = 0; i < ndevs; i++)
1203 {
1204 const fido_dev_info_t* di = fido_dev_info_ptr(devlist, i);
1205 cbor_item_t* entry = cbor_new_definite_map(4);
1206 if (!entry)
1207 continue;
1208
1209 /* key 1: version */
1210 cbor_item_t* k1 = cbor_build_uint8(1);
1211 cbor_item_t* v1 = cbor_build_uint8(1);
1212 if (!cbor_map_add(entry, (struct cbor_pair){ .key = k1, .value = v1 }))
1213 {
1214 cbor_decref(&k1);
1215 cbor_decref(&v1);
1216 cbor_decref(&entry);
1217 continue;
1218 }
1219 cbor_decref(&k1);
1220 cbor_decref(&v1);
1221
1222 /* key 3: authenticator name */
1223 const char* prod = fido_dev_info_product_string(di);
1224 cbor_item_t* k3 = cbor_build_uint8(3);
1225 cbor_item_t* v3 = cbor_build_string(prod ? prod : "FIDO2 Key");
1226 if (!cbor_map_add(entry, (struct cbor_pair){ .key = k3, .value = v3 }))
1227 {
1228 cbor_decref(&k3);
1229 cbor_decref(&v3);
1230 cbor_decref(&entry);
1231 continue;
1232 }
1233 cbor_decref(&k3);
1234 cbor_decref(&v3);
1235
1236 /* key 4: authenticator logo (empty) */
1237 cbor_item_t* k4 = cbor_build_uint8(4);
1238 cbor_item_t* v4 = cbor_build_bytestring(nullptr, 0);
1239 if (!cbor_map_add(entry, (struct cbor_pair){ .key = k4, .value = v4 }))
1240 {
1241 cbor_decref(&k4);
1242 cbor_decref(&v4);
1243 cbor_decref(&entry);
1244 continue;
1245 }
1246 cbor_decref(&k4);
1247 cbor_decref(&v4);
1248
1249 /* key 5: isLocked */
1250 cbor_item_t* k5 = cbor_build_uint8(5);
1251 cbor_item_t* v5 = cbor_build_bool(false);
1252 if (!cbor_map_add(entry, (struct cbor_pair){ .key = k5, .value = v5 }))
1253 {
1254 cbor_decref(&k5);
1255 cbor_decref(&v5);
1256 cbor_decref(&entry);
1257 continue;
1258 }
1259 cbor_decref(&k5);
1260 cbor_decref(&v5);
1261
1262 if (!cbor_array_push(arr, entry))
1263 {
1264 cbor_decref(&entry);
1265 continue;
1266 }
1267 cbor_decref(&entry);
1268 }
1269
1270 fido_dev_info_free(&devlist, RDPEWA_MAX_DEVICES);
1271
1272 size_t cborLen = cbor_serialized_size(arr);
1273 if (cborLen == 0)
1274 {
1275 cbor_decref(&arr);
1276 return nullptr;
1277 }
1278
1279 /* Response: 4-byte LE HRESULT + CBOR array */
1280 wStream* s = Stream_New(nullptr, 4 + cborLen);
1281 if (!s)
1282 {
1283 cbor_decref(&arr);
1284 return nullptr;
1285 }
1286
1287 Stream_Write_UINT32(s, (UINT32)S_OK);
1288 if (cbor_serialize(arr, Stream_Pointer(s), cborLen) == 0)
1289 {
1290 cbor_decref(&arr);
1291 Stream_Free(s, TRUE);
1292 return nullptr;
1293 }
1294 Stream_Seek(s, cborLen);
1295 cbor_decref(&arr);
1296 return s;
1297}
1298
1299wStream* rdpewa_fido_get_credentials(rdpContext* context, const char* rpId)
1300{
1301 WINPR_UNUSED(context);
1302
1303 if (!rpId || !rpId[0])
1304 {
1305 WLog_WARN(TAG, "GET_CREDENTIALS: no rpId provided");
1306 return rdpewa_cbor_encode_hresult_response(S_OK);
1307 }
1308
1309 WLog_DBG(TAG, "GET_CREDENTIALS: querying credentials for rpId '%s'", rpId);
1310
1311 /* Iterate all devices and collect credentials from each */
1312 size_t ndevs = 0;
1313 RDPEWA_DEVICE_INFO devInfo = WINPR_C_ARRAY_INIT;
1314 fido_dev_t* dev = rdpewa_fido_open_device(&devInfo, 0, &ndevs);
1315 if (!dev)
1316 return rdpewa_cbor_encode_hresult_response(S_OK);
1317
1318 cbor_item_t* arr = cbor_new_definite_array(0);
1319 if (!arr)
1320 {
1321 fido_dev_close(dev);
1322 fido_dev_free(&dev);
1323 return nullptr;
1324 }
1325
1326 for (size_t devIdx = 0; devIdx < ndevs; devIdx++)
1327 {
1328 if (devIdx > 0)
1329 {
1330 fido_dev_close(dev);
1331 fido_dev_free(&dev);
1332 dev = rdpewa_fido_open_device(&devInfo, (int)devIdx, nullptr);
1333 if (!dev)
1334 continue;
1335 }
1336
1337 if (!fido_dev_supports_credman(dev))
1338 {
1339 WLog_DBG(TAG,
1340 "GET_CREDENTIALS: device %" PRIuz " does not support credential management",
1341 devIdx);
1342 continue;
1343 }
1344
1345 /* Credential management requires a PIN token. We don't prompt for PIN here
1346 * since the platform handles credential selection differently. The server
1347 * sends allowList in command 5 and the authenticator handles it at touch time.
1348 * Only enumerate credentials from devices that don't require a PIN, to avoid
1349 * spamming the user with PIN prompts. */
1350 if (fido_dev_has_pin(dev))
1351 {
1352 WLog_DBG(TAG, "GET_CREDENTIALS: device %" PRIuz " requires PIN, skipping", devIdx);
1353 continue;
1354 }
1355
1356 const char* pin = nullptr;
1357
1358 fido_credman_rk_t* rk = fido_credman_rk_new();
1359 if (!rk)
1360 continue;
1361
1362 int r = fido_credman_get_dev_rk(dev, rpId, rk, pin);
1363 if (r != FIDO_OK)
1364 {
1365 WLog_DBG(TAG, "GET_CREDENTIALS: device %" PRIuz ": %s", devIdx, fido_strerr(r));
1366 fido_credman_rk_free(&rk);
1367 continue;
1368 }
1369
1370 size_t ncreds = fido_credman_rk_count(rk);
1371 WLog_DBG(TAG, "GET_CREDENTIALS: device %" PRIuz ": found %" PRIuz " credentials", devIdx,
1372 ncreds);
1373
1374 for (size_t i = 0; i < ncreds; i++)
1375 {
1376 const fido_cred_t* cred = fido_credman_rk(rk, i);
1377 if (!cred)
1378 continue;
1379
1380 cbor_item_t* entry = cbor_new_definite_map(4);
1381 if (!entry)
1382 continue;
1383
1384 cbor_item_t* k0 = cbor_build_uint8(0);
1385 cbor_item_t* v0 = cbor_build_uint8(4);
1386 if (!cbor_map_add(entry, (struct cbor_pair){ .key = k0, .value = v0 }))
1387 {
1388 cbor_decref(&k0);
1389 cbor_decref(&v0);
1390 cbor_decref(&entry);
1391 continue;
1392 }
1393 cbor_decref(&k0);
1394 cbor_decref(&v0);
1395
1396 cbor_item_t* k1 = cbor_build_uint8(1);
1397 cbor_item_t* v1 = cbor_build_bytestring(fido_cred_id_ptr(cred), fido_cred_id_len(cred));
1398 if (!cbor_map_add(entry, (struct cbor_pair){ .key = k1, .value = v1 }))
1399 {
1400 cbor_decref(&k1);
1401 cbor_decref(&v1);
1402 cbor_decref(&entry);
1403 continue;
1404 }
1405 cbor_decref(&k1);
1406 cbor_decref(&v1);
1407
1408 cbor_item_t* k2 = cbor_build_uint8(2);
1409 cbor_item_t* rpMap = cbor_new_definite_map(1);
1410 {
1411 cbor_item_t* rk2 = cbor_build_string("id");
1412 cbor_item_t* rv2 = cbor_build_string(rpId);
1413 if (!cbor_map_add(rpMap, (struct cbor_pair){ .key = rk2, .value = rv2 }))
1414 {
1415 cbor_decref(&rk2);
1416 cbor_decref(&rv2);
1417 cbor_decref(&k2);
1418 cbor_decref(&rpMap);
1419 cbor_decref(&entry);
1420 continue;
1421 }
1422 cbor_decref(&rk2);
1423 cbor_decref(&rv2);
1424 }
1425
1426 if (!cbor_map_add(entry, (struct cbor_pair){ .key = k2, .value = rpMap }))
1427 {
1428 cbor_decref(&k2);
1429 cbor_decref(&rpMap);
1430 cbor_decref(&entry);
1431 continue;
1432 }
1433 cbor_decref(&k2);
1434 cbor_decref(&rpMap);
1435
1436 cbor_item_t* k3 = cbor_build_uint8(3);
1437 cbor_item_t* userMap = cbor_new_definite_map(2);
1438 {
1439 cbor_item_t* uk = cbor_build_string("id");
1440 cbor_item_t* uv =
1441 cbor_build_bytestring(fido_cred_user_id_ptr(cred), fido_cred_user_id_len(cred));
1442 if (!cbor_map_add(userMap, (struct cbor_pair){ .key = uk, .value = uv }))
1443 {
1444 cbor_decref(&uk);
1445 cbor_decref(&uv);
1446 cbor_decref(&k3);
1447 cbor_decref(&userMap);
1448 cbor_decref(&entry);
1449 continue;
1450 }
1451 cbor_decref(&uk);
1452 cbor_decref(&uv);
1453
1454 const char* uname = fido_cred_user_name(cred);
1455 if (uname)
1456 {
1457 cbor_item_t* unk = cbor_build_string("name");
1458 cbor_item_t* unv = cbor_build_string(uname);
1459 if (!cbor_map_add(userMap, (struct cbor_pair){ .key = unk, .value = unv }))
1460 {
1461 cbor_decref(&unk);
1462 cbor_decref(&unv);
1463 cbor_decref(&k3);
1464 cbor_decref(&userMap);
1465 cbor_decref(&entry);
1466 continue;
1467 }
1468 cbor_decref(&unk);
1469 cbor_decref(&unv);
1470 }
1471 }
1472
1473 if (!cbor_map_add(entry, (struct cbor_pair){ .key = k3, .value = userMap }))
1474 {
1475 cbor_decref(&k3);
1476 cbor_decref(&userMap);
1477 cbor_decref(&entry);
1478 continue;
1479 }
1480 cbor_decref(&k3);
1481 cbor_decref(&userMap);
1482
1483 if (!cbor_array_push(arr, entry))
1484 {
1485 cbor_decref(&entry);
1486 continue;
1487 }
1488 cbor_decref(&entry);
1489 }
1490
1491 fido_credman_rk_free(&rk);
1492 }
1493
1494 if (dev)
1495 {
1496 fido_dev_close(dev);
1497 fido_dev_free(&dev);
1498 }
1499
1500 size_t cborLen = cbor_serialized_size(arr);
1501 if (cborLen == 0)
1502 {
1503 cbor_decref(&arr);
1504 return nullptr;
1505 }
1506
1507 wStream* s = Stream_New(nullptr, 4 + cborLen);
1508 if (!s)
1509 {
1510 cbor_decref(&arr);
1511 return nullptr;
1512 }
1513
1514 Stream_Write_UINT32(s, (UINT32)S_OK);
1515 if (cbor_serialize(arr, Stream_Pointer(s), cborLen) == 0)
1516 {
1517 cbor_decref(&arr);
1518 Stream_Free(s, TRUE);
1519 return nullptr;
1520 }
1521 Stream_Seek(s, cborLen);
1522 cbor_decref(&arr);
1523 return s;
1524}
Device info for the response map.
Definition rdpewa_cbor.h:42
Decoded MS-RDPEWA request message.
Definition rdpewa_cbor.h:29