summaryrefslogtreecommitdiffstats
path: root/src/hid_osx.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/hid_osx.c')
-rw-r--r--src/hid_osx.c597
1 files changed, 597 insertions, 0 deletions
diff --git a/src/hid_osx.c b/src/hid_osx.c
new file mode 100644
index 0000000..9309762
--- /dev/null
+++ b/src/hid_osx.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2019-2022 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <Availability.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <IOKit/hid/IOHIDManager.h>
+
+#include "fido.h"
+
+#if __MAC_OS_X_VERSION_MIN_REQUIRED < 120000
+#define kIOMainPortDefault kIOMasterPortDefault
+#endif
+
+#define IOREG "ioreg://"
+
+struct hid_osx {
+ IOHIDDeviceRef ref;
+ CFStringRef loop_id;
+ int report_pipe[2];
+ size_t report_in_len;
+ size_t report_out_len;
+ unsigned char report[CTAP_MAX_REPORT_LEN];
+};
+
+static int
+get_int32(IOHIDDeviceRef dev, CFStringRef key, int32_t *v)
+{
+ CFTypeRef ref;
+
+ if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
+ CFGetTypeID(ref) != CFNumberGetTypeID()) {
+ fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
+ return (-1);
+ }
+
+ if (CFNumberGetType(ref) != kCFNumberSInt32Type &&
+ CFNumberGetType(ref) != kCFNumberSInt64Type) {
+ fido_log_debug("%s: CFNumberGetType", __func__);
+ return (-1);
+ }
+
+ if (CFNumberGetValue(ref, kCFNumberSInt32Type, v) == false) {
+ fido_log_debug("%s: CFNumberGetValue", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len)
+{
+ CFTypeRef ref;
+
+ memset(buf, 0, len);
+
+ if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
+ CFGetTypeID(ref) != CFStringGetTypeID()) {
+ fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
+ return (-1);
+ }
+
+ if (CFStringGetCString(ref, buf, (long)len,
+ kCFStringEncodingUTF8) == false) {
+ fido_log_debug("%s: CFStringGetCString", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+get_report_len(IOHIDDeviceRef dev, int dir, size_t *report_len)
+{
+ CFStringRef key;
+ int32_t v;
+
+ if (dir == 0)
+ key = CFSTR(kIOHIDMaxInputReportSizeKey);
+ else
+ key = CFSTR(kIOHIDMaxOutputReportSizeKey);
+
+ if (get_int32(dev, key, &v) < 0) {
+ fido_log_debug("%s: get_int32/%d", __func__, dir);
+ return (-1);
+ }
+
+ if ((*report_len = (size_t)v) > CTAP_MAX_REPORT_LEN) {
+ fido_log_debug("%s: report_len=%zu", __func__, *report_len);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id)
+{
+ int32_t vendor;
+ int32_t product;
+
+ if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 ||
+ vendor > UINT16_MAX) {
+ fido_log_debug("%s: get_int32 vendor", __func__);
+ return (-1);
+ }
+
+ if (get_int32(dev, CFSTR(kIOHIDProductIDKey), &product) < 0 ||
+ product > UINT16_MAX) {
+ fido_log_debug("%s: get_int32 product", __func__);
+ return (-1);
+ }
+
+ *vendor_id = (int16_t)vendor;
+ *product_id = (int16_t)product;
+
+ return (0);
+}
+
+static int
+get_str(IOHIDDeviceRef dev, char **manufacturer, char **product)
+{
+ char buf[512];
+ int ok = -1;
+
+ *manufacturer = NULL;
+ *product = NULL;
+
+ if (get_utf8(dev, CFSTR(kIOHIDManufacturerKey), buf, sizeof(buf)) < 0)
+ *manufacturer = strdup("");
+ else
+ *manufacturer = strdup(buf);
+
+ if (get_utf8(dev, CFSTR(kIOHIDProductKey), buf, sizeof(buf)) < 0)
+ *product = strdup("");
+ else
+ *product = strdup(buf);
+
+ if (*manufacturer == NULL || *product == NULL) {
+ fido_log_debug("%s: strdup", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(*manufacturer);
+ free(*product);
+ *manufacturer = NULL;
+ *product = NULL;
+ }
+
+ return (ok);
+}
+
+static char *
+get_path(IOHIDDeviceRef dev)
+{
+ io_service_t s;
+ uint64_t id;
+ char *path;
+
+ if ((s = IOHIDDeviceGetService(dev)) == MACH_PORT_NULL) {
+ fido_log_debug("%s: IOHIDDeviceGetService", __func__);
+ return (NULL);
+ }
+
+ if (IORegistryEntryGetRegistryEntryID(s, &id) != KERN_SUCCESS) {
+ fido_log_debug("%s: IORegistryEntryGetRegistryEntryID",
+ __func__);
+ return (NULL);
+ }
+
+ if (asprintf(&path, "%s%llu", IOREG, (unsigned long long)id) == -1) {
+ fido_log_error(errno, "%s: asprintf", __func__);
+ return (NULL);
+ }
+
+ return (path);
+}
+
+static bool
+is_fido(IOHIDDeviceRef dev)
+{
+ char buf[32];
+ uint32_t usage_page;
+
+ if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey),
+ (int32_t *)&usage_page) < 0 || usage_page != 0xf1d0)
+ return (false);
+
+ if (get_utf8(dev, CFSTR(kIOHIDTransportKey), buf, sizeof(buf)) < 0) {
+ fido_log_debug("%s: get_utf8 transport", __func__);
+ return (false);
+ }
+
+#ifndef FIDO_HID_ANY
+ if (strcasecmp(buf, "usb") != 0) {
+ fido_log_debug("%s: transport", __func__);
+ return (false);
+ }
+#endif
+
+ return (true);
+}
+
+static int
+copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev)
+{
+ memset(di, 0, sizeof(*di));
+
+ if (is_fido(dev) == false)
+ return (-1);
+
+ if (get_id(dev, &di->vendor_id, &di->product_id) < 0 ||
+ get_str(dev, &di->manufacturer, &di->product) < 0 ||
+ (di->path = get_path(dev)) == NULL) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ IOHIDManagerRef manager = NULL;
+ CFSetRef devset = NULL;
+ size_t devcnt;
+ CFIndex n;
+ IOHIDDeviceRef *devs = NULL;
+ int r = FIDO_ERR_INTERNAL;
+
+ *olen = 0;
+
+ if (ilen == 0)
+ return (FIDO_OK); /* nothing to do */
+
+ if (devlist == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((manager = IOHIDManagerCreate(kCFAllocatorDefault,
+ kIOHIDManagerOptionNone)) == NULL) {
+ fido_log_debug("%s: IOHIDManagerCreate", __func__);
+ goto fail;
+ }
+
+ IOHIDManagerSetDeviceMatching(manager, NULL);
+
+ if ((devset = IOHIDManagerCopyDevices(manager)) == NULL) {
+ fido_log_debug("%s: IOHIDManagerCopyDevices", __func__);
+ goto fail;
+ }
+
+ if ((n = CFSetGetCount(devset)) < 0) {
+ fido_log_debug("%s: CFSetGetCount", __func__);
+ goto fail;
+ }
+
+ devcnt = (size_t)n;
+
+ if ((devs = calloc(devcnt, sizeof(*devs))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ goto fail;
+ }
+
+ CFSetGetValues(devset, (void *)devs);
+
+ for (size_t i = 0; i < devcnt; i++) {
+ if (copy_info(&devlist[*olen], devs[i]) == 0) {
+ devlist[*olen].io = (fido_dev_io_t) {
+ fido_hid_open,
+ fido_hid_close,
+ fido_hid_read,
+ fido_hid_write,
+ };
+ if (++(*olen) == ilen)
+ break;
+ }
+ }
+
+ r = FIDO_OK;
+fail:
+ if (manager != NULL)
+ CFRelease(manager);
+ if (devset != NULL)
+ CFRelease(devset);
+
+ free(devs);
+
+ return (r);
+}
+
+static void
+report_callback(void *context, IOReturn result, void *dev, IOHIDReportType type,
+ uint32_t id, uint8_t *ptr, CFIndex len)
+{
+ struct hid_osx *ctx = context;
+ ssize_t r;
+
+ (void)dev;
+
+ if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput ||
+ id != 0 || len < 0 || (size_t)len != ctx->report_in_len) {
+ fido_log_debug("%s: io error", __func__);
+ return;
+ }
+
+ if ((r = write(ctx->report_pipe[1], ptr, (size_t)len)) == -1) {
+ fido_log_error(errno, "%s: write", __func__);
+ return;
+ }
+
+ if (r < 0 || (size_t)r != (size_t)len) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, (size_t)len);
+ return;
+ }
+}
+
+static void
+removal_callback(void *context, IOReturn result, void *sender)
+{
+ (void)context;
+ (void)result;
+ (void)sender;
+
+ CFRunLoopStop(CFRunLoopGetCurrent());
+}
+
+static int
+set_nonblock(int fd)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL)) == -1) {
+ fido_log_error(errno, "%s: fcntl F_GETFL", __func__);
+ return (-1);
+ }
+
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ fido_log_error(errno, "%s: fcntl F_SETFL", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+disable_sigpipe(int fd)
+{
+ int disabled = 1;
+
+ if (fcntl(fd, F_SETNOSIGPIPE, &disabled) == -1) {
+ fido_log_error(errno, "%s: fcntl F_SETNOSIGPIPE", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static io_registry_entry_t
+get_ioreg_entry(const char *path)
+{
+ uint64_t id;
+
+ if (strncmp(path, IOREG, strlen(IOREG)) != 0)
+ return (IORegistryEntryFromPath(kIOMainPortDefault, path));
+
+ if (fido_to_uint64(path + strlen(IOREG), 10, &id) == -1) {
+ fido_log_debug("%s: fido_to_uint64", __func__);
+ return (MACH_PORT_NULL);
+ }
+
+ return (IOServiceGetMatchingService(kIOMainPortDefault,
+ IORegistryEntryIDMatching(id)));
+}
+
+void *
+fido_hid_open(const char *path)
+{
+ struct hid_osx *ctx;
+ io_registry_entry_t entry = MACH_PORT_NULL;
+ char loop_id[32];
+ int ok = -1;
+ int r;
+
+ if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ goto fail;
+ }
+
+ ctx->report_pipe[0] = -1;
+ ctx->report_pipe[1] = -1;
+
+ if (pipe(ctx->report_pipe) == -1) {
+ fido_log_error(errno, "%s: pipe", __func__);
+ goto fail;
+ }
+
+ if (set_nonblock(ctx->report_pipe[0]) < 0 ||
+ set_nonblock(ctx->report_pipe[1]) < 0) {
+ fido_log_debug("%s: set_nonblock", __func__);
+ goto fail;
+ }
+
+ if (disable_sigpipe(ctx->report_pipe[1]) < 0) {
+ fido_log_debug("%s: disable_sigpipe", __func__);
+ goto fail;
+ }
+
+ if ((entry = get_ioreg_entry(path)) == MACH_PORT_NULL) {
+ fido_log_debug("%s: get_ioreg_entry: %s", __func__, path);
+ goto fail;
+ }
+
+ if ((ctx->ref = IOHIDDeviceCreate(kCFAllocatorDefault,
+ entry)) == NULL) {
+ fido_log_debug("%s: IOHIDDeviceCreate", __func__);
+ goto fail;
+ }
+
+ if (get_report_len(ctx->ref, 0, &ctx->report_in_len) < 0 ||
+ get_report_len(ctx->ref, 1, &ctx->report_out_len) < 0) {
+ fido_log_debug("%s: get_report_len", __func__);
+ goto fail;
+ }
+
+ if (ctx->report_in_len > sizeof(ctx->report)) {
+ fido_log_debug("%s: report_in_len=%zu", __func__,
+ ctx->report_in_len);
+ goto fail;
+ }
+
+ if (IOHIDDeviceOpen(ctx->ref,
+ kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) {
+ fido_log_debug("%s: IOHIDDeviceOpen", __func__);
+ goto fail;
+ }
+
+ if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p",
+ (void *)ctx->ref)) < 0 || (size_t)r >= sizeof(loop_id)) {
+ fido_log_debug("%s: snprintf", __func__);
+ goto fail;
+ }
+
+ if ((ctx->loop_id = CFStringCreateWithCString(NULL, loop_id,
+ kCFStringEncodingASCII)) == NULL) {
+ fido_log_debug("%s: CFStringCreateWithCString", __func__);
+ goto fail;
+ }
+
+ IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report,
+ (long)ctx->report_in_len, &report_callback, ctx);
+ IOHIDDeviceRegisterRemovalCallback(ctx->ref, &removal_callback, ctx);
+
+ ok = 0;
+fail:
+ if (entry != MACH_PORT_NULL)
+ IOObjectRelease(entry);
+
+ if (ok < 0 && ctx != NULL) {
+ if (ctx->ref != NULL)
+ CFRelease(ctx->ref);
+ if (ctx->loop_id != NULL)
+ CFRelease(ctx->loop_id);
+ if (ctx->report_pipe[0] != -1)
+ close(ctx->report_pipe[0]);
+ if (ctx->report_pipe[1] != -1)
+ close(ctx->report_pipe[1]);
+ free(ctx);
+ ctx = NULL;
+ }
+
+ return (ctx);
+}
+
+void
+fido_hid_close(void *handle)
+{
+ struct hid_osx *ctx = handle;
+
+ IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report,
+ (long)ctx->report_in_len, NULL, ctx);
+ IOHIDDeviceRegisterRemovalCallback(ctx->ref, NULL, ctx);
+
+ if (IOHIDDeviceClose(ctx->ref,
+ kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess)
+ fido_log_debug("%s: IOHIDDeviceClose", __func__);
+
+ CFRelease(ctx->ref);
+ CFRelease(ctx->loop_id);
+
+ explicit_bzero(ctx->report, sizeof(ctx->report));
+ close(ctx->report_pipe[0]);
+ close(ctx->report_pipe[1]);
+
+ free(ctx);
+}
+
+int
+fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
+{
+ (void)handle;
+ (void)sigmask;
+
+ return (FIDO_ERR_INTERNAL);
+}
+
+int
+fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ struct hid_osx *ctx = handle;
+ ssize_t r;
+
+ explicit_bzero(buf, len);
+ explicit_bzero(ctx->report, sizeof(ctx->report));
+
+ if (len != ctx->report_in_len || len > sizeof(ctx->report)) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ IOHIDDeviceScheduleWithRunLoop(ctx->ref, CFRunLoopGetCurrent(),
+ ctx->loop_id);
+
+ if (ms == -1)
+ ms = 5000; /* wait 5 seconds by default */
+
+ CFRunLoopRunInMode(ctx->loop_id, (double)ms/1000.0, true);
+
+ IOHIDDeviceUnscheduleFromRunLoop(ctx->ref, CFRunLoopGetCurrent(),
+ ctx->loop_id);
+
+ if ((r = read(ctx->report_pipe[0], buf, len)) == -1) {
+ fido_log_error(errno, "%s: read", __func__);
+ return (-1);
+ }
+
+ if (r < 0 || (size_t)r != len) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, len);
+ return (-1);
+ }
+
+ return ((int)len);
+}
+
+int
+fido_hid_write(void *handle, const unsigned char *buf, size_t len)
+{
+ struct hid_osx *ctx = handle;
+
+ if (len != ctx->report_out_len + 1 || len > LONG_MAX) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ if (IOHIDDeviceSetReport(ctx->ref, kIOHIDReportTypeOutput, 0, buf + 1,
+ (long)(len - 1)) != kIOReturnSuccess) {
+ fido_log_debug("%s: IOHIDDeviceSetReport", __func__);
+ return (-1);
+ }
+
+ return ((int)len);
+}
+
+size_t
+fido_hid_report_in_len(void *handle)
+{
+ struct hid_osx *ctx = handle;
+
+ return (ctx->report_in_len);
+}
+
+size_t
+fido_hid_report_out_len(void *handle)
+{
+ struct hid_osx *ctx = handle;
+
+ return (ctx->report_out_len);
+}