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