Home | History | Annotate | Line # | Download | only in src
      1 /*
      2  * Copyright (c) 2021-2024 Yubico AB. All rights reserved.
      3  * Use of this source code is governed by a BSD-style
      4  * license that can be found in the LICENSE file.
      5  * SPDX-License-Identifier: BSD-2-Clause
      6  */
      7 
      8 #include <sys/types.h>
      9 
     10 #include <stdlib.h>
     11 #include <windows.h>
     12 
     13 #include "fido.h"
     14 #include "webauthn.h"
     15 
     16 #ifndef NTE_INVALID_PARAMETER
     17 #define NTE_INVALID_PARAMETER	_HRESULT_TYPEDEF_(0x80090027)
     18 #endif
     19 #ifndef NTE_NOT_SUPPORTED
     20 #define NTE_NOT_SUPPORTED	_HRESULT_TYPEDEF_(0x80090029)
     21 #endif
     22 #ifndef NTE_DEVICE_NOT_FOUND
     23 #define NTE_DEVICE_NOT_FOUND	_HRESULT_TYPEDEF_(0x80090035)
     24 #endif
     25 #ifndef NTE_USER_CANCELLED
     26 #define NTE_USER_CANCELLED	_HRESULT_TYPEDEF_(0x80090036L)
     27 #endif
     28 
     29 #define MAXCHARS	128
     30 #define MAXCREDS	128
     31 #define MAXMSEC		6000 * 1000
     32 #define VENDORID	0x045e
     33 #define PRODID		0x0001
     34 
     35 struct winhello_assert {
     36 	WEBAUTHN_CLIENT_DATA				 cd;
     37 	WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS	 opt;
     38 	WEBAUTHN_ASSERTION				*assert;
     39 	wchar_t						*rp_id;
     40 	wchar_t						*appid;
     41 };
     42 
     43 struct winhello_cred {
     44 	WEBAUTHN_RP_ENTITY_INFORMATION			 rp;
     45 	WEBAUTHN_USER_ENTITY_INFORMATION		 user;
     46 	WEBAUTHN_COSE_CREDENTIAL_PARAMETER		 alg;
     47 	WEBAUTHN_COSE_CREDENTIAL_PARAMETERS		 cose;
     48 	WEBAUTHN_CLIENT_DATA				 cd;
     49 	WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS	 opt;
     50 	WEBAUTHN_CREDENTIAL_ATTESTATION			*att;
     51 	wchar_t						*rp_id;
     52 	wchar_t						*rp_name;
     53 	wchar_t						*user_name;
     54 	wchar_t						*user_icon;
     55 	wchar_t						*display_name;
     56 };
     57 
     58 typedef DWORD	WINAPI	webauthn_get_api_version_t(void);
     59 typedef PCWSTR	WINAPI	webauthn_strerr_t(HRESULT);
     60 typedef HRESULT	WINAPI	webauthn_get_assert_t(HWND, LPCWSTR,
     61 			    PCWEBAUTHN_CLIENT_DATA,
     62 			    PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS,
     63 			    PWEBAUTHN_ASSERTION *);
     64 typedef HRESULT	WINAPI	webauthn_make_cred_t(HWND,
     65 			    PCWEBAUTHN_RP_ENTITY_INFORMATION,
     66 			    PCWEBAUTHN_USER_ENTITY_INFORMATION,
     67 			    PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS,
     68 			    PCWEBAUTHN_CLIENT_DATA,
     69 			    PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS,
     70 			    PWEBAUTHN_CREDENTIAL_ATTESTATION *);
     71 typedef void	WINAPI	webauthn_free_assert_t(PWEBAUTHN_ASSERTION);
     72 typedef void	WINAPI	webauthn_free_attest_t(PWEBAUTHN_CREDENTIAL_ATTESTATION);
     73 
     74 static TLS BOOL				 webauthn_loaded;
     75 static TLS HMODULE			 webauthn_handle;
     76 static TLS webauthn_get_api_version_t	*webauthn_get_api_version;
     77 static TLS webauthn_strerr_t		*webauthn_strerr;
     78 static TLS webauthn_get_assert_t	*webauthn_get_assert;
     79 static TLS webauthn_make_cred_t		*webauthn_make_cred;
     80 static TLS webauthn_free_assert_t	*webauthn_free_assert;
     81 static TLS webauthn_free_attest_t	*webauthn_free_attest;
     82 
     83 static int
     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 *
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    694 translate_fido_cred(struct winhello_cred *ctx, const fido_cred_t *cred,
    695     const char *pin, int ms)
    696 {
    697 	WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *opt;
    698 
    699 	if (pack_rp(&ctx->rp_id, &ctx->rp_name, &ctx->rp, &cred->rp) < 0) {
    700 		fido_log_debug("%s: pack_rp", __func__);
    701 		return FIDO_ERR_INTERNAL;
    702 	}
    703 	if (pack_user(&ctx->user_name, &ctx->user_icon, &ctx->display_name,
    704 	    &ctx->user, &cred->user) < 0) {
    705 		fido_log_debug("%s: pack_user", __func__);
    706 		return FIDO_ERR_INTERNAL;
    707 	}
    708 	if (pack_cose(&ctx->alg, &ctx->cose, cred->type) < 0) {
    709 		fido_log_debug("%s: pack_cose", __func__);
    710 		return FIDO_ERR_INTERNAL;
    711 	}
    712 	if (pack_cd(&ctx->cd, &cred->cd) < 0) {
    713 		fido_log_debug("%s: pack_cd", __func__);
    714 		return FIDO_ERR_INTERNAL;
    715 	}
    716 	/* options */
    717 	opt = &ctx->opt;
    718 	opt->dwVersion = WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1;
    719 	opt->dwTimeoutMilliseconds = ms < 0 ? MAXMSEC : (DWORD)ms;
    720 	opt->dwAttestationConveyancePreference =
    721 	    WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT;
    722 	if (pack_credlist(&opt->CredentialList, &cred->excl) < 0) {
    723 		fido_log_debug("%s: pack_credlist", __func__);
    724 		return FIDO_ERR_INTERNAL;
    725 	}
    726 	if (pack_cred_ext(&opt->Extensions, &cred->ext) < 0) {
    727 		fido_log_debug("%s: pack_cred_ext", __func__);
    728 		return FIDO_ERR_UNSUPPORTED_EXTENSION;
    729 	}
    730 	if (set_cred_uv(&opt->dwUserVerificationRequirement, (cred->ext.mask &
    731 	    FIDO_EXT_CRED_PROTECT) ? FIDO_OPT_TRUE : cred->uv, pin) < 0) {
    732 		fido_log_debug("%s: set_cred_uv", __func__);
    733 		return FIDO_ERR_INTERNAL;
    734 	}
    735 	if (cred->rk == FIDO_OPT_TRUE) {
    736 		opt->bRequireResidentKey = true;
    737 	}
    738 	if (cred->ea.mode != 0) {
    739 		opt->dwVersion = WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4;
    740 		opt->dwEnterpriseAttestation = (DWORD)cred->ea.mode;
    741 	}
    742 
    743 	return FIDO_OK;
    744 }
    745 
    746 static int
    747 translate_winhello_cred(fido_cred_t *cred,
    748     const WEBAUTHN_CREDENTIAL_ATTESTATION *att)
    749 {
    750 	cbor_item_t *item = NULL;
    751 	struct cbor_load_result cbor;
    752 	int r = FIDO_ERR_INTERNAL;
    753 
    754 	if (att->pbAttestationObject == NULL) {
    755 		fido_log_debug("%s: pbAttestationObject", __func__);
    756 		goto fail;
    757 	}
    758 	if ((item = cbor_load(att->pbAttestationObject,
    759 	    att->cbAttestationObject, &cbor)) == NULL) {
    760 		fido_log_debug("%s: cbor_load", __func__);
    761 		goto fail;
    762 	}
    763 	if (cbor_decode_attobj(item, cred) != 0) {
    764 		fido_log_debug("%s: cbor_decode_attobj", __func__);
    765 		goto fail;
    766 	}
    767 	if (att->dwVersion >= WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4)
    768 		cred->ea.att = att->bEpAtt;
    769 
    770 	r = FIDO_OK;
    771 fail:
    772 	if (item != NULL)
    773 		cbor_decref(&item);
    774 
    775 	return r;
    776 }
    777 
    778 static int
    779 winhello_get_assert(HWND w, struct winhello_assert *ctx)
    780 {
    781 	HRESULT hr;
    782 	int r = FIDO_OK;
    783 
    784 	if ((hr = webauthn_get_assert(w, ctx->rp_id, &ctx->cd, &ctx->opt,
    785 	    &ctx->assert)) != S_OK) {
    786 		r = to_fido(hr);
    787 		fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr),
    788 		    fido_strerr(r));
    789 	}
    790 
    791 	return r;
    792 }
    793 
    794 static int
    795 winhello_make_cred(HWND w, struct winhello_cred *ctx)
    796 {
    797 	HRESULT hr;
    798 	int r = FIDO_OK;
    799 
    800 	if ((hr = webauthn_make_cred(w, &ctx->rp, &ctx->user, &ctx->cose,
    801 	    &ctx->cd, &ctx->opt, &ctx->att)) != S_OK) {
    802 		r = to_fido(hr);
    803 		fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr),
    804 		    fido_strerr(r));
    805 	}
    806 
    807 	return r;
    808 }
    809 
    810 static void
    811 winhello_assert_free(struct winhello_assert *ctx)
    812 {
    813 	if (ctx == NULL)
    814 		return;
    815 	if (ctx->assert != NULL)
    816 		webauthn_free_assert(ctx->assert);
    817 
    818 	free(ctx->rp_id);
    819 	free(ctx->appid);
    820 	free(ctx->opt.CredentialList.pCredentials);
    821 	if (ctx->opt.pHmacSecretSaltValues != NULL)
    822 		free(ctx->opt.pHmacSecretSaltValues->pGlobalHmacSalt);
    823 	free(ctx->opt.pHmacSecretSaltValues);
    824 	free(ctx);
    825 }
    826 
    827 static void
    828 winhello_cred_free(struct winhello_cred *ctx)
    829 {
    830 	if (ctx == NULL)
    831 		return;
    832 	if (ctx->att != NULL)
    833 		webauthn_free_attest(ctx->att);
    834 
    835 	free(ctx->rp_id);
    836 	free(ctx->rp_name);
    837 	free(ctx->user_name);
    838 	free(ctx->user_icon);
    839 	free(ctx->display_name);
    840 	free(ctx->opt.CredentialList.pCredentials);
    841 	for (size_t i = 0; i < ctx->opt.Extensions.cExtensions; i++) {
    842 		WEBAUTHN_EXTENSION *e;
    843 		e = &ctx->opt.Extensions.pExtensions[i];
    844 		free(e->pvExtension);
    845 	}
    846 	free(ctx->opt.Extensions.pExtensions);
    847 	free(ctx);
    848 }
    849 
    850 int
    851 fido_winhello_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
    852 {
    853 	fido_dev_info_t *di;
    854 
    855 	if (ilen == 0) {
    856 		return FIDO_OK;
    857 	}
    858 	if (devlist == NULL) {
    859 		return FIDO_ERR_INVALID_ARGUMENT;
    860 	}
    861 	if (!webauthn_loaded && webauthn_load() < 0) {
    862 		fido_log_debug("%s: webauthn_load", __func__);
    863 		return FIDO_OK; /* not an error */
    864 	}
    865 
    866 	di = &devlist[*olen];
    867 	memset(di, 0, sizeof(*di));
    868 	di->path = strdup(FIDO_WINHELLO_PATH);
    869 	di->manufacturer = strdup("Microsoft Corporation");
    870 	di->product = strdup("Windows Hello");
    871 	di->vendor_id = VENDORID;
    872 	di->product_id = PRODID;
    873 	if (di->path == NULL || di->manufacturer == NULL ||
    874 	    di->product == NULL) {
    875 		free(di->path);
    876 		free(di->manufacturer);
    877 		free(di->product);
    878 		explicit_bzero(di, sizeof(*di));
    879 		return FIDO_ERR_INTERNAL;
    880 	}
    881 	++(*olen);
    882 
    883 	return FIDO_OK;
    884 }
    885 
    886 int
    887 fido_winhello_open(fido_dev_t *dev)
    888 {
    889 	if (!webauthn_loaded && webauthn_load() < 0) {
    890 		fido_log_debug("%s: webauthn_load", __func__);
    891 		return FIDO_ERR_INTERNAL;
    892 	}
    893 	if (dev->flags != 0)
    894 		return FIDO_ERR_INVALID_ARGUMENT;
    895 	dev->attr.flags = FIDO_CAP_CBOR | FIDO_CAP_WINK;
    896 	dev->flags = FIDO_DEV_WINHELLO | FIDO_DEV_CRED_PROT | FIDO_DEV_PIN_SET;
    897 
    898 	return FIDO_OK;
    899 }
    900 
    901 int
    902 fido_winhello_close(fido_dev_t *dev)
    903 {
    904 	memset(dev, 0, sizeof(*dev));
    905 
    906 	return FIDO_OK;
    907 }
    908 
    909 int
    910 fido_winhello_cancel(fido_dev_t *dev)
    911 {
    912 	(void)dev;
    913 
    914 	return FIDO_ERR_INTERNAL;
    915 }
    916 
    917 int
    918 fido_winhello_get_assert(fido_dev_t *dev, fido_assert_t *assert,
    919     const char *pin, int ms)
    920 {
    921 	HWND			 w;
    922 	struct winhello_assert	*ctx;
    923 	int			 r = FIDO_ERR_INTERNAL;
    924 
    925 	(void)dev;
    926 
    927 	fido_assert_reset_rx(assert);
    928 
    929 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
    930 		fido_log_debug("%s: calloc", __func__);
    931 		goto fail;
    932 	}
    933 	if ((w = GetForegroundWindow()) == NULL) {
    934 		fido_log_debug("%s: GetForegroundWindow", __func__);
    935 		if ((w = GetTopWindow(NULL)) == NULL) {
    936 			fido_log_debug("%s: GetTopWindow", __func__);
    937 			goto fail;
    938 		}
    939 	}
    940 	if ((r = translate_fido_assert(ctx, assert, pin, ms)) != FIDO_OK) {
    941 		fido_log_debug("%s: translate_fido_assert", __func__);
    942 		goto fail;
    943 	}
    944 	if ((r = winhello_get_assert(w, ctx)) != FIDO_OK) {
    945 		fido_log_debug("%s: winhello_get_assert", __func__);
    946 		goto fail;
    947 	}
    948 	if ((r = translate_winhello_assert(assert, ctx)) != FIDO_OK) {
    949 		fido_log_debug("%s: translate_winhello_assert", __func__);
    950 		goto fail;
    951 	}
    952 
    953 fail:
    954 	winhello_assert_free(ctx);
    955 
    956 	return r;
    957 }
    958 
    959 int
    960 fido_winhello_get_cbor_info(fido_dev_t *dev, fido_cbor_info_t *ci)
    961 {
    962 	const char *v[3] = { "U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE" };
    963 	const char *e[2] = { "credProtect", "hmac-secret" };
    964 	const char *t[2] = { "nfc", "usb" };
    965 	const char *o[4] = { "rk", "up", "uv", "plat" };
    966 
    967 	(void)dev;
    968 
    969 	fido_cbor_info_reset(ci);
    970 
    971 	if (fido_str_array_pack(&ci->versions, v, nitems(v)) < 0 ||
    972 	    fido_str_array_pack(&ci->extensions, e, nitems(e)) < 0 ||
    973 	    fido_str_array_pack(&ci->transports, t, nitems(t)) < 0) {
    974 		fido_log_debug("%s: fido_str_array_pack", __func__);
    975 		return FIDO_ERR_INTERNAL;
    976 	}
    977 	if ((ci->options.name = calloc(nitems(o), sizeof(char *))) == NULL ||
    978 	    (ci->options.value = calloc(nitems(o), sizeof(bool))) == NULL) {
    979 		fido_log_debug("%s: calloc", __func__);
    980 		return FIDO_ERR_INTERNAL;
    981 	}
    982 	for (size_t i = 0; i < nitems(o); i++) {
    983 		if ((ci->options.name[i] = strdup(o[i])) == NULL) {
    984 			fido_log_debug("%s: strdup", __func__);
    985 			return FIDO_ERR_INTERNAL;
    986 		}
    987 		ci->options.value[i] = true;
    988 		ci->options.len++;
    989 	}
    990 
    991 	return FIDO_OK;
    992 }
    993 
    994 int
    995 fido_winhello_make_cred(fido_dev_t *dev, fido_cred_t *cred, const char *pin,
    996     int ms)
    997 {
    998 	HWND			 w;
    999 	struct winhello_cred	*ctx;
   1000 	int			 r = FIDO_ERR_INTERNAL;
   1001 
   1002 	(void)dev;
   1003 
   1004 	fido_cred_reset_rx(cred);
   1005 
   1006 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
   1007 		fido_log_debug("%s: calloc", __func__);
   1008 		goto fail;
   1009 	}
   1010 	if ((w = GetForegroundWindow()) == NULL) {
   1011 		fido_log_debug("%s: GetForegroundWindow", __func__);
   1012 		if ((w = GetTopWindow(NULL)) == NULL) {
   1013 			fido_log_debug("%s: GetTopWindow", __func__);
   1014 			goto fail;
   1015 		}
   1016 	}
   1017 	if ((r = translate_fido_cred(ctx, cred, pin, ms)) != FIDO_OK) {
   1018 		fido_log_debug("%s: translate_fido_cred", __func__);
   1019 		goto fail;
   1020 	}
   1021 	if ((r = winhello_make_cred(w, ctx)) != FIDO_OK) {
   1022 		fido_log_debug("%s: winhello_make_cred", __func__);
   1023 		goto fail;
   1024 	}
   1025 	if ((r = translate_winhello_cred(cred, ctx->att)) != FIDO_OK) {
   1026 		fido_log_debug("%s: translate_winhello_cred", __func__);
   1027 		goto fail;
   1028 	}
   1029 
   1030 	r = FIDO_OK;
   1031 fail:
   1032 	winhello_cred_free(ctx);
   1033 
   1034 	return r;
   1035 }
   1036