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