xref: /freebsd/contrib/libfido2/src/winhello.c (revision 716fd348e01c5f2ba125f878a634a753436c2994)
1 /*
2  * Copyright (c) 2021 Yubico AB. All rights reserved.
3  * Use of this source code is governed by a BSD-style
4  * license that can be found in the LICENSE file.
5  */
6 
7 #include <sys/types.h>
8 
9 #include <stdlib.h>
10 #include <windows.h>
11 #include <webauthn.h>
12 
13 #include "fido.h"
14 
15 #define MAXCHARS	128
16 #define MAXCREDS	128
17 #define MAXMSEC		6000 * 1000
18 #define VENDORID	0x045e
19 #define PRODID		0x0001
20 
21 struct winhello_assert {
22 	WEBAUTHN_CLIENT_DATA				 cd;
23 	WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS	 opt;
24 	WEBAUTHN_ASSERTION				*assert;
25 	wchar_t						*rp_id;
26 };
27 
28 struct winhello_cred {
29 	WEBAUTHN_RP_ENTITY_INFORMATION			 rp;
30 	WEBAUTHN_USER_ENTITY_INFORMATION		 user;
31 	WEBAUTHN_COSE_CREDENTIAL_PARAMETER		 alg;
32 	WEBAUTHN_COSE_CREDENTIAL_PARAMETERS		 cose;
33 	WEBAUTHN_CLIENT_DATA				 cd;
34 	WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS	 opt;
35 	WEBAUTHN_CREDENTIAL_ATTESTATION			*att;
36 	wchar_t						*rp_id;
37 	wchar_t						*rp_name;
38 	wchar_t						*user_name;
39 	wchar_t						*user_icon;
40 	wchar_t						*display_name;
41 };
42 
43 static wchar_t *
44 to_utf16(const char *utf8)
45 {
46 	int nch;
47 	wchar_t *utf16;
48 
49 	if (utf8 == NULL) {
50 		fido_log_debug("%s: NULL", __func__);
51 		return NULL;
52 	}
53 	if ((nch = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0)) < 1 ||
54 	    (size_t)nch > MAXCHARS) {
55 		fido_log_debug("%s: MultiByteToWideChar %d", __func__, nch);
56 		return NULL;
57 	}
58 	if ((utf16 = calloc((size_t)nch, sizeof(*utf16))) == NULL) {
59 		fido_log_debug("%s: calloc", __func__);
60 		return NULL;
61 	}
62 	if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16, nch) != nch) {
63 		fido_log_debug("%s: MultiByteToWideChar", __func__);
64 		free(utf16);
65 		return NULL;
66 	}
67 
68 	return utf16;
69 }
70 
71 static char *
72 to_utf8(const wchar_t *utf16)
73 {
74 	int nch;
75 	char *utf8;
76 
77 	if (utf16 == NULL) {
78 		fido_log_debug("%s: NULL", __func__);
79 		return NULL;
80 	}
81 	if ((nch = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16,
82 	    -1, NULL, 0, NULL, NULL)) < 1 || (size_t)nch > MAXCHARS) {
83 		fido_log_debug("%s: WideCharToMultiByte %d", __func__);
84 		return NULL;
85 	}
86 	if ((utf8 = calloc((size_t)nch, sizeof(*utf8))) == NULL) {
87 		fido_log_debug("%s: calloc", __func__);
88 		return NULL;
89 	}
90 	if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16, -1,
91 	    utf8, nch, NULL, NULL) != nch) {
92 		fido_log_debug("%s: WideCharToMultiByte", __func__);
93 		free(utf8);
94 		return NULL;
95 	}
96 
97 	return utf8;
98 }
99 
100 static int
101 to_fido_str_array(fido_str_array_t *sa, const char **v, size_t n)
102 {
103 	if ((sa->ptr = calloc(n, sizeof(char *))) == NULL) {
104 		fido_log_debug("%s: calloc", __func__);
105 		return -1;
106 	}
107 	for (size_t i = 0; i < n; i++) {
108 		if ((sa->ptr[i] = strdup(v[i])) == NULL) {
109 			fido_log_debug("%s: strdup", __func__);
110 			return -1;
111 		}
112 		sa->len++;
113 	}
114 
115 	return 0;
116 }
117 
118 static int
119 to_fido(HRESULT hr)
120 {
121 	switch (hr) {
122 	case NTE_NOT_SUPPORTED:
123 		return FIDO_ERR_UNSUPPORTED_OPTION;
124 	case NTE_INVALID_PARAMETER:
125 		return FIDO_ERR_INVALID_PARAMETER;
126 	case NTE_TOKEN_KEYSET_STORAGE_FULL:
127 		return FIDO_ERR_KEY_STORE_FULL;
128 	case NTE_DEVICE_NOT_FOUND:
129 	case NTE_NOT_FOUND:
130 		return FIDO_ERR_NOT_ALLOWED;
131 	default:
132 		fido_log_debug("%s: hr=0x%x", __func__, hr);
133 		return FIDO_ERR_INTERNAL;
134 	}
135 }
136 
137 static int
138 pack_cd(WEBAUTHN_CLIENT_DATA *out, const fido_blob_t *in)
139 {
140 	if (in->ptr == NULL) {
141 		fido_log_debug("%s: NULL", __func__);
142 		return -1;
143 	}
144 	if (in->len > ULONG_MAX) {
145 		fido_log_debug("%s: in->len=%zu", __func__, in->len);
146 		return -1;
147 	}
148 	out->dwVersion = WEBAUTHN_CLIENT_DATA_CURRENT_VERSION;
149 	out->cbClientDataJSON = (DWORD)in->len;
150 	out->pbClientDataJSON = in->ptr;
151 	out->pwszHashAlgId = WEBAUTHN_HASH_ALGORITHM_SHA_256;
152 
153 	return 0;
154 }
155 
156 static int
157 pack_credlist(WEBAUTHN_CREDENTIALS *out, const fido_blob_array_t *in)
158 {
159 	WEBAUTHN_CREDENTIAL *c;
160 
161 	if (in->len == 0) {
162 		return 0; /* nothing to do */
163 	}
164 	if (in->len > MAXCREDS) {
165 		fido_log_debug("%s: in->len=%zu", __func__, in->len);
166 		return -1;
167 	}
168 	if ((out->pCredentials = calloc(in->len, sizeof(*c))) == NULL) {
169 		fido_log_debug("%s: calloc", __func__);
170 		return -1;
171 	}
172 	out->cCredentials = (DWORD)in->len;
173 	for (size_t i = 0; i < in->len; i++) {
174 		if (in->ptr[i].len > ULONG_MAX) {
175 			fido_log_debug("%s: %zu", __func__, in->ptr[i].len);
176 			return -1;
177 		}
178 		c = &out->pCredentials[i];
179 		c->dwVersion = WEBAUTHN_CREDENTIAL_CURRENT_VERSION;
180 		c->cbId = (DWORD)in->ptr[i].len;
181 		c->pbId = in->ptr[i].ptr;
182 		c->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY;
183 	}
184 
185 	return 0;
186 }
187 
188 static int
189 set_uv(DWORD *out, fido_opt_t uv, const char *pin)
190 {
191 	if (pin) {
192 		*out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
193 		return 0;
194 	}
195 
196 	switch (uv) {
197 	case FIDO_OPT_OMIT:
198 		*out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY;
199 		break;
200 	case FIDO_OPT_FALSE:
201 		*out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
202 		break;
203 	case FIDO_OPT_TRUE:
204 		*out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
205 		break;
206 	}
207 
208 	return 0;
209 }
210 
211 static int
212 pack_rp(wchar_t **id, wchar_t **name, WEBAUTHN_RP_ENTITY_INFORMATION *out,
213     fido_rp_t *in)
214 {
215 	/* keep non-const copies of pwsz* for free() */
216 	out->dwVersion = WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION;
217 	if ((out->pwszId = *id = to_utf16(in->id)) == NULL) {
218 		fido_log_debug("%s: id", __func__);
219 		return -1;
220 	}
221 	if (in->name && (out->pwszName = *name = to_utf16(in->name)) == NULL) {
222 		fido_log_debug("%s: name", __func__);
223 		return -1;
224 	}
225 	return 0;
226 }
227 
228 static int
229 pack_user(wchar_t **name, wchar_t **icon, wchar_t **display_name,
230     WEBAUTHN_USER_ENTITY_INFORMATION *out, fido_user_t *in)
231 {
232 	if (in->id.ptr == NULL || in->id.len > ULONG_MAX) {
233 		fido_log_debug("%s: id", __func__);
234 		return -1;
235 	}
236 	out->dwVersion = WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION;
237 	out->cbId = (DWORD)in->id.len;
238 	out->pbId = in->id.ptr;
239 	/* keep non-const copies of pwsz* for free() */
240 	if (in->name != NULL) {
241 		if ((out->pwszName = *name = to_utf16(in->name)) == NULL) {
242 			fido_log_debug("%s: name", __func__);
243 			return -1;
244 		}
245 	}
246 	if (in->icon != NULL) {
247 		if ((out->pwszIcon = *icon = to_utf16(in->icon)) == NULL) {
248 			fido_log_debug("%s: icon", __func__);
249 			return -1;
250 		}
251 	}
252 	if (in->display_name != NULL) {
253 		if ((out->pwszDisplayName = *display_name =
254 		    to_utf16(in->display_name)) == NULL) {
255 			fido_log_debug("%s: display_name", __func__);
256 			return -1;
257 		}
258 	}
259 
260 	return 0;
261 }
262 
263 static int
264 pack_cose(WEBAUTHN_COSE_CREDENTIAL_PARAMETER *alg,
265     WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *cose, int type)
266 {
267 	switch (type) {
268 	case COSE_ES256:
269 		alg->lAlg = WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256;
270 		break;
271 	case COSE_EDDSA:
272 		alg->lAlg = -8; /* XXX */;
273 		break;
274 	case COSE_RS256:
275 		alg->lAlg = WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256;
276 		break;
277 	default:
278 		fido_log_debug("%s: type %d", __func__, type);
279 		return -1;
280 	}
281 	alg->dwVersion = WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION;
282 	alg->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY;
283 	cose->cCredentialParameters = 1;
284 	cose->pCredentialParameters = alg;
285 
286 	return 0;
287 }
288 
289 static int
290 pack_cred_ext(WEBAUTHN_EXTENSIONS *out, fido_cred_ext_t *in)
291 {
292 	WEBAUTHN_EXTENSION *e;
293 	WEBAUTHN_CRED_PROTECT_EXTENSION_IN *p;
294 	BOOL *b;
295 	size_t n = 0, i = 0;
296 
297 	if (in->mask == 0) {
298 		return 0; /* nothing to do */
299 	}
300 	if (in->mask & ~(FIDO_EXT_HMAC_SECRET | FIDO_EXT_CRED_PROTECT)) {
301 		fido_log_debug("%s: mask 0x%x", in->mask);
302 		return -1;
303 	}
304 	if (in->mask & FIDO_EXT_HMAC_SECRET)
305 		n++;
306 	if (in->mask & FIDO_EXT_CRED_PROTECT)
307 		n++;
308 	if ((out->pExtensions = calloc(n, sizeof(*e))) == NULL) {
309 		fido_log_debug("%s: calloc", __func__);
310 		return -1;
311 	}
312 	out->cExtensions = (DWORD)n;
313 	if (in->mask & FIDO_EXT_HMAC_SECRET) {
314 		if ((b = calloc(1, sizeof(*b))) == NULL) {
315 			fido_log_debug("%s: calloc", __func__);
316 			return -1;
317 		}
318 		*b = true;
319 		e = &out->pExtensions[i];
320 		e->pwszExtensionIdentifier =
321 		    WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET;
322 		e->pvExtension = b;
323 		e->cbExtension = sizeof(*b);
324 		i++;
325 	}
326 	if (in->mask & FIDO_EXT_CRED_PROTECT) {
327 		if ((p = calloc(1, sizeof(*p))) == NULL) {
328 			fido_log_debug("%s: calloc", __func__);
329 			return -1;
330 		}
331 		p->dwCredProtect = (DWORD)in->prot;
332 		p->bRequireCredProtect = true;
333 		e = &out->pExtensions[i];
334 		e->pwszExtensionIdentifier =
335 		    WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT;
336 		e->pvExtension = p;
337 		e->cbExtension = sizeof(*p);
338 		i++;
339 	}
340 
341 	return 0;
342 }
343 
344 static int
345 unpack_fmt(fido_cred_t *cred, WEBAUTHN_CREDENTIAL_ATTESTATION *att)
346 {
347 	char *fmt;
348 	int r;
349 
350 	if ((fmt = to_utf8(att->pwszFormatType)) == NULL) {
351 		fido_log_debug("%s: fmt", __func__);
352 		return -1;
353 	}
354 	r = fido_cred_set_fmt(cred, fmt);
355 	free(fmt);
356 	fmt = NULL;
357 	if (r != FIDO_OK) {
358 		fido_log_debug("%s: fido_cred_set_fmt: %s", __func__,
359 		    fido_strerr(r));
360 		return -1;
361 	}
362 
363 	return 0;
364 }
365 
366 static int
367 unpack_cred_authdata(fido_cred_t *cred, WEBAUTHN_CREDENTIAL_ATTESTATION *att)
368 {
369 	int r;
370 
371 	if (att->cbAuthenticatorData > SIZE_MAX) {
372 		fido_log_debug("%s: cbAuthenticatorData", __func__);
373 		return -1;
374 	}
375 	if ((r = fido_cred_set_authdata_raw(cred, att->pbAuthenticatorData,
376 	    (size_t)att->cbAuthenticatorData)) != FIDO_OK) {
377 		fido_log_debug("%s: fido_cred_set_authdata_raw: %s", __func__,
378 		    fido_strerr(r));
379 		return -1;
380 	}
381 
382 	return 0;
383 }
384 
385 static int
386 unpack_cred_sig(fido_cred_t *cred, WEBAUTHN_COMMON_ATTESTATION *attr)
387 {
388 	int r;
389 
390 	if (attr->cbSignature > SIZE_MAX) {
391 		fido_log_debug("%s: cbSignature", __func__);
392 		return -1;
393 	}
394 	if ((r = fido_cred_set_sig(cred, attr->pbSignature,
395 	    (size_t)attr->cbSignature)) != FIDO_OK) {
396 		fido_log_debug("%s: fido_cred_set_sig: %s", __func__,
397 		    fido_strerr(r));
398 		return -1;
399 	}
400 
401 	return 0;
402 }
403 
404 static int
405 unpack_x5c(fido_cred_t *cred, WEBAUTHN_COMMON_ATTESTATION *attr)
406 {
407 	int r;
408 
409 	fido_log_debug("%s: %u cert(s)", __func__, attr->cX5c);
410 
411 	if (attr->cX5c == 0)
412 		return 0; /* self-attestation */
413 	if (attr->lAlg != WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256) {
414 		fido_log_debug("%s: lAlg %d", __func__, attr->lAlg);
415 		return -1;
416 	}
417 	if (attr->pX5c[0].cbData > SIZE_MAX) {
418 		fido_log_debug("%s: cbData", __func__);
419 		return -1;
420 	}
421 	if ((r = fido_cred_set_x509(cred, attr->pX5c[0].pbData,
422 	    (size_t)attr->pX5c[0].cbData)) != FIDO_OK) {
423 		fido_log_debug("%s: fido_cred_set_x509: %s", __func__,
424 		    fido_strerr(r));
425 		return -1;
426 	}
427 
428 	return 0;
429 }
430 
431 static int
432 unpack_assert_authdata(fido_assert_t *assert, WEBAUTHN_ASSERTION *wa)
433 {
434 	int r;
435 
436 	if (wa->cbAuthenticatorData > SIZE_MAX) {
437 		fido_log_debug("%s: cbAuthenticatorData", __func__);
438 		return -1;
439 	}
440 	if ((r = fido_assert_set_authdata_raw(assert, 0, wa->pbAuthenticatorData,
441 	    (size_t)wa->cbAuthenticatorData)) != FIDO_OK) {
442 		fido_log_debug("%s: fido_assert_set_authdata_raw: %s", __func__,
443 		    fido_strerr(r));
444 		return -1;
445 	}
446 
447 	return 0;
448 }
449 
450 static int
451 unpack_assert_sig(fido_assert_t *assert, WEBAUTHN_ASSERTION *wa)
452 {
453 	int r;
454 
455 	if (wa->cbSignature > SIZE_MAX) {
456 		fido_log_debug("%s: cbSignature", __func__);
457 		return -1;
458 	}
459 	if ((r = fido_assert_set_sig(assert, 0, wa->pbSignature,
460 	    (size_t)wa->cbSignature)) != FIDO_OK) {
461 		fido_log_debug("%s: fido_assert_set_sig: %s", __func__,
462 		    fido_strerr(r));
463 		return -1;
464 	}
465 
466 	return 0;
467 }
468 
469 static int
470 unpack_cred_id(fido_assert_t *assert, WEBAUTHN_ASSERTION *wa)
471 {
472 	if (wa->Credential.cbId > SIZE_MAX) {
473 		fido_log_debug("%s: Credential.cbId", __func__);
474 		return -1;
475 	}
476 	if (fido_blob_set(&assert->stmt[0].id, wa->Credential.pbId,
477 	    (size_t)wa->Credential.cbId) < 0) {
478 		fido_log_debug("%s: fido_blob_set", __func__);
479 		return -1;
480 	}
481 
482 	return 0;
483 }
484 
485 static int
486 unpack_user_id(fido_assert_t *assert, WEBAUTHN_ASSERTION *wa)
487 {
488 	if (wa->cbUserId == 0)
489 		return 0; /* user id absent */
490 	if (wa->cbUserId > SIZE_MAX) {
491 		fido_log_debug("%s: cbUserId", __func__);
492 		return -1;
493 	}
494 	if (fido_blob_set(&assert->stmt[0].user.id, wa->pbUserId,
495 	    (size_t)wa->cbUserId) < 0) {
496 		fido_log_debug("%s: fido_blob_set", __func__);
497 		return -1;
498 	}
499 
500 	return 0;
501 }
502 
503 static int
504 translate_fido_assert(struct winhello_assert *ctx, fido_assert_t *assert,
505     const char *pin)
506 {
507 	WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt;
508 
509 	/* not supported by webauthn.h */
510 	if (assert->up == FIDO_OPT_FALSE) {
511 		fido_log_debug("%s: up %d", __func__, assert->up);
512 		return FIDO_ERR_UNSUPPORTED_OPTION;
513 	}
514 	/* not implemented */
515 	if (assert->ext.mask) {
516 		fido_log_debug("%s: ext 0x%x", __func__, assert->ext.mask);
517 		return FIDO_ERR_UNSUPPORTED_EXTENSION;
518 	}
519 	if ((ctx->rp_id = to_utf16(assert->rp_id)) == NULL) {
520 		fido_log_debug("%s: rp_id", __func__);
521 		return FIDO_ERR_INTERNAL;
522 	}
523 	if (pack_cd(&ctx->cd, &assert->cd) < 0) {
524 		fido_log_debug("%s: pack_cd", __func__);
525 		return FIDO_ERR_INTERNAL;
526 	}
527 	/* options */
528 	opt = &ctx->opt;
529 	opt->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1;
530 	opt->dwTimeoutMilliseconds = MAXMSEC;
531 	if (pack_credlist(&opt->CredentialList, &assert->allow_list) < 0) {
532 		fido_log_debug("%s: pack_credlist", __func__);
533 		return FIDO_ERR_INTERNAL;
534 	}
535 	if (set_uv(&opt->dwUserVerificationRequirement, assert->uv, pin) < 0) {
536 		fido_log_debug("%s: set_uv", __func__);
537 		return FIDO_ERR_INTERNAL;
538 	}
539 
540 	return FIDO_OK;
541 }
542 
543 static int
544 translate_winhello_assert(fido_assert_t *assert, WEBAUTHN_ASSERTION *wa)
545 {
546 	int r;
547 
548 	if (assert->stmt_len > 0) {
549 		fido_log_debug("%s: stmt_len=%zu", __func__, assert->stmt_len);
550 		return FIDO_ERR_INTERNAL;
551 	}
552 	if ((r = fido_assert_set_count(assert, 1)) != FIDO_OK) {
553 		fido_log_debug("%s: fido_assert_set_count: %s", __func__,
554 		    fido_strerr(r));
555 		return FIDO_ERR_INTERNAL;
556 	}
557 	if (unpack_assert_authdata(assert, wa) < 0) {
558 		fido_log_debug("%s: unpack_assert_authdata", __func__);
559 		return FIDO_ERR_INTERNAL;
560 	}
561 	if (unpack_assert_sig(assert, wa) < 0) {
562 		fido_log_debug("%s: unpack_assert_sig", __func__);
563 		return FIDO_ERR_INTERNAL;
564 	}
565 	if (unpack_cred_id(assert, wa) < 0) {
566 		fido_log_debug("%s: unpack_cred_id", __func__);
567 		return FIDO_ERR_INTERNAL;
568 	}
569 	if (unpack_user_id(assert, wa) < 0) {
570 		fido_log_debug("%s: unpack_user_id", __func__);
571 		return FIDO_ERR_INTERNAL;
572 	}
573 
574 	return FIDO_OK;
575 }
576 
577 static int
578 translate_fido_cred(struct winhello_cred *ctx, fido_cred_t *cred,
579     const char *pin)
580 {
581 	WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *opt;
582 
583 	if (pack_rp(&ctx->rp_id, &ctx->rp_name, &ctx->rp, &cred->rp) < 0) {
584 		fido_log_debug("%s: pack_rp", __func__);
585 		return FIDO_ERR_INTERNAL;
586 	}
587 	if (pack_user(&ctx->user_name, &ctx->user_icon, &ctx->display_name,
588 	    &ctx->user, &cred->user) < 0) {
589 		fido_log_debug("%s: pack_user", __func__);
590 		return FIDO_ERR_INTERNAL;
591 	}
592 	if (pack_cose(&ctx->alg, &ctx->cose, cred->type) < 0) {
593 		fido_log_debug("%s: pack_cose", __func__);
594 		return FIDO_ERR_INTERNAL;
595 	}
596 	if (pack_cd(&ctx->cd, &cred->cd) < 0) {
597 		fido_log_debug("%s: pack_cd", __func__);
598 		return FIDO_ERR_INTERNAL;
599 	}
600 	/* options */
601 	opt = &ctx->opt;
602 	opt->dwVersion = WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1;
603 	opt->dwTimeoutMilliseconds = MAXMSEC;
604 	if (pack_credlist(&opt->CredentialList, &cred->excl) < 0) {
605 		fido_log_debug("%s: pack_credlist", __func__);
606 		return FIDO_ERR_INTERNAL;
607 	}
608 	if (pack_cred_ext(&opt->Extensions, &cred->ext) < 0) {
609 		fido_log_debug("%s: pack_cred_ext", __func__);
610 		return FIDO_ERR_UNSUPPORTED_EXTENSION;
611 	}
612 	if (set_uv(&opt->dwUserVerificationRequirement, cred->uv, pin) < 0) {
613 		fido_log_debug("%s: set_uv", __func__);
614 		return FIDO_ERR_INTERNAL;
615 	}
616 	if (cred->rk == FIDO_OPT_TRUE) {
617 		opt->bRequireResidentKey = true;
618 	}
619 
620 	return FIDO_OK;
621 }
622 
623 static int
624 translate_winhello_cred(fido_cred_t *cred, WEBAUTHN_CREDENTIAL_ATTESTATION *att)
625 {
626 	if (unpack_fmt(cred, att) < 0) {
627 		fido_log_debug("%s: unpack_fmt", __func__);
628 		return FIDO_ERR_INTERNAL;
629 	}
630 	if (unpack_cred_authdata(cred, att) < 0) {
631 		fido_log_debug("%s: unpack_cred_authdata", __func__);
632 		return FIDO_ERR_INTERNAL;
633 	}
634 
635 	switch (att->dwAttestationDecodeType) {
636 	case WEBAUTHN_ATTESTATION_DECODE_NONE:
637 		if (att->pvAttestationDecode != NULL) {
638 			fido_log_debug("%s: pvAttestationDecode", __func__);
639 			return FIDO_ERR_INTERNAL;
640 		}
641 		break;
642 	case WEBAUTHN_ATTESTATION_DECODE_COMMON:
643 		if (att->pvAttestationDecode == NULL) {
644 			fido_log_debug("%s: pvAttestationDecode", __func__);
645 			return FIDO_ERR_INTERNAL;
646 		}
647 		if (unpack_cred_sig(cred, att->pvAttestationDecode) < 0) {
648 			fido_log_debug("%s: unpack_cred_sig", __func__);
649 			return FIDO_ERR_INTERNAL;
650 		}
651 		if (unpack_x5c(cred, att->pvAttestationDecode) < 0) {
652 			fido_log_debug("%s: unpack_x5c", __func__);
653 			return FIDO_ERR_INTERNAL;
654 		}
655 		break;
656 	default:
657 		fido_log_debug("%s: dwAttestationDecodeType: %u", __func__,
658 		    att->dwAttestationDecodeType);
659 		return FIDO_ERR_INTERNAL;
660 	}
661 
662 	return FIDO_OK;
663 }
664 
665 static int
666 winhello_manifest(BOOL *present)
667 {
668 	DWORD n;
669 	HRESULT hr;
670 	int r = FIDO_OK;
671 
672 	if ((n = WebAuthNGetApiVersionNumber()) < 1) {
673 		fido_log_debug("%s: unsupported api %u", __func__, n);
674 		return FIDO_ERR_INTERNAL;
675 	}
676 	fido_log_debug("%s: api version %u", __func__, n);
677 	hr = WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable(present);
678 	if (hr != S_OK)  {
679 		r = to_fido(hr);
680 		fido_log_debug("%s: %ls -> %s", __func__,
681 		    WebAuthNGetErrorName(hr), fido_strerr(r));
682 	}
683 
684 	return r;
685 }
686 
687 static int
688 winhello_get_assert(HWND w, struct winhello_assert *ctx)
689 {
690 	HRESULT hr;
691 	int r = FIDO_OK;
692 
693 	hr = WebAuthNAuthenticatorGetAssertion(w, ctx->rp_id, &ctx->cd,
694 	    &ctx->opt, &ctx->assert);
695 	if (hr != S_OK) {
696 		r = to_fido(hr);
697 		fido_log_debug("%s: %ls -> %s", __func__,
698 		    WebAuthNGetErrorName(hr), fido_strerr(r));
699 	}
700 
701 	return r;
702 }
703 
704 static int
705 winhello_make_cred(HWND w, struct winhello_cred *ctx)
706 {
707 	HRESULT hr;
708 	int r = FIDO_OK;
709 
710 	hr = WebAuthNAuthenticatorMakeCredential(w, &ctx->rp, &ctx->user,
711 	    &ctx->cose, &ctx->cd, &ctx->opt, &ctx->att);
712 	if (hr != S_OK) {
713 		r = to_fido(hr);
714 		fido_log_debug("%s: %ls -> %s", __func__,
715 		    WebAuthNGetErrorName(hr), fido_strerr(r));
716 	}
717 
718 	return r;
719 }
720 
721 static void
722 winhello_assert_free(struct winhello_assert *ctx)
723 {
724 	if (ctx == NULL)
725 		return;
726 	if (ctx->assert != NULL)
727 		WebAuthNFreeAssertion(ctx->assert);
728 
729 	free(ctx->rp_id);
730 	free(ctx->opt.CredentialList.pCredentials);
731 	free(ctx);
732 }
733 
734 static void
735 winhello_cred_free(struct winhello_cred *ctx)
736 {
737 	if (ctx == NULL)
738 		return;
739 	if (ctx->att != NULL)
740 		WebAuthNFreeCredentialAttestation(ctx->att);
741 
742 	free(ctx->rp_id);
743 	free(ctx->rp_name);
744 	free(ctx->user_name);
745 	free(ctx->user_icon);
746 	free(ctx->display_name);
747 	free(ctx->opt.CredentialList.pCredentials);
748 	for (size_t i = 0; i < ctx->opt.Extensions.cExtensions; i++) {
749 		WEBAUTHN_EXTENSION *e;
750 		e = &ctx->opt.Extensions.pExtensions[i];
751 		free(e->pvExtension);
752 	}
753 	free(ctx->opt.Extensions.pExtensions);
754 	free(ctx);
755 }
756 
757 int
758 fido_winhello_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
759 {
760 	int r;
761 	BOOL present;
762 	fido_dev_info_t *di;
763 
764 	if (ilen == 0) {
765 		return FIDO_OK;
766 	}
767 	if (devlist == NULL) {
768 		return FIDO_ERR_INVALID_ARGUMENT;
769 	}
770 	if ((r = winhello_manifest(&present)) != FIDO_OK) {
771 		fido_log_debug("%s: winhello_manifest", __func__);
772 		return r;
773 	}
774 	if (present == false) {
775 		fido_log_debug("%s: not present", __func__);
776 		return FIDO_OK;
777 	}
778 
779 	di = &devlist[*olen];
780 	memset(di, 0, sizeof(*di));
781 	di->path = strdup(FIDO_WINHELLO_PATH);
782 	di->manufacturer = strdup("Microsoft Corporation");
783 	di->product = strdup("Windows Hello");
784 	di->vendor_id = VENDORID;
785 	di->product_id = PRODID;
786 	if (di->path == NULL || di->manufacturer == NULL ||
787 	    di->product == NULL) {
788 		free(di->path);
789 		free(di->manufacturer);
790 		free(di->product);
791 		explicit_bzero(di, sizeof(*di));
792 		return FIDO_ERR_INTERNAL;
793 	}
794 	++(*olen);
795 
796 	return FIDO_OK;
797 }
798 
799 int
800 fido_winhello_open(fido_dev_t *dev)
801 {
802 	if (dev->flags != 0)
803 		return FIDO_ERR_INVALID_ARGUMENT;
804 
805 	dev->attr.flags = FIDO_CAP_CBOR | FIDO_CAP_WINK;
806 	dev->flags = FIDO_DEV_WINHELLO | FIDO_DEV_CRED_PROT | FIDO_DEV_PIN_SET;
807 
808 	return FIDO_OK;
809 }
810 
811 int
812 fido_winhello_close(fido_dev_t *dev)
813 {
814 	memset(dev, 0, sizeof(*dev));
815 
816 	return FIDO_OK;
817 }
818 
819 int
820 fido_winhello_cancel(fido_dev_t *dev)
821 {
822 	(void)dev;
823 
824 	return FIDO_ERR_INTERNAL;
825 }
826 
827 int
828 fido_winhello_get_assert(fido_dev_t *dev, fido_assert_t *assert,
829     const char *pin)
830 {
831 	HWND			 w;
832 	struct winhello_assert	*ctx;
833 	int			 r = FIDO_ERR_INTERNAL;
834 
835 	(void)dev;
836 
837 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
838 		fido_log_debug("%s: calloc", __func__);
839 		goto fail;
840 	}
841 	if ((w = GetForegroundWindow()) == NULL) {
842 		fido_log_debug("%s: GetForegroundWindow", __func__);
843 		goto fail;
844 	}
845 	if ((r = translate_fido_assert(ctx, assert, pin)) != FIDO_OK) {
846 		fido_log_debug("%s: translate_fido_assert", __func__);
847 		goto fail;
848 	}
849 	if ((r = winhello_get_assert(w, ctx)) != S_OK) {
850 		fido_log_debug("%s: winhello_get_assert", __func__);
851 		goto fail;
852 	}
853 	if ((r = translate_winhello_assert(assert, ctx->assert)) != FIDO_OK) {
854 		fido_log_debug("%s: translate_winhello_assert", __func__);
855 		goto fail;
856 	}
857 
858 fail:
859 	winhello_assert_free(ctx);
860 
861 	return r;
862 }
863 
864 int
865 fido_winhello_get_cbor_info(fido_dev_t *dev, fido_cbor_info_t *ci)
866 {
867 	const char *v[3] = { "U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE" };
868 	const char *e[2] = { "credProtect", "hmac-secret" };
869 	const char *t[2] = { "nfc", "usb" };
870 	const char *o[4] = { "rk", "up", "plat", "clientPin" };
871 
872 	(void)dev;
873 
874 	fido_cbor_info_reset(ci);
875 
876 	if (to_fido_str_array(&ci->versions, v, nitems(v)) < 0 ||
877 	    to_fido_str_array(&ci->extensions, e, nitems(e)) < 0 ||
878 	    to_fido_str_array(&ci->transports, t, nitems(t)) < 0) {
879 		fido_log_debug("%s: to_fido_str_array", __func__);
880 		return FIDO_ERR_INTERNAL;
881 	}
882 	if ((ci->options.name = calloc(nitems(o), sizeof(char *))) == NULL ||
883 	    (ci->options.value = calloc(nitems(o), sizeof(bool))) == NULL) {
884 		fido_log_debug("%s: calloc", __func__);
885 		return FIDO_ERR_INTERNAL;
886 	}
887 	for (size_t i = 0; i < nitems(o); i++) {
888 		if ((ci->options.name[i] = strdup(o[i])) == NULL) {
889 			fido_log_debug("%s: strdup", __func__);
890 			return FIDO_ERR_INTERNAL;
891 		}
892 		ci->options.value[i] = true;
893 		ci->options.len++;
894 	}
895 
896 	return FIDO_OK;
897 }
898 
899 int
900 fido_winhello_make_cred(fido_dev_t *dev, fido_cred_t *cred, const char *pin)
901 {
902 	HWND			 w;
903 	struct winhello_cred	*ctx;
904 	int			 r = FIDO_ERR_INTERNAL;
905 
906 	(void)dev;
907 
908 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
909 		fido_log_debug("%s: calloc", __func__);
910 		goto fail;
911 	}
912 	if ((w = GetForegroundWindow()) == NULL) {
913 		fido_log_debug("%s: GetForegroundWindow", __func__);
914 		goto fail;
915 	}
916 	if ((r = translate_fido_cred(ctx, cred, pin)) != FIDO_OK) {
917 		fido_log_debug("%s: translate_fido_cred", __func__);
918 		goto fail;
919 	}
920 	if ((r = winhello_make_cred(w, ctx)) != FIDO_OK) {
921 		fido_log_debug("%s: winhello_make_cred", __func__);
922 		goto fail;
923 	}
924 	if ((r = translate_winhello_cred(cred, ctx->att)) != FIDO_OK) {
925 		fido_log_debug("%s: translate_winhello_cred", __func__);
926 		goto fail;
927 	}
928 
929 	r = FIDO_OK;
930 fail:
931 	winhello_cred_free(ctx);
932 
933 	return r;
934 }
935