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