1 /* 2 * Copyright (c) 2019 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_int(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_str(HANDLE dev, char **manufacturer, char **product) 125 { 126 wchar_t buf[512]; 127 int utf8_len; 128 int ok = -1; 129 130 *manufacturer = NULL; 131 *product = 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 if (HidD_GetProductString(dev, &buf, sizeof(buf)) == false) { 156 fido_log_debug("%s: HidD_GetProductString", __func__); 157 goto fail; 158 } 159 160 if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, 161 -1, NULL, 0, NULL, NULL)) <= 0 || utf8_len > 128) { 162 fido_log_debug("%s: WideCharToMultiByte", __func__); 163 goto fail; 164 } 165 166 if ((*product = malloc((size_t)utf8_len)) == NULL) { 167 fido_log_debug("%s: malloc", __func__); 168 goto fail; 169 } 170 171 if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, -1, 172 *product, utf8_len, NULL, NULL) != utf8_len) { 173 fido_log_debug("%s: WideCharToMultiByte", __func__); 174 goto fail; 175 } 176 177 ok = 0; 178 fail: 179 if (ok < 0) { 180 free(*manufacturer); 181 free(*product); 182 *manufacturer = NULL; 183 *product = NULL; 184 } 185 186 return (ok); 187 } 188 189 static char * 190 get_path(HDEVINFO devinfo, SP_DEVICE_INTERFACE_DATA *ifdata) 191 { 192 SP_DEVICE_INTERFACE_DETAIL_DATA_A *ifdetail = NULL; 193 char *path = NULL; 194 DWORD len = 0; 195 196 /* 197 * "Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail 198 * with a NULL DeviceInterfaceDetailData pointer, a 199 * DeviceInterfaceDetailDataSize of zero, and a valid RequiredSize 200 * variable. In response to such a call, this function returns the 201 * required buffer size at RequiredSize and fails with GetLastError 202 * returning ERROR_INSUFFICIENT_BUFFER." 203 */ 204 if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, NULL, 0, &len, 205 NULL) != false || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { 206 fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 1", 207 __func__); 208 goto fail; 209 } 210 211 if ((ifdetail = malloc(len)) == NULL) { 212 fido_log_debug("%s: malloc", __func__); 213 goto fail; 214 } 215 216 ifdetail->cbSize = sizeof(*ifdetail); 217 218 if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, ifdetail, len, 219 NULL, NULL) == false) { 220 fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 2", 221 __func__); 222 goto fail; 223 } 224 225 if ((path = strdup(ifdetail->DevicePath)) == NULL) { 226 fido_log_debug("%s: strdup", __func__); 227 goto fail; 228 } 229 230 fail: 231 free(ifdetail); 232 233 return (path); 234 } 235 236 #ifndef FIDO_HID_ANY 237 static bool 238 hid_ok(HDEVINFO devinfo, DWORD idx) 239 { 240 SP_DEVINFO_DATA devinfo_data; 241 wchar_t *parent = NULL; 242 DWORD parent_type = DEVPROP_TYPE_STRING; 243 DWORD len = 0; 244 bool ok = false; 245 246 memset(&devinfo_data, 0, sizeof(devinfo_data)); 247 devinfo_data.cbSize = sizeof(devinfo_data); 248 249 if (SetupDiEnumDeviceInfo(devinfo, idx, &devinfo_data) == false) { 250 fido_log_debug("%s: SetupDiEnumDeviceInfo", __func__); 251 goto fail; 252 } 253 254 if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data, 255 &DEVPKEY_Device_Parent, &parent_type, NULL, 0, &len, 0) != false || 256 GetLastError() != ERROR_INSUFFICIENT_BUFFER) { 257 fido_log_debug("%s: SetupDiGetDevicePropertyW 1", __func__); 258 goto fail; 259 } 260 261 if ((parent = malloc(len)) == NULL) { 262 fido_log_debug("%s: malloc", __func__); 263 goto fail; 264 } 265 266 if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data, 267 &DEVPKEY_Device_Parent, &parent_type, (PBYTE)parent, len, NULL, 268 0) == false) { 269 fido_log_debug("%s: SetupDiGetDevicePropertyW 2", __func__); 270 goto fail; 271 } 272 273 ok = wcsncmp(parent, L"USB\\", 4) == 0; 274 fail: 275 free(parent); 276 277 return (ok); 278 } 279 #endif 280 281 static int 282 copy_info(fido_dev_info_t *di, HDEVINFO devinfo, DWORD idx, 283 SP_DEVICE_INTERFACE_DATA *ifdata) 284 { 285 HANDLE dev = INVALID_HANDLE_VALUE; 286 int ok = -1; 287 288 memset(di, 0, sizeof(*di)); 289 290 if ((di->path = get_path(devinfo, ifdata)) == NULL) { 291 fido_log_debug("%s: get_path", __func__); 292 goto fail; 293 } 294 295 fido_log_debug("%s: path=%s", __func__, di->path); 296 297 #ifndef FIDO_HID_ANY 298 if (hid_ok(devinfo, idx) == false) { 299 fido_log_debug("%s: hid_ok", __func__); 300 goto fail; 301 } 302 #endif 303 304 dev = CreateFileA(di->path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 305 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 306 if (dev == INVALID_HANDLE_VALUE) { 307 fido_log_debug("%s: CreateFileA", __func__); 308 goto fail; 309 } 310 311 if (is_fido(dev) == false) { 312 fido_log_debug("%s: is_fido", __func__); 313 goto fail; 314 } 315 316 if (get_int(dev, &di->vendor_id, &di->product_id) < 0 || 317 get_str(dev, &di->manufacturer, &di->product) < 0) { 318 fido_log_debug("%s: get_int/get_str", __func__); 319 goto fail; 320 } 321 322 ok = 0; 323 fail: 324 if (dev != INVALID_HANDLE_VALUE) 325 CloseHandle(dev); 326 327 if (ok < 0) { 328 free(di->path); 329 free(di->manufacturer); 330 free(di->product); 331 explicit_bzero(di, sizeof(*di)); 332 } 333 334 return (ok); 335 } 336 337 int 338 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) 339 { 340 GUID hid_guid = GUID_DEVINTERFACE_HID; 341 HDEVINFO devinfo = INVALID_HANDLE_VALUE; 342 SP_DEVICE_INTERFACE_DATA ifdata; 343 DWORD idx; 344 int r = FIDO_ERR_INTERNAL; 345 346 *olen = 0; 347 348 if (ilen == 0) 349 return (FIDO_OK); /* nothing to do */ 350 if (devlist == NULL) 351 return (FIDO_ERR_INVALID_ARGUMENT); 352 353 if ((devinfo = SetupDiGetClassDevsA(&hid_guid, NULL, NULL, 354 DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)) == INVALID_HANDLE_VALUE) { 355 fido_log_debug("%s: SetupDiGetClassDevsA", __func__); 356 goto fail; 357 } 358 359 ifdata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); 360 361 for (idx = 0; SetupDiEnumDeviceInterfaces(devinfo, NULL, &hid_guid, 362 idx, &ifdata) == true; idx++) { 363 if (copy_info(&devlist[*olen], devinfo, idx, &ifdata) == 0) { 364 devlist[*olen].io = (fido_dev_io_t) { 365 fido_hid_open, 366 fido_hid_close, 367 fido_hid_read, 368 fido_hid_write, 369 }; 370 if (++(*olen) == ilen) 371 break; 372 } 373 } 374 375 r = FIDO_OK; 376 fail: 377 if (devinfo != INVALID_HANDLE_VALUE) 378 SetupDiDestroyDeviceInfoList(devinfo); 379 380 return (r); 381 } 382 383 void * 384 fido_hid_open(const char *path) 385 { 386 struct hid_win *ctx; 387 388 if ((ctx = calloc(1, sizeof(*ctx))) == NULL) 389 return (NULL); 390 391 ctx->dev = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, 392 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 393 FILE_FLAG_OVERLAPPED, NULL); 394 395 if (ctx->dev == INVALID_HANDLE_VALUE) { 396 free(ctx); 397 return (NULL); 398 } 399 400 if ((ctx->overlap.hEvent = CreateEventA(NULL, FALSE, FALSE, 401 NULL)) == NULL) { 402 fido_log_debug("%s: CreateEventA", __func__); 403 fido_hid_close(ctx); 404 return (NULL); 405 } 406 407 if (get_report_len(ctx->dev, 0, &ctx->report_in_len) < 0 || 408 get_report_len(ctx->dev, 1, &ctx->report_out_len) < 0) { 409 fido_log_debug("%s: get_report_len", __func__); 410 fido_hid_close(ctx); 411 return (NULL); 412 } 413 414 return (ctx); 415 } 416 417 void 418 fido_hid_close(void *handle) 419 { 420 struct hid_win *ctx = handle; 421 422 if (ctx->overlap.hEvent != NULL) { 423 if (ctx->report_pending) { 424 fido_log_debug("%s: report_pending", __func__); 425 if (CancelIoEx(ctx->dev, &ctx->overlap) == 0) 426 fido_log_debug("%s CancelIoEx: 0x%lx", 427 __func__, GetLastError()); 428 } 429 CloseHandle(ctx->overlap.hEvent); 430 } 431 432 explicit_bzero(ctx->report, sizeof(ctx->report)); 433 CloseHandle(ctx->dev); 434 free(ctx); 435 } 436 437 int 438 fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) 439 { 440 (void)handle; 441 (void)sigmask; 442 443 return (FIDO_ERR_INTERNAL); 444 } 445 446 int 447 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) 448 { 449 struct hid_win *ctx = handle; 450 DWORD n; 451 452 if (len != ctx->report_in_len - 1 || len > sizeof(ctx->report) - 1) { 453 fido_log_debug("%s: len %zu", __func__, len); 454 return (-1); 455 } 456 457 if (ctx->report_pending == 0) { 458 memset(&ctx->report, 0, sizeof(ctx->report)); 459 ResetEvent(ctx->overlap.hEvent); 460 if (ReadFile(ctx->dev, ctx->report, (DWORD)(len + 1), &n, 461 &ctx->overlap) == 0 && GetLastError() != ERROR_IO_PENDING) { 462 CancelIo(ctx->dev); 463 fido_log_debug("%s: ReadFile", __func__); 464 return (-1); 465 } 466 ctx->report_pending = 1; 467 } 468 469 if (ms > -1 && WaitForSingleObject(ctx->overlap.hEvent, 470 (DWORD)ms) != WAIT_OBJECT_0) 471 return (0); 472 473 ctx->report_pending = 0; 474 475 if (GetOverlappedResult(ctx->dev, &ctx->overlap, &n, TRUE) == 0) { 476 fido_log_debug("%s: GetOverlappedResult", __func__); 477 return (-1); 478 } 479 480 if (n != len + 1) { 481 fido_log_debug("%s: expected %zu, got %zu", __func__, 482 len + 1, (size_t)n); 483 return (-1); 484 } 485 486 memcpy(buf, ctx->report + 1, len); 487 explicit_bzero(ctx->report, sizeof(ctx->report)); 488 489 return ((int)len); 490 } 491 492 int 493 fido_hid_write(void *handle, const unsigned char *buf, size_t len) 494 { 495 struct hid_win *ctx = handle; 496 OVERLAPPED overlap; 497 DWORD n; 498 499 memset(&overlap, 0, sizeof(overlap)); 500 501 if (len != ctx->report_out_len) { 502 fido_log_debug("%s: len %zu", __func__, len); 503 return (-1); 504 } 505 506 if (WriteFile(ctx->dev, buf, (DWORD)len, NULL, &overlap) == 0 && 507 GetLastError() != ERROR_IO_PENDING) { 508 fido_log_debug("%s: WriteFile", __func__); 509 return (-1); 510 } 511 512 if (GetOverlappedResult(ctx->dev, &overlap, &n, TRUE) == 0) { 513 fido_log_debug("%s: GetOverlappedResult", __func__); 514 return (-1); 515 } 516 517 if (n != len) { 518 fido_log_debug("%s: expected %zu, got %zu", __func__, len, 519 (size_t)n); 520 return (-1); 521 } 522 523 return ((int)len); 524 } 525 526 size_t 527 fido_hid_report_in_len(void *handle) 528 { 529 struct hid_win *ctx = handle; 530 531 return (ctx->report_in_len - 1); 532 } 533 534 size_t 535 fido_hid_report_out_len(void *handle) 536 { 537 struct hid_win *ctx = handle; 538 539 return (ctx->report_out_len - 1); 540 } 541