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