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