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