summaryrefslogtreecommitdiff
path: root/src/hid_osx.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/hid_osx.c')
-rw-r--r--src/hid_osx.c410
1 files changed, 410 insertions, 0 deletions
diff --git a/src/hid_osx.c b/src/hid_osx.c
new file mode 100644
index 0000000..b705b43
--- /dev/null
+++ b/src/hid_osx.c
@@ -0,0 +1,410 @@
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
15#include <CoreFoundation/CoreFoundation.h>
16#include <IOKit/IOKitLib.h>
17#include <IOKit/hid/IOHIDKeys.h>
18#include <IOKit/hid/IOHIDManager.h>
19
20#include "fido.h"
21
22#define REPORT_LEN 65
23
24struct dev {
25 IOHIDDeviceRef ref;
26 CFStringRef loop_id;
27};
28
29static int
30get_int32(IOHIDDeviceRef dev, CFStringRef key, int32_t *v)
31{
32 CFTypeRef ref;
33
34 if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
35 CFGetTypeID(ref) != CFNumberGetTypeID()) {
36 fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
37 return (-1);
38 }
39
40 if (CFNumberGetType(ref) != kCFNumberSInt32Type &&
41 CFNumberGetType(ref) != kCFNumberSInt64Type) {
42 fido_log_debug("%s: CFNumberGetType", __func__);
43 return (-1);
44 }
45
46 if (CFNumberGetValue(ref, kCFNumberSInt32Type, v) == false) {
47 fido_log_debug("%s: CFNumberGetValue", __func__);
48 return (-1);
49 }
50
51 return (0);
52}
53
54static int
55get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len)
56{
57 CFTypeRef ref;
58
59 memset(buf, 0, len);
60
61 if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
62 CFGetTypeID(ref) != CFStringGetTypeID()) {
63 fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
64 return (-1);
65 }
66
67 if (CFStringGetCString(ref, buf, len, kCFStringEncodingUTF8) == false) {
68 fido_log_debug("%s: CFStringGetCString", __func__);
69 return (-1);
70 }
71
72 return (0);
73}
74
75static bool
76is_fido(IOHIDDeviceRef dev)
77{
78 uint32_t usage_page;
79 int32_t report_len;
80
81 if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey),
82 (int32_t *)&usage_page) != 0 || usage_page != 0xf1d0)
83 return (false);
84
85 if (get_int32(dev, CFSTR(kIOHIDMaxInputReportSizeKey),
86 &report_len) < 0 || report_len != REPORT_LEN - 1) {
87 fido_log_debug("%s: unsupported report len", __func__);
88 return (false);
89 }
90
91 return (true);
92}
93
94static int
95get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id)
96{
97 int32_t vendor;
98 int32_t product;
99
100 if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 ||
101 vendor > UINT16_MAX) {
102 fido_log_debug("%s: get_int32 vendor", __func__);
103 return (-1);
104 }
105
106 if (get_int32(dev, CFSTR(kIOHIDProductIDKey), &product) < 0 ||
107 product > UINT16_MAX) {
108 fido_log_debug("%s: get_int32 product", __func__);
109 return (-1);
110 }
111
112 *vendor_id = (int16_t)vendor;
113 *product_id = (int16_t)product;
114
115 return (0);
116}
117
118static int
119get_str(IOHIDDeviceRef dev, char **manufacturer, char **product)
120{
121 char buf[512];
122 int ok = -1;
123
124 *manufacturer = NULL;
125 *product = NULL;
126
127 if (get_utf8(dev, CFSTR(kIOHIDManufacturerKey), buf, sizeof(buf)) < 0) {
128 fido_log_debug("%s: get_utf8 manufacturer", __func__);
129 goto fail;
130 }
131
132 if ((*manufacturer = strdup(buf)) == NULL) {
133 fido_log_debug("%s: strdup manufacturer", __func__);
134 goto fail;
135 }
136
137 if (get_utf8(dev, CFSTR(kIOHIDProductKey), buf, sizeof(buf)) < 0) {
138 fido_log_debug("%s: get_utf8 product", __func__);
139 goto fail;
140 }
141
142 if ((*product = strdup(buf)) == NULL) {
143 fido_log_debug("%s: strdup product", __func__);
144 goto fail;
145 }
146
147 ok = 0;
148fail:
149 if (ok < 0) {
150 free(*manufacturer);
151 free(*product);
152 *manufacturer = NULL;
153 *product = NULL;
154 }
155
156 return (ok);
157}
158
159static char *
160get_path(IOHIDDeviceRef dev)
161{
162 io_service_t s;
163 io_string_t path;
164
165 if ((s = IOHIDDeviceGetService(dev)) == MACH_PORT_NULL) {
166 fido_log_debug("%s: IOHIDDeviceGetService", __func__);
167 return (NULL);
168 }
169
170 if (IORegistryEntryGetPath(s, kIOServicePlane, path) != KERN_SUCCESS) {
171 fido_log_debug("%s: IORegistryEntryGetPath", __func__);
172 return (NULL);
173 }
174
175 return (strdup(path));
176}
177
178static int
179copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev)
180{
181 memset(di, 0, sizeof(*di));
182
183 if (is_fido(dev) == false)
184 return (-1);
185
186 if (get_id(dev, &di->vendor_id, &di->product_id) < 0 ||
187 get_str(dev, &di->manufacturer, &di->product) < 0 ||
188 (di->path = get_path(dev)) == NULL) {
189 free(di->path);
190 free(di->manufacturer);
191 free(di->product);
192 explicit_bzero(di, sizeof(*di));
193 return (-1);
194 }
195
196 return (0);
197}
198
199int
200fido_dev_info_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
201{
202 IOHIDManagerRef manager = NULL;
203 CFSetRef devset = NULL;
204 CFIndex devcnt;
205 IOHIDDeviceRef *devs = NULL;
206 int r = FIDO_ERR_INTERNAL;
207
208 *olen = 0;
209
210 if (ilen == 0)
211 return (FIDO_OK); /* nothing to do */
212
213 if (devlist == NULL)
214 return (FIDO_ERR_INVALID_ARGUMENT);
215
216 if ((manager = IOHIDManagerCreate(kCFAllocatorDefault,
217 kIOHIDManagerOptionNone)) == NULL) {
218 fido_log_debug("%s: IOHIDManagerCreate", __func__);
219 goto fail;
220 }
221
222 IOHIDManagerSetDeviceMatching(manager, NULL);
223
224 if ((devset = IOHIDManagerCopyDevices(manager)) == NULL) {
225 fido_log_debug("%s: IOHIDManagerCopyDevices", __func__);
226 goto fail;
227 }
228
229 if ((devcnt = CFSetGetCount(devset)) < 0) {
230 fido_log_debug("%s: CFSetGetCount", __func__);
231 goto fail;
232 }
233
234 if ((devs = calloc(devcnt, sizeof(*devs))) == NULL) {
235 fido_log_debug("%s: calloc", __func__);
236 goto fail;
237 }
238
239 CFSetGetValues(devset, (void *)devs);
240
241 for (CFIndex i = 0; i < devcnt; i++) {
242 if (copy_info(&devlist[*olen], devs[i]) == 0) {
243 if (++(*olen) == ilen)
244 break;
245 }
246 }
247
248 r = FIDO_OK;
249fail:
250 if (manager != NULL)
251 CFRelease(manager);
252 if (devset != NULL)
253 CFRelease(devset);
254
255 free(devs);
256
257 return (r);
258}
259
260void *
261fido_hid_open(const char *path)
262{
263 io_registry_entry_t entry = MACH_PORT_NULL;
264 struct dev *dev = NULL;
265 int ok = -1;
266 int r;
267 char loop_id[32];
268
269 if ((dev = calloc(1, sizeof(*dev))) == NULL) {
270 fido_log_debug("%s: calloc", __func__);
271 goto fail;
272 }
273
274 if ((entry = IORegistryEntryFromPath(kIOMasterPortDefault,
275 path)) == MACH_PORT_NULL) {
276 fido_log_debug("%s: IORegistryEntryFromPath", __func__);
277 goto fail;
278 }
279
280 if ((dev->ref = IOHIDDeviceCreate(kCFAllocatorDefault,
281 entry)) == NULL) {
282 fido_log_debug("%s: IOHIDDeviceCreate", __func__);
283 goto fail;
284 }
285
286 if (IOHIDDeviceOpen(dev->ref,
287 kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) {
288 fido_log_debug("%s: IOHIDDeviceOpen", __func__);
289 goto fail;
290 }
291
292 if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p",
293 (void *)dev->ref)) < 0 || (size_t)r >= sizeof(loop_id)) {
294 fido_log_debug("%s: snprintf", __func__);
295 goto fail;
296 }
297
298 if ((dev->loop_id = CFStringCreateWithCString(NULL, loop_id,
299 kCFStringEncodingASCII)) == NULL) {
300 fido_log_debug("%s: CFStringCreateWithCString", __func__);
301 goto fail;
302 }
303
304 ok = 0;
305fail:
306 if (entry != MACH_PORT_NULL)
307 IOObjectRelease(entry);
308
309 if (ok < 0 && dev != NULL) {
310 if (dev->ref != NULL)
311 CFRelease(dev->ref);
312 if (dev->loop_id != NULL)
313 CFRelease(dev->loop_id);
314 free(dev);
315 dev = NULL;
316 }
317
318 return (dev);
319}
320
321void
322fido_hid_close(void *handle)
323{
324 struct dev *dev = handle;
325
326 if (IOHIDDeviceClose(dev->ref,
327 kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess)
328 fido_log_debug("%s: IOHIDDeviceClose", __func__);
329
330 CFRelease(dev->ref);
331 CFRelease(dev->loop_id);
332
333 free(dev);
334}
335
336static void
337read_callback(void *context, IOReturn result, void *dev, IOHIDReportType type,
338 uint32_t report_id, uint8_t *report, CFIndex report_len)
339{
340 (void)context;
341 (void)dev;
342 (void)report;
343
344 if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput ||
345 report_id != 0 || report_len != REPORT_LEN - 1) {
346 fido_log_debug("%s: io error", __func__);
347 }
348}
349
350static void
351removal_callback(void *context, IOReturn result, void *sender)
352{
353 (void)context;
354 (void)result;
355 (void)sender;
356
357 CFRunLoopStop(CFRunLoopGetCurrent());
358}
359
360int
361fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
362{
363 struct dev *dev = handle;
364 CFRunLoopRunResult r;
365
366 (void)ms; /* XXX */
367
368 if (len != REPORT_LEN - 1) {
369 fido_log_debug("%s: invalid len", __func__);
370 return (-1);
371 }
372
373 explicit_bzero(buf, len);
374
375 IOHIDDeviceRegisterInputReportCallback(dev->ref, buf, len,
376 &read_callback, NULL);
377 IOHIDDeviceRegisterRemovalCallback(dev->ref, &removal_callback, dev);
378 IOHIDDeviceScheduleWithRunLoop(dev->ref, CFRunLoopGetCurrent(),
379 dev->loop_id);
380
381 do
382 r = CFRunLoopRunInMode(dev->loop_id, 0.003, true);
383 while (r != kCFRunLoopRunHandledSource);
384
385 IOHIDDeviceRegisterInputReportCallback(dev->ref, buf, len, NULL, NULL);
386 IOHIDDeviceRegisterRemovalCallback(dev->ref, NULL, NULL);
387 IOHIDDeviceUnscheduleFromRunLoop(dev->ref, CFRunLoopGetCurrent(),
388 dev->loop_id);
389
390 return (REPORT_LEN - 1);
391}
392
393int
394fido_hid_write(void *handle, const unsigned char *buf, size_t len)
395{
396 struct dev *dev = handle;
397
398 if (len != REPORT_LEN) {
399 fido_log_debug("%s: invalid len", __func__);
400 return (-1);
401 }
402
403 if (IOHIDDeviceSetReport(dev->ref, kIOHIDReportTypeOutput, 0, buf + 1,
404 len - 1) != kIOReturnSuccess) {
405 fido_log_debug("%s: IOHIDDeviceSetReport", __func__);
406 return (-1);
407 }
408
409 return (REPORT_LEN);
410}