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