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