]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libfido2/src/hid_win.c
Merge bearssl-20220418
[FreeBSD/FreeBSD.git] / contrib / libfido2 / src / hid_win.c
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 }