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