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.
14 #include <CoreFoundation/CoreFoundation.h>
15 #include <IOKit/IOKitLib.h>
16 #include <IOKit/hid/IOHIDKeys.h>
17 #include <IOKit/hid/IOHIDManager.h>
26 size_t report_out_len;
27 unsigned char report[CTAP_MAX_REPORT_LEN];
31 get_int32(IOHIDDeviceRef dev, CFStringRef key, int32_t *v)
35 if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
36 CFGetTypeID(ref) != CFNumberGetTypeID()) {
37 fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
41 if (CFNumberGetType(ref) != kCFNumberSInt32Type &&
42 CFNumberGetType(ref) != kCFNumberSInt64Type) {
43 fido_log_debug("%s: CFNumberGetType", __func__);
47 if (CFNumberGetValue(ref, kCFNumberSInt32Type, v) == false) {
48 fido_log_debug("%s: CFNumberGetValue", __func__);
56 get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len)
62 if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
63 CFGetTypeID(ref) != CFStringGetTypeID()) {
64 fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
68 if (CFStringGetCString(ref, buf, (long)len,
69 kCFStringEncodingUTF8) == false) {
70 fido_log_debug("%s: CFStringGetCString", __func__);
78 get_report_len(IOHIDDeviceRef dev, int dir, size_t *report_len)
84 key = CFSTR(kIOHIDMaxInputReportSizeKey);
86 key = CFSTR(kIOHIDMaxOutputReportSizeKey);
88 if (get_int32(dev, key, &v) < 0) {
89 fido_log_debug("%s: get_int32/%d", __func__, dir);
93 if ((*report_len = (size_t)v) > CTAP_MAX_REPORT_LEN) {
94 fido_log_debug("%s: report_len=%zu", __func__, *report_len);
102 get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id)
107 if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 ||
108 vendor > UINT16_MAX) {
109 fido_log_debug("%s: get_int32 vendor", __func__);
113 if (get_int32(dev, CFSTR(kIOHIDProductIDKey), &product) < 0 ||
114 product > UINT16_MAX) {
115 fido_log_debug("%s: get_int32 product", __func__);
119 *vendor_id = (int16_t)vendor;
120 *product_id = (int16_t)product;
126 get_str(IOHIDDeviceRef dev, char **manufacturer, char **product)
131 *manufacturer = NULL;
134 if (get_utf8(dev, CFSTR(kIOHIDManufacturerKey), buf, sizeof(buf)) < 0) {
135 fido_log_debug("%s: get_utf8 manufacturer", __func__);
139 if ((*manufacturer = strdup(buf)) == NULL) {
140 fido_log_debug("%s: strdup manufacturer", __func__);
144 if (get_utf8(dev, CFSTR(kIOHIDProductKey), buf, sizeof(buf)) < 0) {
145 fido_log_debug("%s: get_utf8 product", __func__);
149 if ((*product = strdup(buf)) == NULL) {
150 fido_log_debug("%s: strdup product", __func__);
159 *manufacturer = NULL;
167 get_path(IOHIDDeviceRef dev)
172 if ((s = IOHIDDeviceGetService(dev)) == MACH_PORT_NULL) {
173 fido_log_debug("%s: IOHIDDeviceGetService", __func__);
177 if (IORegistryEntryGetPath(s, kIOServicePlane, path) != KERN_SUCCESS) {
178 fido_log_debug("%s: IORegistryEntryGetPath", __func__);
182 return (strdup(path));
186 is_fido(IOHIDDeviceRef dev)
191 if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey),
192 (int32_t *)&usage_page) < 0 || usage_page != 0xf1d0)
195 if (get_utf8(dev, CFSTR(kIOHIDTransportKey), buf, sizeof(buf)) < 0) {
196 fido_log_debug("%s: get_utf8 transport", __func__);
201 if (strcasecmp(buf, "usb") != 0) {
202 fido_log_debug("%s: transport", __func__);
211 copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev)
213 memset(di, 0, sizeof(*di));
215 if (is_fido(dev) == false)
218 if (get_id(dev, &di->vendor_id, &di->product_id) < 0 ||
219 get_str(dev, &di->manufacturer, &di->product) < 0 ||
220 (di->path = get_path(dev)) == NULL) {
222 free(di->manufacturer);
224 explicit_bzero(di, sizeof(*di));
232 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
234 IOHIDManagerRef manager = NULL;
235 CFSetRef devset = NULL;
238 IOHIDDeviceRef *devs = NULL;
239 int r = FIDO_ERR_INTERNAL;
244 return (FIDO_OK); /* nothing to do */
247 return (FIDO_ERR_INVALID_ARGUMENT);
249 if ((manager = IOHIDManagerCreate(kCFAllocatorDefault,
250 kIOHIDManagerOptionNone)) == NULL) {
251 fido_log_debug("%s: IOHIDManagerCreate", __func__);
255 IOHIDManagerSetDeviceMatching(manager, NULL);
257 if ((devset = IOHIDManagerCopyDevices(manager)) == NULL) {
258 fido_log_debug("%s: IOHIDManagerCopyDevices", __func__);
262 if ((n = CFSetGetCount(devset)) < 0) {
263 fido_log_debug("%s: CFSetGetCount", __func__);
269 if ((devs = calloc(devcnt, sizeof(*devs))) == NULL) {
270 fido_log_debug("%s: calloc", __func__);
274 CFSetGetValues(devset, (void *)devs);
276 for (size_t i = 0; i < devcnt; i++) {
277 if (copy_info(&devlist[*olen], devs[i]) == 0) {
278 devlist[*olen].io = (fido_dev_io_t) {
284 if (++(*olen) == ilen)
302 report_callback(void *context, IOReturn result, void *dev, IOHIDReportType type,
303 uint32_t id, uint8_t *ptr, CFIndex len)
305 struct hid_osx *ctx = context;
310 if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput ||
311 id != 0 || len < 0 || (size_t)len != ctx->report_in_len) {
312 fido_log_debug("%s: io error", __func__);
316 if ((r = write(ctx->report_pipe[1], ptr, (size_t)len)) == -1) {
317 fido_log_error(errno, "%s: write", __func__);
321 if (r < 0 || (size_t)r != (size_t)len) {
322 fido_log_debug("%s: %zd != %zu", __func__, r, (size_t)len);
328 removal_callback(void *context, IOReturn result, void *sender)
334 CFRunLoopStop(CFRunLoopGetCurrent());
342 if ((flags = fcntl(fd, F_GETFL)) == -1) {
343 fido_log_error(errno, "%s: fcntl F_GETFL", __func__);
347 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
348 fido_log_error(errno, "%s: fcntl F_SETFL", __func__);
356 disable_sigpipe(int fd)
360 if (fcntl(fd, F_SETNOSIGPIPE, &disabled) == -1) {
361 fido_log_error(errno, "%s: fcntl F_SETNOSIGPIPE", __func__);
369 fido_hid_open(const char *path)
372 io_registry_entry_t entry = MACH_PORT_NULL;
377 if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
378 fido_log_debug("%s: calloc", __func__);
382 ctx->report_pipe[0] = -1;
383 ctx->report_pipe[1] = -1;
385 if (pipe(ctx->report_pipe) == -1) {
386 fido_log_error(errno, "%s: pipe", __func__);
390 if (set_nonblock(ctx->report_pipe[0]) < 0 ||
391 set_nonblock(ctx->report_pipe[1]) < 0) {
392 fido_log_debug("%s: set_nonblock", __func__);
396 if (disable_sigpipe(ctx->report_pipe[1]) < 0) {
397 fido_log_debug("%s: disable_sigpipe", __func__);
401 if ((entry = IORegistryEntryFromPath(kIOMasterPortDefault,
402 path)) == MACH_PORT_NULL) {
403 fido_log_debug("%s: IORegistryEntryFromPath", __func__);
407 if ((ctx->ref = IOHIDDeviceCreate(kCFAllocatorDefault,
409 fido_log_debug("%s: IOHIDDeviceCreate", __func__);
413 if (get_report_len(ctx->ref, 0, &ctx->report_in_len) < 0 ||
414 get_report_len(ctx->ref, 1, &ctx->report_out_len) < 0) {
415 fido_log_debug("%s: get_report_len", __func__);
419 if (ctx->report_in_len > sizeof(ctx->report)) {
420 fido_log_debug("%s: report_in_len=%zu", __func__,
425 if (IOHIDDeviceOpen(ctx->ref,
426 kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) {
427 fido_log_debug("%s: IOHIDDeviceOpen", __func__);
431 if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p",
432 (void *)ctx->ref)) < 0 || (size_t)r >= sizeof(loop_id)) {
433 fido_log_debug("%s: snprintf", __func__);
437 if ((ctx->loop_id = CFStringCreateWithCString(NULL, loop_id,
438 kCFStringEncodingASCII)) == NULL) {
439 fido_log_debug("%s: CFStringCreateWithCString", __func__);
443 IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report,
444 (long)ctx->report_in_len, &report_callback, ctx);
445 IOHIDDeviceRegisterRemovalCallback(ctx->ref, &removal_callback, ctx);
449 if (entry != MACH_PORT_NULL)
450 IOObjectRelease(entry);
452 if (ok < 0 && ctx != NULL) {
453 if (ctx->ref != NULL)
455 if (ctx->loop_id != NULL)
456 CFRelease(ctx->loop_id);
457 if (ctx->report_pipe[0] != -1)
458 close(ctx->report_pipe[0]);
459 if (ctx->report_pipe[1] != -1)
460 close(ctx->report_pipe[1]);
469 fido_hid_close(void *handle)
471 struct hid_osx *ctx = handle;
473 IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report,
474 (long)ctx->report_in_len, NULL, ctx);
475 IOHIDDeviceRegisterRemovalCallback(ctx->ref, NULL, ctx);
477 if (IOHIDDeviceClose(ctx->ref,
478 kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess)
479 fido_log_debug("%s: IOHIDDeviceClose", __func__);
482 CFRelease(ctx->loop_id);
484 explicit_bzero(ctx->report, sizeof(ctx->report));
485 close(ctx->report_pipe[0]);
486 close(ctx->report_pipe[1]);
492 fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
497 return (FIDO_ERR_INTERNAL);
501 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
503 struct hid_osx *ctx = handle;
506 explicit_bzero(buf, len);
507 explicit_bzero(ctx->report, sizeof(ctx->report));
509 if (len != ctx->report_in_len || len > sizeof(ctx->report)) {
510 fido_log_debug("%s: len %zu", __func__, len);
514 IOHIDDeviceScheduleWithRunLoop(ctx->ref, CFRunLoopGetCurrent(),
518 ms = 5000; /* wait 5 seconds by default */
520 CFRunLoopRunInMode(ctx->loop_id, (double)ms/1000.0, true);
522 IOHIDDeviceUnscheduleFromRunLoop(ctx->ref, CFRunLoopGetCurrent(),
525 if ((r = read(ctx->report_pipe[0], buf, len)) == -1) {
526 fido_log_error(errno, "%s: read", __func__);
530 if (r < 0 || (size_t)r != len) {
531 fido_log_debug("%s: %zd != %zu", __func__, r, len);
539 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
541 struct hid_osx *ctx = handle;
543 if (len != ctx->report_out_len + 1 || len > LONG_MAX) {
544 fido_log_debug("%s: len %zu", __func__, len);
548 if (IOHIDDeviceSetReport(ctx->ref, kIOHIDReportTypeOutput, 0, buf + 1,
549 (long)(len - 1)) != kIOReturnSuccess) {
550 fido_log_debug("%s: IOHIDDeviceSetReport", __func__);
558 fido_hid_report_in_len(void *handle)
560 struct hid_osx *ctx = handle;
562 return (ctx->report_in_len);
566 fido_hid_report_out_len(void *handle)
568 struct hid_osx *ctx = handle;
570 return (ctx->report_out_len);