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