xref: /netbsd-src/external/bsd/libfido2/dist/src/hid_win.c (revision cef8759bd76c1b621f8eab8faa6f208faabc2e15)
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 <hidclass.h>
18 #include <hidsdi.h>
19 
20 #include "fido.h"
21 
22 #define REPORT_LEN	65
23 
24 static bool
25 is_fido(HANDLE dev)
26 {
27 	PHIDP_PREPARSED_DATA	data = NULL;
28 	HIDP_CAPS		caps;
29 	uint16_t		usage_page = 0;
30 
31 	if (HidD_GetPreparsedData(dev, &data) == false) {
32 		fido_log_debug("%s: HidD_GetPreparsedData", __func__);
33 		goto fail;
34 	}
35 
36 	if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) {
37 		fido_log_debug("%s: HidP_GetCaps", __func__);
38 		goto fail;
39 	}
40 
41 	if (caps.OutputReportByteLength != REPORT_LEN ||
42 	    caps.InputReportByteLength != REPORT_LEN) {
43 		fido_log_debug("%s: unsupported report len", __func__);
44 		goto fail;
45 	}
46 
47 	usage_page = caps.UsagePage;
48 fail:
49 	if (data != NULL)
50 		HidD_FreePreparsedData(data);
51 
52 	return (usage_page == 0xf1d0);
53 }
54 
55 static int
56 get_int(HANDLE dev, int16_t *vendor_id, int16_t *product_id)
57 {
58 	HIDD_ATTRIBUTES attr;
59 
60 	attr.Size = sizeof(attr);
61 
62 	if (HidD_GetAttributes(dev, &attr) == false) {
63 		fido_log_debug("%s: HidD_GetAttributes", __func__);
64 		return (-1);
65 	}
66 
67 	*vendor_id = attr.VendorID;
68 	*product_id = attr.ProductID;
69 
70 	return (0);
71 }
72 
73 static int
74 get_str(HANDLE dev, char **manufacturer, char **product)
75 {
76 	wchar_t	buf[512];
77 	int	utf8_len;
78 	int	ok = -1;
79 
80 	*manufacturer = NULL;
81 	*product = NULL;
82 
83 	if (HidD_GetManufacturerString(dev, &buf, sizeof(buf)) == false) {
84 		fido_log_debug("%s: HidD_GetManufacturerString", __func__);
85 		goto fail;
86 	}
87 
88 	if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf,
89 	    -1, NULL, 0, NULL, NULL)) <= 0 || utf8_len > 128) {
90 		fido_log_debug("%s: WideCharToMultiByte", __func__);
91 		goto fail;
92 	}
93 
94 	if ((*manufacturer = malloc(utf8_len)) == NULL) {
95 		fido_log_debug("%s: malloc", __func__);
96 		goto fail;
97 	}
98 
99 	if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, -1,
100 	    *manufacturer, utf8_len, NULL, NULL) != utf8_len) {
101 		fido_log_debug("%s: WideCharToMultiByte", __func__);
102 		goto fail;
103 	}
104 
105 	if (HidD_GetProductString(dev, &buf, sizeof(buf)) == false) {
106 		fido_log_debug("%s: HidD_GetProductString", __func__);
107 		goto fail;
108 	}
109 
110 	if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf,
111 	    -1, NULL, 0, NULL, NULL)) <= 0 || utf8_len > 128) {
112 		fido_log_debug("%s: WideCharToMultiByte", __func__);
113 		goto fail;
114 	}
115 
116 	if ((*product = malloc(utf8_len)) == NULL) {
117 		fido_log_debug("%s: malloc", __func__);
118 		goto fail;
119 	}
120 
121 	if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, -1,
122 	    *product, utf8_len, NULL, NULL) != utf8_len) {
123 		fido_log_debug("%s: WideCharToMultiByte", __func__);
124 		goto fail;
125 	}
126 
127 	ok = 0;
128 fail:
129 	if (ok < 0) {
130 		free(*manufacturer);
131 		free(*product);
132 		*manufacturer = NULL;
133 		*product = NULL;
134 	}
135 
136 	return (ok);
137 }
138 
139 static int
140 copy_info(fido_dev_info_t *di, const char *path)
141 {
142 	HANDLE	dev = INVALID_HANDLE_VALUE;
143 	int	ok = -1;
144 
145 	memset(di, 0, sizeof(*di));
146 
147 	dev = CreateFileA(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
148 	    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
149 	if (dev == INVALID_HANDLE_VALUE || is_fido(dev) == 0)
150 		goto fail;
151 
152 	if (get_int(dev, &di->vendor_id, &di->product_id) < 0 ||
153 	    get_str(dev, &di->manufacturer, &di->product) < 0)
154 		goto fail;
155 
156 	if ((di->path = strdup(path)) == NULL)
157 		goto fail;
158 
159 	ok = 0;
160 fail:
161 	if (dev != INVALID_HANDLE_VALUE)
162 		CloseHandle(dev);
163 
164 	if (ok < 0) {
165 		free(di->path);
166 		free(di->manufacturer);
167 		free(di->product);
168 		explicit_bzero(di, sizeof(*di));
169 	}
170 
171 	return (ok);
172 }
173 
174 int
175 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
176 {
177 	GUID					 hid_guid = GUID_DEVINTERFACE_HID;
178 	HDEVINFO				 devinfo = INVALID_HANDLE_VALUE;
179 	SP_DEVICE_INTERFACE_DATA		 ifdata;
180 	SP_DEVICE_INTERFACE_DETAIL_DATA_A	*ifdetail = NULL;
181 	DWORD					 len = 0;
182 	DWORD					 idx = 0;
183 	int					 r = FIDO_ERR_INTERNAL;
184 
185 	*olen = 0;
186 
187 	if (ilen == 0)
188 		return (FIDO_OK); /* nothing to do */
189 
190 	if (devlist == NULL)
191 		return (FIDO_ERR_INVALID_ARGUMENT);
192 
193 	devinfo = SetupDiGetClassDevsA(&hid_guid, NULL, NULL,
194 	    DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
195 	if (devinfo == INVALID_HANDLE_VALUE) {
196 		fido_log_debug("%s: SetupDiGetClassDevsA", __func__);
197 		goto fail;
198 	}
199 
200 	ifdata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
201 
202 	while (SetupDiEnumDeviceInterfaces(devinfo, NULL, &hid_guid, idx++,
203 	    &ifdata) == true) {
204 		/*
205 		 * "Get the required buffer size. Call
206 		 * SetupDiGetDeviceInterfaceDetail with a NULL
207 		 * DeviceInterfaceDetailData pointer, a
208 		 * DeviceInterfaceDetailDataSize of zero, and a valid
209 		 * RequiredSize variable. In response to such a call, this
210 		 * function returns the required buffer size at RequiredSize
211 		 * and fails with GetLastError returning
212 		 * ERROR_INSUFFICIENT_BUFFER."
213 		 */
214 		if (SetupDiGetDeviceInterfaceDetailA(devinfo, &ifdata, NULL, 0,
215 		    &len, NULL) != false ||
216 		    GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
217 			fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 1",
218 			    __func__);
219 			goto fail;
220 		}
221 
222 		if ((ifdetail = malloc(len)) == NULL) {
223 			fido_log_debug("%s: malloc", __func__);
224 			goto fail;
225 		}
226 
227 		ifdetail->cbSize = sizeof(*ifdetail);
228 
229 		if (SetupDiGetDeviceInterfaceDetailA(devinfo, &ifdata, ifdetail,
230 		    len, NULL, NULL) == false) {
231 			fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 2",
232 			    __func__);
233 			goto fail;
234 		}
235 
236 		if (copy_info(&devlist[*olen], ifdetail->DevicePath) == 0) {
237 			devlist[*olen].io = (fido_dev_io_t) {
238 				fido_hid_open,
239 				fido_hid_close,
240 				fido_hid_read,
241 				fido_hid_write,
242 			};
243 			if (++(*olen) == ilen)
244 				break;
245 		}
246 
247 		free(ifdetail);
248 		ifdetail = NULL;
249 	}
250 
251 	r = FIDO_OK;
252 fail:
253 	if (devinfo != INVALID_HANDLE_VALUE)
254 		SetupDiDestroyDeviceInfoList(devinfo);
255 
256 	free(ifdetail);
257 
258 	return (r);
259 }
260 
261 void *
262 fido_hid_open(const char *path)
263 {
264 	HANDLE dev;
265 
266 	dev = CreateFileA(path, GENERIC_READ | GENERIC_WRITE,
267 	    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
268 	    FILE_ATTRIBUTE_NORMAL, NULL);
269 
270 	if (dev == INVALID_HANDLE_VALUE)
271 		return (NULL);
272 
273 	return (dev);
274 }
275 
276 void
277 fido_hid_close(void *handle)
278 {
279 	CloseHandle(handle);
280 }
281 
282 int
283 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
284 {
285 	DWORD	n;
286 	int	r = -1;
287 	uint8_t	report[REPORT_LEN];
288 
289 	(void)ms; /* XXX */
290 
291 	memset(report, 0, sizeof(report));
292 
293 	if (len != sizeof(report) - 1) {
294 		fido_log_debug("%s: invalid len", __func__);
295 		return (-1);
296 	}
297 
298 	if (ReadFile(handle, report, sizeof(report), &n, NULL) == false ||
299 	    n != sizeof(report)) {
300 		fido_log_debug("%s: ReadFile", __func__);
301 		goto fail;
302 	}
303 
304 	r = sizeof(report) - 1;
305 	memcpy(buf, report + 1, len);
306 
307 fail:
308 	explicit_bzero(report, sizeof(report));
309 
310 	return (r);
311 }
312 
313 int
314 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
315 {
316 	DWORD n;
317 
318 	if (len != REPORT_LEN) {
319 		fido_log_debug("%s: invalid len", __func__);
320 		return (-1);
321 	}
322 
323 	if (WriteFile(handle, buf, (DWORD)len, &n, NULL) == false ||
324 	    n != REPORT_LEN) {
325 		fido_log_debug("%s: WriteFile", __func__);
326 		return (-1);
327 	}
328 
329 	return (REPORT_LEN);
330 }
331