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
is_fido(HANDLE dev)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
get_report_len(HANDLE dev,int dir,size_t * report_len)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
get_id(HANDLE dev,int16_t * vendor_id,int16_t * product_id)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
get_manufacturer(HANDLE dev,char ** manufacturer)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
get_product(HANDLE dev,char ** product)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 *
get_path(HDEVINFO devinfo,SP_DEVICE_INTERFACE_DATA * ifdata)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
hid_ok(HDEVINFO devinfo,DWORD idx)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
copy_info(fido_dev_info_t * di,HDEVINFO devinfo,DWORD idx,SP_DEVICE_INTERFACE_DATA * ifdata)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
fido_hid_manifest(fido_dev_info_t * devlist,size_t ilen,size_t * olen)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 *
fido_hid_open(const char * path)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
fido_hid_close(void * handle)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
fido_hid_set_sigmask(void * handle,const fido_sigset_t * sigmask)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
fido_hid_read(void * handle,unsigned char * buf,size_t len,int ms)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
fido_hid_write(void * handle,const unsigned char * buf,size_t len)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
fido_hid_report_in_len(void * handle)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
fido_hid_report_out_len(void * handle)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