1 /* 2 * Copyright (c) 2019-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 <fcntl.h> 11 #ifdef HAVE_UNISTD_H 12 #include <unistd.h> 13 #endif 14 #include <windows.h> 15 #include <setupapi.h> 16 #include <initguid.h> 17 #include <devpkey.h> 18 #include <devpropdef.h> 19 #include <hidclass.h> 20 #include <hidsdi.h> 21 #include <wchar.h> 22 23 #include "fido.h" 24 25 #if defined(__MINGW32__) && __MINGW64_VERSION_MAJOR < 6 26 WINSETUPAPI WINBOOL WINAPI SetupDiGetDevicePropertyW(HDEVINFO, 27 PSP_DEVINFO_DATA, const DEVPROPKEY *, DEVPROPTYPE *, PBYTE, 28 DWORD, PDWORD, DWORD); 29 #endif 30 31 #if defined(__MINGW32__) && __MINGW64_VERSION_MAJOR < 8 32 DEFINE_DEVPROPKEY(DEVPKEY_Device_Parent, 0x4340a6c5, 0x93fa, 0x4706, 0x97, 33 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7, 8); 34 #endif 35 36 struct hid_win { 37 HANDLE dev; 38 OVERLAPPED overlap; 39 int report_pending; 40 size_t report_in_len; 41 size_t report_out_len; 42 unsigned char report[1 + CTAP_MAX_REPORT_LEN]; 43 }; 44 45 static bool 46 is_fido(HANDLE dev) 47 { 48 PHIDP_PREPARSED_DATA data = NULL; 49 HIDP_CAPS caps; 50 int fido = 0; 51 52 if (HidD_GetPreparsedData(dev, &data) == false) { 53 fido_log_debug("%s: HidD_GetPreparsedData", __func__); 54 goto fail; 55 } 56 57 if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) { 58 fido_log_debug("%s: HidP_GetCaps", __func__); 59 goto fail; 60 } 61 62 fido = (uint16_t)caps.UsagePage == 0xf1d0; 63 fail: 64 if (data != NULL) 65 HidD_FreePreparsedData(data); 66 67 return (fido); 68 } 69 70 static int 71 get_report_len(HANDLE dev, int dir, size_t *report_len) 72 { 73 PHIDP_PREPARSED_DATA data = NULL; 74 HIDP_CAPS caps; 75 USHORT v; 76 int ok = -1; 77 78 if (HidD_GetPreparsedData(dev, &data) == false) { 79 fido_log_debug("%s: HidD_GetPreparsedData/%d", __func__, dir); 80 goto fail; 81 } 82 83 if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) { 84 fido_log_debug("%s: HidP_GetCaps/%d", __func__, dir); 85 goto fail; 86 } 87 88 if (dir == 0) 89 v = caps.InputReportByteLength; 90 else 91 v = caps.OutputReportByteLength; 92 93 if ((*report_len = (size_t)v) == 0) { 94 fido_log_debug("%s: report_len == 0", __func__); 95 goto fail; 96 } 97 98 ok = 0; 99 fail: 100 if (data != NULL) 101 HidD_FreePreparsedData(data); 102 103 return (ok); 104 } 105 106 static int 107 get_id(HANDLE dev, int16_t *vendor_id, int16_t *product_id) 108 { 109 HIDD_ATTRIBUTES attr; 110 111 attr.Size = sizeof(attr); 112 113 if (HidD_GetAttributes(dev, &attr) == false) { 114 fido_log_debug("%s: HidD_GetAttributes", __func__); 115 return (-1); 116 } 117 118 *vendor_id = (int16_t)attr.VendorID; 119 *product_id = (int16_t)attr.ProductID; 120 121 return (0); 122 } 123 124 static int 125 get_manufacturer(HANDLE dev, char **manufacturer) 126 { 127 wchar_t buf[512]; 128 int utf8_len; 129 int ok = -1; 130 131 *manufacturer = NULL; 132 133 if (HidD_GetManufacturerString(dev, &buf, sizeof(buf)) == false) { 134 fido_log_debug("%s: HidD_GetManufacturerString", __func__); 135 goto fail; 136 } 137 138 if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, 139 -1, NULL, 0, NULL, NULL)) <= 0 || utf8_len > 128) { 140 fido_log_debug("%s: WideCharToMultiByte", __func__); 141 goto fail; 142 } 143 144 if ((*manufacturer = malloc((size_t)utf8_len)) == NULL) { 145 fido_log_debug("%s: malloc", __func__); 146 goto fail; 147 } 148 149 if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, -1, 150 *manufacturer, utf8_len, NULL, NULL) != utf8_len) { 151 fido_log_debug("%s: WideCharToMultiByte", __func__); 152 goto fail; 153 } 154 155 ok = 0; 156 fail: 157 if (ok < 0) { 158 free(*manufacturer); 159 *manufacturer = NULL; 160 } 161 162 return (ok); 163 } 164 165 static int 166 get_product(HANDLE dev, char **product) 167 { 168 wchar_t buf[512]; 169 int utf8_len; 170 int ok = -1; 171 172 *product = NULL; 173 174 if (HidD_GetProductString(dev, &buf, sizeof(buf)) == false) { 175 fido_log_debug("%s: HidD_GetProductString", __func__); 176 goto fail; 177 } 178 179 if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, 180 -1, NULL, 0, NULL, NULL)) <= 0 || utf8_len > 128) { 181 fido_log_debug("%s: WideCharToMultiByte", __func__); 182 goto fail; 183 } 184 185 if ((*product = malloc((size_t)utf8_len)) == NULL) { 186 fido_log_debug("%s: malloc", __func__); 187 goto fail; 188 } 189 190 if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, -1, 191 *product, utf8_len, NULL, NULL) != utf8_len) { 192 fido_log_debug("%s: WideCharToMultiByte", __func__); 193 goto fail; 194 } 195 196 ok = 0; 197 fail: 198 if (ok < 0) { 199 free(*product); 200 *product = NULL; 201 } 202 203 return (ok); 204 } 205 206 static char * 207 get_path(HDEVINFO devinfo, SP_DEVICE_INTERFACE_DATA *ifdata) 208 { 209 SP_DEVICE_INTERFACE_DETAIL_DATA_A *ifdetail = NULL; 210 char *path = NULL; 211 DWORD len = 0; 212 213 /* 214 * "Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail 215 * with a NULL DeviceInterfaceDetailData pointer, a 216 * DeviceInterfaceDetailDataSize of zero, and a valid RequiredSize 217 * variable. In response to such a call, this function returns the 218 * required buffer size at RequiredSize and fails with GetLastError 219 * returning ERROR_INSUFFICIENT_BUFFER." 220 */ 221 if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, NULL, 0, &len, 222 NULL) != false || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { 223 fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 1", 224 __func__); 225 goto fail; 226 } 227 228 if ((ifdetail = malloc(len)) == NULL) { 229 fido_log_debug("%s: malloc", __func__); 230 goto fail; 231 } 232 233 ifdetail->cbSize = sizeof(*ifdetail); 234 235 if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, ifdetail, len, 236 NULL, NULL) == false) { 237 fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 2", 238 __func__); 239 goto fail; 240 } 241 242 if ((path = strdup(ifdetail->DevicePath)) == NULL) { 243 fido_log_debug("%s: strdup", __func__); 244 goto fail; 245 } 246 247 fail: 248 free(ifdetail); 249 250 return (path); 251 } 252 253 #ifndef FIDO_HID_ANY 254 static bool 255 hid_ok(HDEVINFO devinfo, DWORD idx) 256 { 257 SP_DEVINFO_DATA devinfo_data; 258 wchar_t *parent = NULL; 259 DWORD parent_type = DEVPROP_TYPE_STRING; 260 DWORD len = 0; 261 bool ok = false; 262 263 memset(&devinfo_data, 0, sizeof(devinfo_data)); 264 devinfo_data.cbSize = sizeof(devinfo_data); 265 266 if (SetupDiEnumDeviceInfo(devinfo, idx, &devinfo_data) == false) { 267 fido_log_debug("%s: SetupDiEnumDeviceInfo", __func__); 268 goto fail; 269 } 270 271 if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data, 272 &DEVPKEY_Device_Parent, &parent_type, NULL, 0, &len, 0) != false || 273 GetLastError() != ERROR_INSUFFICIENT_BUFFER) { 274 fido_log_debug("%s: SetupDiGetDevicePropertyW 1", __func__); 275 goto fail; 276 } 277 278 if ((parent = malloc(len)) == NULL) { 279 fido_log_debug("%s: malloc", __func__); 280 goto fail; 281 } 282 283 if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data, 284 &DEVPKEY_Device_Parent, &parent_type, (PBYTE)parent, len, NULL, 285 0) == false) { 286 fido_log_debug("%s: SetupDiGetDevicePropertyW 2", __func__); 287 goto fail; 288 } 289 290 ok = wcsncmp(parent, L"USB\\", 4) == 0; 291 fail: 292 free(parent); 293 294 return (ok); 295 } 296 #endif 297 298 static int 299 copy_info(fido_dev_info_t *di, HDEVINFO devinfo, DWORD idx, 300 SP_DEVICE_INTERFACE_DATA *ifdata) 301 { 302 HANDLE dev = INVALID_HANDLE_VALUE; 303 int ok = -1; 304 305 memset(di, 0, sizeof(*di)); 306 307 if ((di->path = get_path(devinfo, ifdata)) == NULL) { 308 fido_log_debug("%s: get_path", __func__); 309 goto fail; 310 } 311 312 fido_log_debug("%s: path=%s", __func__, di->path); 313 314 #ifndef FIDO_HID_ANY 315 if (hid_ok(devinfo, idx) == false) { 316 fido_log_debug("%s: hid_ok", __func__); 317 goto fail; 318 } 319 #endif 320 321 dev = CreateFileA(di->path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 322 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 323 if (dev == INVALID_HANDLE_VALUE) { 324 fido_log_debug("%s: CreateFileA", __func__); 325 goto fail; 326 } 327 328 if (is_fido(dev) == false) { 329 fido_log_debug("%s: is_fido", __func__); 330 goto fail; 331 } 332 333 if (get_id(dev, &di->vendor_id, &di->product_id) < 0) { 334 fido_log_debug("%s: get_id", __func__); 335 goto fail; 336 } 337 338 if (get_manufacturer(dev, &di->manufacturer) < 0) { 339 fido_log_debug("%s: get_manufacturer", __func__); 340 di->manufacturer = strdup(""); 341 } 342 343 if (get_product(dev, &di->product) < 0) { 344 fido_log_debug("%s: get_product", __func__); 345 di->product = strdup(""); 346 } 347 348 if (di->manufacturer == NULL || di->product == NULL) { 349 fido_log_debug("%s: manufacturer/product", __func__); 350 goto fail; 351 } 352 353 ok = 0; 354 fail: 355 if (dev != INVALID_HANDLE_VALUE) 356 CloseHandle(dev); 357 358 if (ok < 0) { 359 free(di->path); 360 free(di->manufacturer); 361 free(di->product); 362 explicit_bzero(di, sizeof(*di)); 363 } 364 365 return (ok); 366 } 367 368 int 369 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) 370 { 371 GUID hid_guid = GUID_DEVINTERFACE_HID; 372 HDEVINFO devinfo = INVALID_HANDLE_VALUE; 373 SP_DEVICE_INTERFACE_DATA ifdata; 374 DWORD idx; 375 int r = FIDO_ERR_INTERNAL; 376 377 *olen = 0; 378 379 if (ilen == 0) 380 return (FIDO_OK); /* nothing to do */ 381 if (devlist == NULL) 382 return (FIDO_ERR_INVALID_ARGUMENT); 383 384 if ((devinfo = SetupDiGetClassDevsA(&hid_guid, NULL, NULL, 385 DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)) == INVALID_HANDLE_VALUE) { 386 fido_log_debug("%s: SetupDiGetClassDevsA", __func__); 387 goto fail; 388 } 389 390 ifdata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); 391 392 for (idx = 0; SetupDiEnumDeviceInterfaces(devinfo, NULL, &hid_guid, 393 idx, &ifdata) == true; idx++) { 394 if (copy_info(&devlist[*olen], devinfo, idx, &ifdata) == 0) { 395 devlist[*olen].io = (fido_dev_io_t) { 396 fido_hid_open, 397 fido_hid_close, 398 fido_hid_read, 399 fido_hid_write, 400 }; 401 if (++(*olen) == ilen) 402 break; 403 } 404 } 405 406 r = FIDO_OK; 407 fail: 408 if (devinfo != INVALID_HANDLE_VALUE) 409 SetupDiDestroyDeviceInfoList(devinfo); 410 411 return (r); 412 } 413 414 void * 415 fido_hid_open(const char *path) 416 { 417 struct hid_win *ctx; 418 419 if ((ctx = calloc(1, sizeof(*ctx))) == NULL) 420 return (NULL); 421 422 ctx->dev = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, 423 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 424 FILE_FLAG_OVERLAPPED, NULL); 425 426 if (ctx->dev == INVALID_HANDLE_VALUE) { 427 free(ctx); 428 return (NULL); 429 } 430 431 if ((ctx->overlap.hEvent = CreateEventA(NULL, FALSE, FALSE, 432 NULL)) == NULL) { 433 fido_log_debug("%s: CreateEventA", __func__); 434 fido_hid_close(ctx); 435 return (NULL); 436 } 437 438 if (get_report_len(ctx->dev, 0, &ctx->report_in_len) < 0 || 439 get_report_len(ctx->dev, 1, &ctx->report_out_len) < 0) { 440 fido_log_debug("%s: get_report_len", __func__); 441 fido_hid_close(ctx); 442 return (NULL); 443 } 444 445 return (ctx); 446 } 447 448 void 449 fido_hid_close(void *handle) 450 { 451 struct hid_win *ctx = handle; 452 453 if (ctx->overlap.hEvent != NULL) { 454 if (ctx->report_pending) { 455 fido_log_debug("%s: report_pending", __func__); 456 if (CancelIoEx(ctx->dev, &ctx->overlap) == 0) 457 fido_log_debug("%s CancelIoEx: 0x%lx", 458 __func__, (u_long)GetLastError()); 459 } 460 CloseHandle(ctx->overlap.hEvent); 461 } 462 463 explicit_bzero(ctx->report, sizeof(ctx->report)); 464 CloseHandle(ctx->dev); 465 free(ctx); 466 } 467 468 int 469 fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) 470 { 471 (void)handle; 472 (void)sigmask; 473 474 return (FIDO_ERR_INTERNAL); 475 } 476 477 int 478 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) 479 { 480 struct hid_win *ctx = handle; 481 DWORD n; 482 483 if (len != ctx->report_in_len - 1 || len > sizeof(ctx->report) - 1) { 484 fido_log_debug("%s: len %zu", __func__, len); 485 return (-1); 486 } 487 488 if (ctx->report_pending == 0) { 489 memset(&ctx->report, 0, sizeof(ctx->report)); 490 ResetEvent(ctx->overlap.hEvent); 491 if (ReadFile(ctx->dev, ctx->report, (DWORD)(len + 1), &n, 492 &ctx->overlap) == 0 && GetLastError() != ERROR_IO_PENDING) { 493 CancelIo(ctx->dev); 494 fido_log_debug("%s: ReadFile", __func__); 495 return (-1); 496 } 497 ctx->report_pending = 1; 498 } 499 500 if (ms > -1 && WaitForSingleObject(ctx->overlap.hEvent, 501 (DWORD)ms) != WAIT_OBJECT_0) 502 return (0); 503 504 ctx->report_pending = 0; 505 506 if (GetOverlappedResult(ctx->dev, &ctx->overlap, &n, TRUE) == 0) { 507 fido_log_debug("%s: GetOverlappedResult", __func__); 508 return (-1); 509 } 510 511 if (n != len + 1) { 512 fido_log_debug("%s: expected %zu, got %zu", __func__, 513 len + 1, (size_t)n); 514 return (-1); 515 } 516 517 memcpy(buf, ctx->report + 1, len); 518 explicit_bzero(ctx->report, sizeof(ctx->report)); 519 520 return ((int)len); 521 } 522 523 int 524 fido_hid_write(void *handle, const unsigned char *buf, size_t len) 525 { 526 struct hid_win *ctx = handle; 527 OVERLAPPED overlap; 528 DWORD n; 529 530 memset(&overlap, 0, sizeof(overlap)); 531 532 if (len != ctx->report_out_len) { 533 fido_log_debug("%s: len %zu", __func__, len); 534 return (-1); 535 } 536 537 if (WriteFile(ctx->dev, buf, (DWORD)len, NULL, &overlap) == 0 && 538 GetLastError() != ERROR_IO_PENDING) { 539 fido_log_debug("%s: WriteFile", __func__); 540 return (-1); 541 } 542 543 if (GetOverlappedResult(ctx->dev, &overlap, &n, TRUE) == 0) { 544 fido_log_debug("%s: GetOverlappedResult", __func__); 545 return (-1); 546 } 547 548 if (n != len) { 549 fido_log_debug("%s: expected %zu, got %zu", __func__, len, 550 (size_t)n); 551 return (-1); 552 } 553 554 return ((int)len); 555 } 556 557 size_t 558 fido_hid_report_in_len(void *handle) 559 { 560 struct hid_win *ctx = handle; 561 562 return (ctx->report_in_len - 1); 563 } 564 565 size_t 566 fido_hid_report_out_len(void *handle) 567 { 568 struct hid_win *ctx = handle; 569 570 return (ctx->report_out_len - 1); 571 } 572