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
35struct 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
43struct 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
58typedef DWORD	WINAPI	webauthn_get_api_version_t(void);
59typedef PCWSTR	WINAPI	webauthn_strerr_t(HRESULT);
60typedef HRESULT	WINAPI	webauthn_get_assert_t(HWND, LPCWSTR,
61			    PCWEBAUTHN_CLIENT_DATA,
62			    PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS,
63			    PWEBAUTHN_ASSERTION *);
64typedef 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 *);
71typedef void	WINAPI	webauthn_free_assert_t(PWEBAUTHN_ASSERTION);
72typedef void	WINAPI	webauthn_free_attest_t(PWEBAUTHN_CREDENTIAL_ATTESTATION);
73
74static TLS BOOL				 webauthn_loaded;
75static TLS HMODULE			 webauthn_handle;
76static TLS webauthn_get_api_version_t	*webauthn_get_api_version;
77static TLS webauthn_strerr_t		*webauthn_strerr;
78static TLS webauthn_get_assert_t	*webauthn_get_assert;
79static TLS webauthn_make_cred_t		*webauthn_make_cred;
80static TLS webauthn_free_assert_t	*webauthn_free_assert;
81static TLS webauthn_free_attest_t	*webauthn_free_attest;
82
83static int
84webauthn_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;
146fail:
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
160static wchar_t *
161to_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
188static int
189to_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
210static int
211pack_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
229static int
230pack_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
261static int
262set_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
282static int
283set_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
305static int
306pack_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
322static int
323pack_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
357static int
358pack_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
386static int
387pack_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
445static int
446pack_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
481static int
482pack_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
502static void
503unpack_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
518static int
519unpack_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
533static int
534unpack_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
548static int
549unpack_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
560static int
561unpack_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
574static int
575unpack_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
606static int
607translate_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
650static int
651translate_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
693static int
694translate_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
742static int
743decode_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;
780fail:
781	free(name);
782
783	return (ok);
784}
785
786static int
787translate_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;
811fail:
812	if (item != NULL)
813		cbor_decref(&item);
814
815	return r;
816}
817
818static int
819winhello_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
834static int
835winhello_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
850static void
851winhello_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
867static void
868winhello_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
890int
891fido_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
926int
927fido_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
941int
942fido_winhello_close(fido_dev_t *dev)
943{
944	memset(dev, 0, sizeof(*dev));
945
946	return FIDO_OK;
947}
948
949int
950fido_winhello_cancel(fido_dev_t *dev)
951{
952	(void)dev;
953
954	return FIDO_ERR_INTERNAL;
955}
956
957int
958fido_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
993fail:
994	winhello_assert_free(ctx);
995
996	return r;
997}
998
999int
1000fido_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
1034int
1035fido_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;
1071fail:
1072	winhello_cred_free(ctx);
1073
1074	return r;
1075}
1076