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