xref: /netbsd-src/external/bsd/libfido2/dist/src/hid_win.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
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