xref: /freebsd/contrib/libfido2/src/hid_osx.c (revision f81cdf24ba5436367377f7c8e8f51f6df2a75ca7)
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 <errno.h>
11 #include <fcntl.h>
12 #include <signal.h>
13 #include <unistd.h>
14 
15 #include <Availability.h>
16 #include <CoreFoundation/CoreFoundation.h>
17 #include <IOKit/IOKitLib.h>
18 #include <IOKit/hid/IOHIDKeys.h>
19 #include <IOKit/hid/IOHIDManager.h>
20 
21 #include "fido.h"
22 
23 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 120000
24 #define kIOMainPortDefault kIOMasterPortDefault
25 #endif
26 
27 #define IOREG "ioreg://"
28 
29 struct hid_osx {
30 	IOHIDDeviceRef	ref;
31 	CFStringRef	loop_id;
32 	int		report_pipe[2];
33 	size_t		report_in_len;
34 	size_t		report_out_len;
35 	unsigned char	report[CTAP_MAX_REPORT_LEN];
36 };
37 
38 static int
39 get_int32(IOHIDDeviceRef dev, CFStringRef key, int32_t *v)
40 {
41 	CFTypeRef ref;
42 
43 	if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
44 	    CFGetTypeID(ref) != CFNumberGetTypeID()) {
45 		fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
46 		return (-1);
47 	}
48 
49 	if (CFNumberGetType(ref) != kCFNumberSInt32Type &&
50 	    CFNumberGetType(ref) != kCFNumberSInt64Type) {
51 		fido_log_debug("%s: CFNumberGetType", __func__);
52 		return (-1);
53 	}
54 
55 	if (CFNumberGetValue(ref, kCFNumberSInt32Type, v) == false) {
56 		fido_log_debug("%s: CFNumberGetValue", __func__);
57 		return (-1);
58 	}
59 
60 	return (0);
61 }
62 
63 static int
64 get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len)
65 {
66 	CFTypeRef ref;
67 
68 	memset(buf, 0, len);
69 
70 	if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
71 	    CFGetTypeID(ref) != CFStringGetTypeID()) {
72 		fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
73 		return (-1);
74 	}
75 
76 	if (CFStringGetCString(ref, buf, (long)len,
77 	    kCFStringEncodingUTF8) == false) {
78 		fido_log_debug("%s: CFStringGetCString", __func__);
79 		return (-1);
80 	}
81 
82 	return (0);
83 }
84 
85 static int
86 get_report_len(IOHIDDeviceRef dev, int dir, size_t *report_len)
87 {
88 	CFStringRef	key;
89 	int32_t		v;
90 
91 	if (dir == 0)
92 		key = CFSTR(kIOHIDMaxInputReportSizeKey);
93 	else
94 		key = CFSTR(kIOHIDMaxOutputReportSizeKey);
95 
96 	if (get_int32(dev, key, &v) < 0) {
97 		fido_log_debug("%s: get_int32/%d", __func__, dir);
98 		return (-1);
99 	}
100 
101 	if ((*report_len = (size_t)v) > CTAP_MAX_REPORT_LEN) {
102 		fido_log_debug("%s: report_len=%zu", __func__, *report_len);
103 		return (-1);
104 	}
105 
106 	return (0);
107 }
108 
109 static int
110 get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id)
111 {
112 	int32_t	vendor;
113 	int32_t product;
114 
115 	if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 ||
116 	    vendor > UINT16_MAX) {
117 		fido_log_debug("%s: get_int32 vendor", __func__);
118 		return (-1);
119 	}
120 
121 	if (get_int32(dev, CFSTR(kIOHIDProductIDKey), &product) < 0 ||
122 	    product > UINT16_MAX) {
123 		fido_log_debug("%s: get_int32 product", __func__);
124 		return (-1);
125 	}
126 
127 	*vendor_id = (int16_t)vendor;
128 	*product_id = (int16_t)product;
129 
130 	return (0);
131 }
132 
133 static int
134 get_str(IOHIDDeviceRef dev, char **manufacturer, char **product)
135 {
136 	char	buf[512];
137 	int	ok = -1;
138 
139 	*manufacturer = NULL;
140 	*product = NULL;
141 
142 	if (get_utf8(dev, CFSTR(kIOHIDManufacturerKey), buf, sizeof(buf)) < 0)
143 		*manufacturer = strdup("");
144 	else
145 		*manufacturer = strdup(buf);
146 
147 	if (get_utf8(dev, CFSTR(kIOHIDProductKey), buf, sizeof(buf)) < 0)
148 		*product = strdup("");
149 	else
150 		*product = strdup(buf);
151 
152 	if (*manufacturer == NULL || *product == NULL) {
153 		fido_log_debug("%s: strdup", __func__);
154 		goto fail;
155 	}
156 
157 	ok = 0;
158 fail:
159 	if (ok < 0) {
160 		free(*manufacturer);
161 		free(*product);
162 		*manufacturer = NULL;
163 		*product = NULL;
164 	}
165 
166 	return (ok);
167 }
168 
169 static char *
170 get_path(IOHIDDeviceRef dev)
171 {
172 	io_service_t	 s;
173 	uint64_t	 id;
174 	char		*path;
175 
176 	if ((s = IOHIDDeviceGetService(dev)) == MACH_PORT_NULL) {
177 		fido_log_debug("%s: IOHIDDeviceGetService", __func__);
178 		return (NULL);
179 	}
180 
181 	if (IORegistryEntryGetRegistryEntryID(s, &id) != KERN_SUCCESS) {
182 		fido_log_debug("%s: IORegistryEntryGetRegistryEntryID",
183 		    __func__);
184 		return (NULL);
185 	}
186 
187 	if (asprintf(&path, "%s%llu", IOREG, (unsigned long long)id) == -1) {
188 		fido_log_error(errno, "%s: asprintf", __func__);
189 		return (NULL);
190 	}
191 
192 	return (path);
193 }
194 
195 static bool
196 is_fido(IOHIDDeviceRef dev)
197 {
198 	char		buf[32];
199 	uint32_t	usage_page;
200 
201 	if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey),
202 	    (int32_t *)&usage_page) < 0 || usage_page != 0xf1d0)
203 		return (false);
204 
205 	if (get_utf8(dev, CFSTR(kIOHIDTransportKey), buf, sizeof(buf)) < 0) {
206 		fido_log_debug("%s: get_utf8 transport", __func__);
207 		return (false);
208 	}
209 
210 #ifndef FIDO_HID_ANY
211 	if (strcasecmp(buf, "usb") != 0) {
212 		fido_log_debug("%s: transport", __func__);
213 		return (false);
214 	}
215 #endif
216 
217 	return (true);
218 }
219 
220 static int
221 copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev)
222 {
223 	memset(di, 0, sizeof(*di));
224 
225 	if (is_fido(dev) == false)
226 		return (-1);
227 
228 	if (get_id(dev, &di->vendor_id, &di->product_id) < 0 ||
229 	    get_str(dev, &di->manufacturer, &di->product) < 0 ||
230 	    (di->path = get_path(dev)) == NULL) {
231 		free(di->path);
232 		free(di->manufacturer);
233 		free(di->product);
234 		explicit_bzero(di, sizeof(*di));
235 		return (-1);
236 	}
237 
238 	return (0);
239 }
240 
241 int
242 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
243 {
244 	IOHIDManagerRef	 manager = NULL;
245 	CFSetRef	 devset = NULL;
246 	size_t		 devcnt;
247 	CFIndex		 n;
248 	IOHIDDeviceRef	*devs = NULL;
249 	int		 r = FIDO_ERR_INTERNAL;
250 
251 	*olen = 0;
252 
253 	if (ilen == 0)
254 		return (FIDO_OK); /* nothing to do */
255 
256 	if (devlist == NULL)
257 		return (FIDO_ERR_INVALID_ARGUMENT);
258 
259 	if ((manager = IOHIDManagerCreate(kCFAllocatorDefault,
260 	    kIOHIDManagerOptionNone)) == NULL) {
261 		fido_log_debug("%s: IOHIDManagerCreate", __func__);
262 		goto fail;
263 	}
264 
265 	IOHIDManagerSetDeviceMatching(manager, NULL);
266 
267 	if ((devset = IOHIDManagerCopyDevices(manager)) == NULL) {
268 		fido_log_debug("%s: IOHIDManagerCopyDevices", __func__);
269 		goto fail;
270 	}
271 
272 	if ((n = CFSetGetCount(devset)) < 0) {
273 		fido_log_debug("%s: CFSetGetCount", __func__);
274 		goto fail;
275 	}
276 
277 	devcnt = (size_t)n;
278 
279 	if ((devs = calloc(devcnt, sizeof(*devs))) == NULL) {
280 		fido_log_debug("%s: calloc", __func__);
281 		goto fail;
282 	}
283 
284 	CFSetGetValues(devset, (void *)devs);
285 
286 	for (size_t i = 0; i < devcnt; i++) {
287 		if (copy_info(&devlist[*olen], devs[i]) == 0) {
288 			devlist[*olen].io = (fido_dev_io_t) {
289 				fido_hid_open,
290 				fido_hid_close,
291 				fido_hid_read,
292 				fido_hid_write,
293 			};
294 			if (++(*olen) == ilen)
295 				break;
296 		}
297 	}
298 
299 	r = FIDO_OK;
300 fail:
301 	if (manager != NULL)
302 		CFRelease(manager);
303 	if (devset != NULL)
304 		CFRelease(devset);
305 
306 	free(devs);
307 
308 	return (r);
309 }
310 
311 static void
312 report_callback(void *context, IOReturn result, void *dev, IOHIDReportType type,
313     uint32_t id, uint8_t *ptr, CFIndex len)
314 {
315 	struct hid_osx	*ctx = context;
316 	ssize_t		 r;
317 
318 	(void)dev;
319 
320 	if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput ||
321 	    id != 0 || len < 0 || (size_t)len != ctx->report_in_len) {
322 		fido_log_debug("%s: io error", __func__);
323 		return;
324 	}
325 
326 	if ((r = write(ctx->report_pipe[1], ptr, (size_t)len)) == -1) {
327 		fido_log_error(errno, "%s: write", __func__);
328 		return;
329 	}
330 
331 	if (r < 0 || (size_t)r != (size_t)len) {
332 		fido_log_debug("%s: %zd != %zu", __func__, r, (size_t)len);
333 		return;
334 	}
335 }
336 
337 static void
338 removal_callback(void *context, IOReturn result, void *sender)
339 {
340 	(void)context;
341 	(void)result;
342 	(void)sender;
343 
344 	CFRunLoopStop(CFRunLoopGetCurrent());
345 }
346 
347 static int
348 set_nonblock(int fd)
349 {
350 	int flags;
351 
352 	if ((flags = fcntl(fd, F_GETFL)) == -1) {
353 		fido_log_error(errno, "%s: fcntl F_GETFL", __func__);
354 		return (-1);
355 	}
356 
357 	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
358 		fido_log_error(errno, "%s: fcntl F_SETFL", __func__);
359 		return (-1);
360 	}
361 
362 	return (0);
363 }
364 
365 static int
366 disable_sigpipe(int fd)
367 {
368 	int disabled = 1;
369 
370 	if (fcntl(fd, F_SETNOSIGPIPE, &disabled) == -1) {
371 		fido_log_error(errno, "%s: fcntl F_SETNOSIGPIPE", __func__);
372 		return (-1);
373 	}
374 
375 	return (0);
376 }
377 
378 static io_registry_entry_t
379 get_ioreg_entry(const char *path)
380 {
381 	uint64_t id;
382 
383 	if (strncmp(path, IOREG, strlen(IOREG)) != 0)
384 		return (IORegistryEntryFromPath(kIOMainPortDefault, path));
385 
386 	if (fido_to_uint64(path + strlen(IOREG), 10, &id) == -1) {
387 		fido_log_debug("%s: fido_to_uint64", __func__);
388 		return (MACH_PORT_NULL);
389 	}
390 
391 	return (IOServiceGetMatchingService(kIOMainPortDefault,
392 	    IORegistryEntryIDMatching(id)));
393 }
394 
395 void *
396 fido_hid_open(const char *path)
397 {
398 	struct hid_osx		*ctx;
399 	io_registry_entry_t	 entry = MACH_PORT_NULL;
400 	char			 loop_id[32];
401 	int			 ok = -1;
402 	int			 r;
403 
404 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
405 		fido_log_debug("%s: calloc", __func__);
406 		goto fail;
407 	}
408 
409 	ctx->report_pipe[0] = -1;
410 	ctx->report_pipe[1] = -1;
411 
412 	if (pipe(ctx->report_pipe) == -1) {
413 		fido_log_error(errno, "%s: pipe", __func__);
414 		goto fail;
415 	}
416 
417 	if (set_nonblock(ctx->report_pipe[0]) < 0 ||
418 	    set_nonblock(ctx->report_pipe[1]) < 0) {
419 		fido_log_debug("%s: set_nonblock", __func__);
420 		goto fail;
421 	}
422 
423 	if (disable_sigpipe(ctx->report_pipe[1]) < 0) {
424 		fido_log_debug("%s: disable_sigpipe", __func__);
425 		goto fail;
426 	}
427 
428 	if ((entry = get_ioreg_entry(path)) == MACH_PORT_NULL) {
429 		fido_log_debug("%s: get_ioreg_entry: %s", __func__, path);
430 		goto fail;
431 	}
432 
433 	if ((ctx->ref = IOHIDDeviceCreate(kCFAllocatorDefault,
434 	    entry)) == NULL) {
435 		fido_log_debug("%s: IOHIDDeviceCreate", __func__);
436 		goto fail;
437 	}
438 
439 	if (get_report_len(ctx->ref, 0, &ctx->report_in_len) < 0 ||
440 	    get_report_len(ctx->ref, 1, &ctx->report_out_len) < 0) {
441 		fido_log_debug("%s: get_report_len", __func__);
442 		goto fail;
443 	}
444 
445 	if (ctx->report_in_len > sizeof(ctx->report)) {
446 		fido_log_debug("%s: report_in_len=%zu", __func__,
447 		    ctx->report_in_len);
448 		goto fail;
449 	}
450 
451 	if (IOHIDDeviceOpen(ctx->ref,
452 	    kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) {
453 		fido_log_debug("%s: IOHIDDeviceOpen", __func__);
454 		goto fail;
455 	}
456 
457 	if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p",
458 	    (void *)ctx->ref)) < 0 || (size_t)r >= sizeof(loop_id)) {
459 		fido_log_debug("%s: snprintf", __func__);
460 		goto fail;
461 	}
462 
463 	if ((ctx->loop_id = CFStringCreateWithCString(NULL, loop_id,
464 	    kCFStringEncodingASCII)) == NULL) {
465 		fido_log_debug("%s: CFStringCreateWithCString", __func__);
466 		goto fail;
467 	}
468 
469 	IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report,
470 	    (long)ctx->report_in_len, &report_callback, ctx);
471 	IOHIDDeviceRegisterRemovalCallback(ctx->ref, &removal_callback, ctx);
472 
473 	ok = 0;
474 fail:
475 	if (entry != MACH_PORT_NULL)
476 		IOObjectRelease(entry);
477 
478 	if (ok < 0 && ctx != NULL) {
479 		if (ctx->ref != NULL)
480 			CFRelease(ctx->ref);
481 		if (ctx->loop_id != NULL)
482 			CFRelease(ctx->loop_id);
483 		if (ctx->report_pipe[0] != -1)
484 			close(ctx->report_pipe[0]);
485 		if (ctx->report_pipe[1] != -1)
486 			close(ctx->report_pipe[1]);
487 		free(ctx);
488 		ctx = NULL;
489 	}
490 
491 	return (ctx);
492 }
493 
494 void
495 fido_hid_close(void *handle)
496 {
497 	struct hid_osx *ctx = handle;
498 
499 	IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report,
500 	    (long)ctx->report_in_len, NULL, ctx);
501 	IOHIDDeviceRegisterRemovalCallback(ctx->ref, NULL, ctx);
502 
503 	if (IOHIDDeviceClose(ctx->ref,
504 	    kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess)
505 		fido_log_debug("%s: IOHIDDeviceClose", __func__);
506 
507 	CFRelease(ctx->ref);
508 	CFRelease(ctx->loop_id);
509 
510 	explicit_bzero(ctx->report, sizeof(ctx->report));
511 	close(ctx->report_pipe[0]);
512 	close(ctx->report_pipe[1]);
513 
514 	free(ctx);
515 }
516 
517 int
518 fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
519 {
520 	(void)handle;
521 	(void)sigmask;
522 
523 	return (FIDO_ERR_INTERNAL);
524 }
525 
526 int
527 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
528 {
529 	struct hid_osx		*ctx = handle;
530 	ssize_t			 r;
531 
532 	explicit_bzero(buf, len);
533 	explicit_bzero(ctx->report, sizeof(ctx->report));
534 
535 	if (len != ctx->report_in_len || len > sizeof(ctx->report)) {
536 		fido_log_debug("%s: len %zu", __func__, len);
537 		return (-1);
538 	}
539 
540 	IOHIDDeviceScheduleWithRunLoop(ctx->ref, CFRunLoopGetCurrent(),
541 	    ctx->loop_id);
542 
543 	if (ms == -1)
544 		ms = 5000; /* wait 5 seconds by default */
545 
546 	CFRunLoopRunInMode(ctx->loop_id, (double)ms/1000.0, true);
547 
548 	IOHIDDeviceUnscheduleFromRunLoop(ctx->ref, CFRunLoopGetCurrent(),
549 	    ctx->loop_id);
550 
551 	if ((r = read(ctx->report_pipe[0], buf, len)) == -1) {
552 		fido_log_error(errno, "%s: read", __func__);
553 		return (-1);
554 	}
555 
556 	if (r < 0 || (size_t)r != len) {
557 		fido_log_debug("%s: %zd != %zu", __func__, r, len);
558 		return (-1);
559 	}
560 
561 	return ((int)len);
562 }
563 
564 int
565 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
566 {
567 	struct hid_osx *ctx = handle;
568 
569 	if (len != ctx->report_out_len + 1 || len > LONG_MAX) {
570 		fido_log_debug("%s: len %zu", __func__, len);
571 		return (-1);
572 	}
573 
574 	if (IOHIDDeviceSetReport(ctx->ref, kIOHIDReportTypeOutput, 0, buf + 1,
575 	    (long)(len - 1)) != kIOReturnSuccess) {
576 		fido_log_debug("%s: IOHIDDeviceSetReport", __func__);
577 		return (-1);
578 	}
579 
580 	return ((int)len);
581 }
582 
583 size_t
584 fido_hid_report_in_len(void *handle)
585 {
586 	struct hid_osx *ctx = handle;
587 
588 	return (ctx->report_in_len);
589 }
590 
591 size_t
592 fido_hid_report_out_len(void *handle)
593 {
594 	struct hid_osx *ctx = handle;
595 
596 	return (ctx->report_out_len);
597 }
598