xref: /freebsd/contrib/libfido2/src/hid_win.c (revision 4d65a7c6951cea0333f1a0c1b32c38489cdfa6c5)
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
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
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
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
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
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 *
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
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
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
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 *
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
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
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
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
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
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
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