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