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