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