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