diff options
Diffstat (limited to 'src/hid_win.c')
-rw-r--r-- | src/hid_win.c | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/src/hid_win.c b/src/hid_win.c new file mode 100644 index 0000000..6d93778 --- /dev/null +++ b/src/hid_win.c | |||
@@ -0,0 +1,324 @@ | |||
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_dev_info_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 | if (++(*olen) == ilen) | ||
238 | break; | ||
239 | } | ||
240 | |||
241 | free(ifdetail); | ||
242 | ifdetail = NULL; | ||
243 | } | ||
244 | |||
245 | r = FIDO_OK; | ||
246 | fail: | ||
247 | if (devinfo != INVALID_HANDLE_VALUE) | ||
248 | SetupDiDestroyDeviceInfoList(devinfo); | ||
249 | |||
250 | free(ifdetail); | ||
251 | |||
252 | return (r); | ||
253 | } | ||
254 | |||
255 | void * | ||
256 | fido_hid_open(const char *path) | ||
257 | { | ||
258 | HANDLE dev; | ||
259 | |||
260 | dev = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, | ||
261 | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, | ||
262 | FILE_ATTRIBUTE_NORMAL, NULL); | ||
263 | |||
264 | if (dev == INVALID_HANDLE_VALUE) | ||
265 | return (NULL); | ||
266 | |||
267 | return (dev); | ||
268 | } | ||
269 | |||
270 | void | ||
271 | fido_hid_close(void *handle) | ||
272 | { | ||
273 | CloseHandle(handle); | ||
274 | } | ||
275 | |||
276 | int | ||
277 | fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) | ||
278 | { | ||
279 | DWORD n; | ||
280 | int r = -1; | ||
281 | uint8_t report[REPORT_LEN]; | ||
282 | |||
283 | (void)ms; /* XXX */ | ||
284 | |||
285 | memset(report, 0, sizeof(report)); | ||
286 | |||
287 | if (len != sizeof(report) - 1) { | ||
288 | fido_log_debug("%s: invalid len", __func__); | ||
289 | return (-1); | ||
290 | } | ||
291 | |||
292 | if (ReadFile(handle, report, sizeof(report), &n, NULL) == false || | ||
293 | n != sizeof(report)) { | ||
294 | fido_log_debug("%s: ReadFile", __func__); | ||
295 | goto fail; | ||
296 | } | ||
297 | |||
298 | r = sizeof(report) - 1; | ||
299 | memcpy(buf, report + 1, len); | ||
300 | |||
301 | fail: | ||
302 | explicit_bzero(report, sizeof(report)); | ||
303 | |||
304 | return (r); | ||
305 | } | ||
306 | |||
307 | int | ||
308 | fido_hid_write(void *handle, const unsigned char *buf, size_t len) | ||
309 | { | ||
310 | DWORD n; | ||
311 | |||
312 | if (len != REPORT_LEN) { | ||
313 | fido_log_debug("%s: invalid len", __func__); | ||
314 | return (-1); | ||
315 | } | ||
316 | |||
317 | if (WriteFile(handle, buf, (DWORD)len, &n, NULL) == false || | ||
318 | n != REPORT_LEN) { | ||
319 | fido_log_debug("%s: WriteFile", __func__); | ||
320 | return (-1); | ||
321 | } | ||
322 | |||
323 | return (REPORT_LEN); | ||
324 | } | ||