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