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
26WINSETUPAPI 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
32DEFINE_DEVPROPKEY(DEVPKEY_Device_Parent, 0x4340a6c5, 0x93fa, 0x4706, 0x97,
33    0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7, 8);
34#endif
35
36struct 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
45static bool
46is_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;
63fail:
64	if (data != NULL)
65		HidD_FreePreparsedData(data);
66
67	return (fido);
68}
69
70static int
71get_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;
99fail:
100	if (data != NULL)
101		HidD_FreePreparsedData(data);
102
103	return (ok);
104}
105
106static int
107get_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
124static int
125get_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;
156fail:
157	if (ok < 0) {
158		free(*manufacturer);
159		*manufacturer = NULL;
160	}
161
162	return (ok);
163}
164
165static int
166get_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;
197fail:
198	if (ok < 0) {
199		free(*product);
200		*product = NULL;
201	}
202
203	return (ok);
204}
205
206static char *
207get_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
247fail:
248	free(ifdetail);
249
250	return (path);
251}
252
253#ifndef FIDO_HID_ANY
254static bool
255hid_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;
291fail:
292	free(parent);
293
294	return (ok);
295}
296#endif
297
298static int
299copy_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;
354fail:
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
368int
369fido_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;
407fail:
408	if (devinfo != INVALID_HANDLE_VALUE)
409		SetupDiDestroyDeviceInfoList(devinfo);
410
411	return (r);
412}
413
414void *
415fido_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
448void
449fido_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
468int
469fido_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
477int
478fido_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
523int
524fido_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
557size_t
558fido_hid_report_in_len(void *handle)
559{
560	struct hid_win *ctx = handle;
561
562	return (ctx->report_in_len - 1);
563}
564
565size_t
566fido_hid_report_out_len(void *handle)
567{
568	struct hid_win *ctx = handle;
569
570	return (ctx->report_out_len - 1);
571}
572