xref: /netbsd-src/external/bsd/libfido2/dist/src/hid_osx.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 
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 
24 struct dev {
25 	IOHIDDeviceRef	ref;
26 	CFStringRef	loop_id;
27 };
28 
29 static int
30 get_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 
54 static int
55 get_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 
75 static bool
76 is_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 
94 static int
95 get_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 
118 static int
119 get_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;
148 fail:
149 	if (ok < 0) {
150 		free(*manufacturer);
151 		free(*product);
152 		*manufacturer = NULL;
153 		*product = NULL;
154 	}
155 
156 	return (ok);
157 }
158 
159 static char *
160 get_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 
178 static int
179 copy_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 
199 int
200 fido_hid_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 			devlist[*olen].io = (fido_dev_io_t) {
244 				fido_hid_open,
245 				fido_hid_close,
246 				fido_hid_read,
247 				fido_hid_write,
248 			};
249 			if (++(*olen) == ilen)
250 				break;
251 		}
252 	}
253 
254 	r = FIDO_OK;
255 fail:
256 	if (manager != NULL)
257 		CFRelease(manager);
258 	if (devset != NULL)
259 		CFRelease(devset);
260 
261 	free(devs);
262 
263 	return (r);
264 }
265 
266 void *
267 fido_hid_open(const char *path)
268 {
269 	io_registry_entry_t	 entry = MACH_PORT_NULL;
270 	struct dev		*dev = NULL;
271 	int			 ok = -1;
272 	int			 r;
273 	char			 loop_id[32];
274 
275 	if ((dev = calloc(1, sizeof(*dev))) == NULL) {
276 		fido_log_debug("%s: calloc", __func__);
277 		goto fail;
278 	}
279 
280 	if ((entry = IORegistryEntryFromPath(kIOMasterPortDefault,
281 	    path)) == MACH_PORT_NULL) {
282 		fido_log_debug("%s: IORegistryEntryFromPath", __func__);
283 		goto fail;
284 	}
285 
286 	if ((dev->ref = IOHIDDeviceCreate(kCFAllocatorDefault,
287 	    entry)) == NULL) {
288 		fido_log_debug("%s: IOHIDDeviceCreate", __func__);
289 		goto fail;
290 	}
291 
292 	if (IOHIDDeviceOpen(dev->ref,
293 	    kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) {
294 		fido_log_debug("%s: IOHIDDeviceOpen", __func__);
295 		goto fail;
296 	}
297 
298 	if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p",
299 	    (void *)dev->ref)) < 0 || (size_t)r >= sizeof(loop_id)) {
300 		fido_log_debug("%s: snprintf", __func__);
301 		goto fail;
302 	}
303 
304 	if ((dev->loop_id = CFStringCreateWithCString(NULL, loop_id,
305 	    kCFStringEncodingASCII)) == NULL) {
306 		fido_log_debug("%s: CFStringCreateWithCString", __func__);
307 		goto fail;
308 	}
309 
310 	ok = 0;
311 fail:
312 	if (entry != MACH_PORT_NULL)
313 		IOObjectRelease(entry);
314 
315 	if (ok < 0 && dev != NULL) {
316 		if (dev->ref != NULL)
317 			CFRelease(dev->ref);
318 		if (dev->loop_id != NULL)
319 			CFRelease(dev->loop_id);
320 		free(dev);
321 		dev = NULL;
322 	}
323 
324 	return (dev);
325 }
326 
327 void
328 fido_hid_close(void *handle)
329 {
330 	struct dev *dev = handle;
331 
332 	if (IOHIDDeviceClose(dev->ref,
333 	    kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess)
334 		fido_log_debug("%s: IOHIDDeviceClose", __func__);
335 
336 	CFRelease(dev->ref);
337 	CFRelease(dev->loop_id);
338 
339 	free(dev);
340 }
341 
342 static void
343 read_callback(void *context, IOReturn result, void *dev, IOHIDReportType type,
344     uint32_t report_id, uint8_t *report, CFIndex report_len)
345 {
346 	(void)context;
347 	(void)dev;
348 	(void)report;
349 
350 	if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput ||
351 	    report_id != 0 || report_len != REPORT_LEN - 1) {
352 		fido_log_debug("%s: io error", __func__);
353 	}
354 }
355 
356 static void
357 removal_callback(void *context, IOReturn result, void *sender)
358 {
359 	(void)context;
360 	(void)result;
361 	(void)sender;
362 
363 	CFRunLoopStop(CFRunLoopGetCurrent());
364 }
365 
366 int
367 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
368 {
369 	struct dev		*dev = handle;
370 	CFRunLoopRunResult	 r;
371 
372 	(void)ms; /* XXX */
373 
374 	if (len != REPORT_LEN - 1) {
375 		fido_log_debug("%s: invalid len", __func__);
376 		return (-1);
377 	}
378 
379 	explicit_bzero(buf, len);
380 
381 	IOHIDDeviceRegisterInputReportCallback(dev->ref, buf, len,
382 	    &read_callback, NULL);
383 	IOHIDDeviceRegisterRemovalCallback(dev->ref, &removal_callback, dev);
384 	IOHIDDeviceScheduleWithRunLoop(dev->ref, CFRunLoopGetCurrent(),
385 	    dev->loop_id);
386 
387 	do
388 		r = CFRunLoopRunInMode(dev->loop_id, 0.003, true);
389 	while (r != kCFRunLoopRunHandledSource);
390 
391 	IOHIDDeviceRegisterInputReportCallback(dev->ref, buf, len, NULL, NULL);
392 	IOHIDDeviceRegisterRemovalCallback(dev->ref, NULL, NULL);
393 	IOHIDDeviceUnscheduleFromRunLoop(dev->ref, CFRunLoopGetCurrent(),
394 	    dev->loop_id);
395 
396 	return (REPORT_LEN - 1);
397 }
398 
399 int
400 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
401 {
402 	struct dev *dev = handle;
403 
404 	if (len != REPORT_LEN) {
405 		fido_log_debug("%s: invalid len", __func__);
406 		return (-1);
407 	}
408 
409 	if (IOHIDDeviceSetReport(dev->ref, kIOHIDReportTypeOutput, 0, buf + 1,
410 	    len - 1) != kIOReturnSuccess) {
411 		fido_log_debug("%s: IOHIDDeviceSetReport", __func__);
412 		return (-1);
413 	}
414 
415 	return (REPORT_LEN);
416 }
417