xref: /freebsd/contrib/libfido2/src/winhello.c (revision 60a517b6)
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 #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 
739 	return FIDO_OK;
740 }
741 
742 static int
decode_attobj(const cbor_item_t * key,const cbor_item_t * val,void * arg)743 decode_attobj(const cbor_item_t *key, const cbor_item_t *val, void *arg)
744 {
745 	fido_cred_t *cred = arg;
746 	char *name = NULL;
747 	int ok = -1;
748 
749 	if (cbor_string_copy(key, &name) < 0) {
750 		fido_log_debug("%s: cbor type", __func__);
751 		ok = 0; /* ignore */
752 		goto fail;
753 	}
754 
755 	if (!strcmp(name, "fmt")) {
756 		if (cbor_decode_fmt(val, &cred->fmt) < 0) {
757 			fido_log_debug("%s: cbor_decode_fmt", __func__);
758 			goto fail;
759 		}
760 	} else if (!strcmp(name, "attStmt")) {
761 		if (cbor_decode_attstmt(val, &cred->attstmt) < 0) {
762 			fido_log_debug("%s: cbor_decode_attstmt", __func__);
763 			goto fail;
764 		}
765 	} else if (!strcmp(name, "authData")) {
766 		if (fido_blob_decode(val, &cred->authdata_raw) < 0) {
767 			fido_log_debug("%s: fido_blob_decode", __func__);
768 			goto fail;
769 		}
770 		if (cbor_decode_cred_authdata(val, cred->type,
771 		    &cred->authdata_cbor, &cred->authdata, &cred->attcred,
772 		    &cred->authdata_ext) < 0) {
773 			fido_log_debug("%s: cbor_decode_cred_authdata",
774 			    __func__);
775 			goto fail;
776 		}
777 	}
778 
779 	ok = 0;
780 fail:
781 	free(name);
782 
783 	return (ok);
784 }
785 
786 static int
translate_winhello_cred(fido_cred_t * cred,const WEBAUTHN_CREDENTIAL_ATTESTATION * att)787 translate_winhello_cred(fido_cred_t *cred,
788     const WEBAUTHN_CREDENTIAL_ATTESTATION *att)
789 {
790 	cbor_item_t *item = NULL;
791 	struct cbor_load_result cbor;
792 	int r = FIDO_ERR_INTERNAL;
793 
794 	if (att->pbAttestationObject == NULL) {
795 		fido_log_debug("%s: pbAttestationObject", __func__);
796 		goto fail;
797 	}
798 	if ((item = cbor_load(att->pbAttestationObject,
799 	    att->cbAttestationObject, &cbor)) == NULL) {
800 		fido_log_debug("%s: cbor_load", __func__);
801 		goto fail;
802 	}
803 	if (cbor_isa_map(item) == false ||
804 	    cbor_map_is_definite(item) == false ||
805 	    cbor_map_iter(item, cred, decode_attobj) < 0) {
806 		fido_log_debug("%s: cbor type", __func__);
807 		goto fail;
808 	}
809 
810 	r = FIDO_OK;
811 fail:
812 	if (item != NULL)
813 		cbor_decref(&item);
814 
815 	return r;
816 }
817 
818 static int
winhello_get_assert(HWND w,struct winhello_assert * ctx)819 winhello_get_assert(HWND w, struct winhello_assert *ctx)
820 {
821 	HRESULT hr;
822 	int r = FIDO_OK;
823 
824 	if ((hr = webauthn_get_assert(w, ctx->rp_id, &ctx->cd, &ctx->opt,
825 	    &ctx->assert)) != S_OK) {
826 		r = to_fido(hr);
827 		fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr),
828 		    fido_strerr(r));
829 	}
830 
831 	return r;
832 }
833 
834 static int
winhello_make_cred(HWND w,struct winhello_cred * ctx)835 winhello_make_cred(HWND w, struct winhello_cred *ctx)
836 {
837 	HRESULT hr;
838 	int r = FIDO_OK;
839 
840 	if ((hr = webauthn_make_cred(w, &ctx->rp, &ctx->user, &ctx->cose,
841 	    &ctx->cd, &ctx->opt, &ctx->att)) != S_OK) {
842 		r = to_fido(hr);
843 		fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr),
844 		    fido_strerr(r));
845 	}
846 
847 	return r;
848 }
849 
850 static void
winhello_assert_free(struct winhello_assert * ctx)851 winhello_assert_free(struct winhello_assert *ctx)
852 {
853 	if (ctx == NULL)
854 		return;
855 	if (ctx->assert != NULL)
856 		webauthn_free_assert(ctx->assert);
857 
858 	free(ctx->rp_id);
859 	free(ctx->appid);
860 	free(ctx->opt.CredentialList.pCredentials);
861 	if (ctx->opt.pHmacSecretSaltValues != NULL)
862 		free(ctx->opt.pHmacSecretSaltValues->pGlobalHmacSalt);
863 	free(ctx->opt.pHmacSecretSaltValues);
864 	free(ctx);
865 }
866 
867 static void
winhello_cred_free(struct winhello_cred * ctx)868 winhello_cred_free(struct winhello_cred *ctx)
869 {
870 	if (ctx == NULL)
871 		return;
872 	if (ctx->att != NULL)
873 		webauthn_free_attest(ctx->att);
874 
875 	free(ctx->rp_id);
876 	free(ctx->rp_name);
877 	free(ctx->user_name);
878 	free(ctx->user_icon);
879 	free(ctx->display_name);
880 	free(ctx->opt.CredentialList.pCredentials);
881 	for (size_t i = 0; i < ctx->opt.Extensions.cExtensions; i++) {
882 		WEBAUTHN_EXTENSION *e;
883 		e = &ctx->opt.Extensions.pExtensions[i];
884 		free(e->pvExtension);
885 	}
886 	free(ctx->opt.Extensions.pExtensions);
887 	free(ctx);
888 }
889 
890 int
fido_winhello_manifest(fido_dev_info_t * devlist,size_t ilen,size_t * olen)891 fido_winhello_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
892 {
893 	fido_dev_info_t *di;
894 
895 	if (ilen == 0) {
896 		return FIDO_OK;
897 	}
898 	if (devlist == NULL) {
899 		return FIDO_ERR_INVALID_ARGUMENT;
900 	}
901 	if (!webauthn_loaded && webauthn_load() < 0) {
902 		fido_log_debug("%s: webauthn_load", __func__);
903 		return FIDO_OK; /* not an error */
904 	}
905 
906 	di = &devlist[*olen];
907 	memset(di, 0, sizeof(*di));
908 	di->path = strdup(FIDO_WINHELLO_PATH);
909 	di->manufacturer = strdup("Microsoft Corporation");
910 	di->product = strdup("Windows Hello");
911 	di->vendor_id = VENDORID;
912 	di->product_id = PRODID;
913 	if (di->path == NULL || di->manufacturer == NULL ||
914 	    di->product == NULL) {
915 		free(di->path);
916 		free(di->manufacturer);
917 		free(di->product);
918 		explicit_bzero(di, sizeof(*di));
919 		return FIDO_ERR_INTERNAL;
920 	}
921 	++(*olen);
922 
923 	return FIDO_OK;
924 }
925 
926 int
fido_winhello_open(fido_dev_t * dev)927 fido_winhello_open(fido_dev_t *dev)
928 {
929 	if (!webauthn_loaded && webauthn_load() < 0) {
930 		fido_log_debug("%s: webauthn_load", __func__);
931 		return FIDO_ERR_INTERNAL;
932 	}
933 	if (dev->flags != 0)
934 		return FIDO_ERR_INVALID_ARGUMENT;
935 	dev->attr.flags = FIDO_CAP_CBOR | FIDO_CAP_WINK;
936 	dev->flags = FIDO_DEV_WINHELLO | FIDO_DEV_CRED_PROT | FIDO_DEV_PIN_SET;
937 
938 	return FIDO_OK;
939 }
940 
941 int
fido_winhello_close(fido_dev_t * dev)942 fido_winhello_close(fido_dev_t *dev)
943 {
944 	memset(dev, 0, sizeof(*dev));
945 
946 	return FIDO_OK;
947 }
948 
949 int
fido_winhello_cancel(fido_dev_t * dev)950 fido_winhello_cancel(fido_dev_t *dev)
951 {
952 	(void)dev;
953 
954 	return FIDO_ERR_INTERNAL;
955 }
956 
957 int
fido_winhello_get_assert(fido_dev_t * dev,fido_assert_t * assert,const char * pin,int ms)958 fido_winhello_get_assert(fido_dev_t *dev, fido_assert_t *assert,
959     const char *pin, int ms)
960 {
961 	HWND			 w;
962 	struct winhello_assert	*ctx;
963 	int			 r = FIDO_ERR_INTERNAL;
964 
965 	(void)dev;
966 
967 	fido_assert_reset_rx(assert);
968 
969 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
970 		fido_log_debug("%s: calloc", __func__);
971 		goto fail;
972 	}
973 	if ((w = GetForegroundWindow()) == NULL) {
974 		fido_log_debug("%s: GetForegroundWindow", __func__);
975 		if ((w = GetTopWindow(NULL)) == NULL) {
976 			fido_log_debug("%s: GetTopWindow", __func__);
977 			goto fail;
978 		}
979 	}
980 	if ((r = translate_fido_assert(ctx, assert, pin, ms)) != FIDO_OK) {
981 		fido_log_debug("%s: translate_fido_assert", __func__);
982 		goto fail;
983 	}
984 	if ((r = winhello_get_assert(w, ctx)) != FIDO_OK) {
985 		fido_log_debug("%s: winhello_get_assert", __func__);
986 		goto fail;
987 	}
988 	if ((r = translate_winhello_assert(assert, ctx)) != FIDO_OK) {
989 		fido_log_debug("%s: translate_winhello_assert", __func__);
990 		goto fail;
991 	}
992 
993 fail:
994 	winhello_assert_free(ctx);
995 
996 	return r;
997 }
998 
999 int
fido_winhello_get_cbor_info(fido_dev_t * dev,fido_cbor_info_t * ci)1000 fido_winhello_get_cbor_info(fido_dev_t *dev, fido_cbor_info_t *ci)
1001 {
1002 	const char *v[3] = { "U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE" };
1003 	const char *e[2] = { "credProtect", "hmac-secret" };
1004 	const char *t[2] = { "nfc", "usb" };
1005 	const char *o[4] = { "rk", "up", "uv", "plat" };
1006 
1007 	(void)dev;
1008 
1009 	fido_cbor_info_reset(ci);
1010 
1011 	if (fido_str_array_pack(&ci->versions, v, nitems(v)) < 0 ||
1012 	    fido_str_array_pack(&ci->extensions, e, nitems(e)) < 0 ||
1013 	    fido_str_array_pack(&ci->transports, t, nitems(t)) < 0) {
1014 		fido_log_debug("%s: fido_str_array_pack", __func__);
1015 		return FIDO_ERR_INTERNAL;
1016 	}
1017 	if ((ci->options.name = calloc(nitems(o), sizeof(char *))) == NULL ||
1018 	    (ci->options.value = calloc(nitems(o), sizeof(bool))) == NULL) {
1019 		fido_log_debug("%s: calloc", __func__);
1020 		return FIDO_ERR_INTERNAL;
1021 	}
1022 	for (size_t i = 0; i < nitems(o); i++) {
1023 		if ((ci->options.name[i] = strdup(o[i])) == NULL) {
1024 			fido_log_debug("%s: strdup", __func__);
1025 			return FIDO_ERR_INTERNAL;
1026 		}
1027 		ci->options.value[i] = true;
1028 		ci->options.len++;
1029 	}
1030 
1031 	return FIDO_OK;
1032 }
1033 
1034 int
fido_winhello_make_cred(fido_dev_t * dev,fido_cred_t * cred,const char * pin,int ms)1035 fido_winhello_make_cred(fido_dev_t *dev, fido_cred_t *cred, const char *pin,
1036     int ms)
1037 {
1038 	HWND			 w;
1039 	struct winhello_cred	*ctx;
1040 	int			 r = FIDO_ERR_INTERNAL;
1041 
1042 	(void)dev;
1043 
1044 	fido_cred_reset_rx(cred);
1045 
1046 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
1047 		fido_log_debug("%s: calloc", __func__);
1048 		goto fail;
1049 	}
1050 	if ((w = GetForegroundWindow()) == NULL) {
1051 		fido_log_debug("%s: GetForegroundWindow", __func__);
1052 		if ((w = GetTopWindow(NULL)) == NULL) {
1053 			fido_log_debug("%s: GetTopWindow", __func__);
1054 			goto fail;
1055 		}
1056 	}
1057 	if ((r = translate_fido_cred(ctx, cred, pin, ms)) != FIDO_OK) {
1058 		fido_log_debug("%s: translate_fido_cred", __func__);
1059 		goto fail;
1060 	}
1061 	if ((r = winhello_make_cred(w, ctx)) != FIDO_OK) {
1062 		fido_log_debug("%s: winhello_make_cred", __func__);
1063 		goto fail;
1064 	}
1065 	if ((r = translate_winhello_cred(cred, ctx->att)) != FIDO_OK) {
1066 		fido_log_debug("%s: translate_winhello_cred", __func__);
1067 		goto fail;
1068 	}
1069 
1070 	r = FIDO_OK;
1071 fail:
1072 	winhello_cred_free(ctx);
1073 
1074 	return r;
1075 }
1076