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 <fcntl.h> 10 #include <string.h> 11 #ifdef HAVE_UNISTD_H 12 #include <unistd.h> 13 #endif 14 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 #define REPORT_LEN 65 23 24 struct dev { 25 IOHIDDeviceRef ref; 26 CFStringRef loop_id; 27 }; 28 29 static int 30 get_int32(IOHIDDeviceRef dev, CFStringRef key, int32_t *v) 31 { 32 CFTypeRef ref; 33 34 if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL || 35 CFGetTypeID(ref) != CFNumberGetTypeID()) { 36 fido_log_debug("%s: IOHIDDeviceGetProperty", __func__); 37 return (-1); 38 } 39 40 if (CFNumberGetType(ref) != kCFNumberSInt32Type && 41 CFNumberGetType(ref) != kCFNumberSInt64Type) { 42 fido_log_debug("%s: CFNumberGetType", __func__); 43 return (-1); 44 } 45 46 if (CFNumberGetValue(ref, kCFNumberSInt32Type, v) == false) { 47 fido_log_debug("%s: CFNumberGetValue", __func__); 48 return (-1); 49 } 50 51 return (0); 52 } 53 54 static int 55 get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len) 56 { 57 CFTypeRef ref; 58 59 memset(buf, 0, len); 60 61 if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL || 62 CFGetTypeID(ref) != CFStringGetTypeID()) { 63 fido_log_debug("%s: IOHIDDeviceGetProperty", __func__); 64 return (-1); 65 } 66 67 if (CFStringGetCString(ref, buf, len, kCFStringEncodingUTF8) == false) { 68 fido_log_debug("%s: CFStringGetCString", __func__); 69 return (-1); 70 } 71 72 return (0); 73 } 74 75 static bool 76 is_fido(IOHIDDeviceRef dev) 77 { 78 uint32_t usage_page; 79 int32_t report_len; 80 81 if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey), 82 (int32_t *)&usage_page) != 0 || usage_page != 0xf1d0) 83 return (false); 84 85 if (get_int32(dev, CFSTR(kIOHIDMaxInputReportSizeKey), 86 &report_len) < 0 || report_len != REPORT_LEN - 1) { 87 fido_log_debug("%s: unsupported report len", __func__); 88 return (false); 89 } 90 91 return (true); 92 } 93 94 static int 95 get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id) 96 { 97 int32_t vendor; 98 int32_t product; 99 100 if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 || 101 vendor > UINT16_MAX) { 102 fido_log_debug("%s: get_int32 vendor", __func__); 103 return (-1); 104 } 105 106 if (get_int32(dev, CFSTR(kIOHIDProductIDKey), &product) < 0 || 107 product > UINT16_MAX) { 108 fido_log_debug("%s: get_int32 product", __func__); 109 return (-1); 110 } 111 112 *vendor_id = (int16_t)vendor; 113 *product_id = (int16_t)product; 114 115 return (0); 116 } 117 118 static int 119 get_str(IOHIDDeviceRef dev, char **manufacturer, char **product) 120 { 121 char buf[512]; 122 int ok = -1; 123 124 *manufacturer = NULL; 125 *product = NULL; 126 127 if (get_utf8(dev, CFSTR(kIOHIDManufacturerKey), buf, sizeof(buf)) < 0) { 128 fido_log_debug("%s: get_utf8 manufacturer", __func__); 129 goto fail; 130 } 131 132 if ((*manufacturer = strdup(buf)) == NULL) { 133 fido_log_debug("%s: strdup manufacturer", __func__); 134 goto fail; 135 } 136 137 if (get_utf8(dev, CFSTR(kIOHIDProductKey), buf, sizeof(buf)) < 0) { 138 fido_log_debug("%s: get_utf8 product", __func__); 139 goto fail; 140 } 141 142 if ((*product = strdup(buf)) == NULL) { 143 fido_log_debug("%s: strdup product", __func__); 144 goto fail; 145 } 146 147 ok = 0; 148 fail: 149 if (ok < 0) { 150 free(*manufacturer); 151 free(*product); 152 *manufacturer = NULL; 153 *product = NULL; 154 } 155 156 return (ok); 157 } 158 159 static char * 160 get_path(IOHIDDeviceRef dev) 161 { 162 io_service_t s; 163 io_string_t path; 164 165 if ((s = IOHIDDeviceGetService(dev)) == MACH_PORT_NULL) { 166 fido_log_debug("%s: IOHIDDeviceGetService", __func__); 167 return (NULL); 168 } 169 170 if (IORegistryEntryGetPath(s, kIOServicePlane, path) != KERN_SUCCESS) { 171 fido_log_debug("%s: IORegistryEntryGetPath", __func__); 172 return (NULL); 173 } 174 175 return (strdup(path)); 176 } 177 178 static int 179 copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev) 180 { 181 memset(di, 0, sizeof(*di)); 182 183 if (is_fido(dev) == false) 184 return (-1); 185 186 if (get_id(dev, &di->vendor_id, &di->product_id) < 0 || 187 get_str(dev, &di->manufacturer, &di->product) < 0 || 188 (di->path = get_path(dev)) == NULL) { 189 free(di->path); 190 free(di->manufacturer); 191 free(di->product); 192 explicit_bzero(di, sizeof(*di)); 193 return (-1); 194 } 195 196 return (0); 197 } 198 199 int 200 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) 201 { 202 IOHIDManagerRef manager = NULL; 203 CFSetRef devset = NULL; 204 CFIndex devcnt; 205 IOHIDDeviceRef *devs = NULL; 206 int r = FIDO_ERR_INTERNAL; 207 208 *olen = 0; 209 210 if (ilen == 0) 211 return (FIDO_OK); /* nothing to do */ 212 213 if (devlist == NULL) 214 return (FIDO_ERR_INVALID_ARGUMENT); 215 216 if ((manager = IOHIDManagerCreate(kCFAllocatorDefault, 217 kIOHIDManagerOptionNone)) == NULL) { 218 fido_log_debug("%s: IOHIDManagerCreate", __func__); 219 goto fail; 220 } 221 222 IOHIDManagerSetDeviceMatching(manager, NULL); 223 224 if ((devset = IOHIDManagerCopyDevices(manager)) == NULL) { 225 fido_log_debug("%s: IOHIDManagerCopyDevices", __func__); 226 goto fail; 227 } 228 229 if ((devcnt = CFSetGetCount(devset)) < 0) { 230 fido_log_debug("%s: CFSetGetCount", __func__); 231 goto fail; 232 } 233 234 if ((devs = calloc(devcnt, sizeof(*devs))) == NULL) { 235 fido_log_debug("%s: calloc", __func__); 236 goto fail; 237 } 238 239 CFSetGetValues(devset, (void *)devs); 240 241 for (CFIndex i = 0; i < devcnt; i++) { 242 if (copy_info(&devlist[*olen], devs[i]) == 0) { 243 devlist[*olen].io = (fido_dev_io_t) { 244 fido_hid_open, 245 fido_hid_close, 246 fido_hid_read, 247 fido_hid_write, 248 }; 249 if (++(*olen) == ilen) 250 break; 251 } 252 } 253 254 r = FIDO_OK; 255 fail: 256 if (manager != NULL) 257 CFRelease(manager); 258 if (devset != NULL) 259 CFRelease(devset); 260 261 free(devs); 262 263 return (r); 264 } 265 266 void * 267 fido_hid_open(const char *path) 268 { 269 io_registry_entry_t entry = MACH_PORT_NULL; 270 struct dev *dev = NULL; 271 int ok = -1; 272 int r; 273 char loop_id[32]; 274 275 if ((dev = calloc(1, sizeof(*dev))) == NULL) { 276 fido_log_debug("%s: calloc", __func__); 277 goto fail; 278 } 279 280 if ((entry = IORegistryEntryFromPath(kIOMasterPortDefault, 281 path)) == MACH_PORT_NULL) { 282 fido_log_debug("%s: IORegistryEntryFromPath", __func__); 283 goto fail; 284 } 285 286 if ((dev->ref = IOHIDDeviceCreate(kCFAllocatorDefault, 287 entry)) == NULL) { 288 fido_log_debug("%s: IOHIDDeviceCreate", __func__); 289 goto fail; 290 } 291 292 if (IOHIDDeviceOpen(dev->ref, 293 kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) { 294 fido_log_debug("%s: IOHIDDeviceOpen", __func__); 295 goto fail; 296 } 297 298 if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p", 299 (void *)dev->ref)) < 0 || (size_t)r >= sizeof(loop_id)) { 300 fido_log_debug("%s: snprintf", __func__); 301 goto fail; 302 } 303 304 if ((dev->loop_id = CFStringCreateWithCString(NULL, loop_id, 305 kCFStringEncodingASCII)) == NULL) { 306 fido_log_debug("%s: CFStringCreateWithCString", __func__); 307 goto fail; 308 } 309 310 ok = 0; 311 fail: 312 if (entry != MACH_PORT_NULL) 313 IOObjectRelease(entry); 314 315 if (ok < 0 && dev != NULL) { 316 if (dev->ref != NULL) 317 CFRelease(dev->ref); 318 if (dev->loop_id != NULL) 319 CFRelease(dev->loop_id); 320 free(dev); 321 dev = NULL; 322 } 323 324 return (dev); 325 } 326 327 void 328 fido_hid_close(void *handle) 329 { 330 struct dev *dev = handle; 331 332 if (IOHIDDeviceClose(dev->ref, 333 kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) 334 fido_log_debug("%s: IOHIDDeviceClose", __func__); 335 336 CFRelease(dev->ref); 337 CFRelease(dev->loop_id); 338 339 free(dev); 340 } 341 342 static void 343 read_callback(void *context, IOReturn result, void *dev, IOHIDReportType type, 344 uint32_t report_id, uint8_t *report, CFIndex report_len) 345 { 346 (void)context; 347 (void)dev; 348 (void)report; 349 350 if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput || 351 report_id != 0 || report_len != REPORT_LEN - 1) { 352 fido_log_debug("%s: io error", __func__); 353 } 354 } 355 356 static void 357 removal_callback(void *context, IOReturn result, void *sender) 358 { 359 (void)context; 360 (void)result; 361 (void)sender; 362 363 CFRunLoopStop(CFRunLoopGetCurrent()); 364 } 365 366 int 367 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) 368 { 369 struct dev *dev = handle; 370 CFRunLoopRunResult r; 371 372 (void)ms; /* XXX */ 373 374 if (len != REPORT_LEN - 1) { 375 fido_log_debug("%s: invalid len", __func__); 376 return (-1); 377 } 378 379 explicit_bzero(buf, len); 380 381 IOHIDDeviceRegisterInputReportCallback(dev->ref, buf, len, 382 &read_callback, NULL); 383 IOHIDDeviceRegisterRemovalCallback(dev->ref, &removal_callback, dev); 384 IOHIDDeviceScheduleWithRunLoop(dev->ref, CFRunLoopGetCurrent(), 385 dev->loop_id); 386 387 do 388 r = CFRunLoopRunInMode(dev->loop_id, 0.003, true); 389 while (r != kCFRunLoopRunHandledSource); 390 391 IOHIDDeviceRegisterInputReportCallback(dev->ref, buf, len, NULL, NULL); 392 IOHIDDeviceRegisterRemovalCallback(dev->ref, NULL, NULL); 393 IOHIDDeviceUnscheduleFromRunLoop(dev->ref, CFRunLoopGetCurrent(), 394 dev->loop_id); 395 396 return (REPORT_LEN - 1); 397 } 398 399 int 400 fido_hid_write(void *handle, const unsigned char *buf, size_t len) 401 { 402 struct dev *dev = handle; 403 404 if (len != REPORT_LEN) { 405 fido_log_debug("%s: invalid len", __func__); 406 return (-1); 407 } 408 409 if (IOHIDDeviceSetReport(dev->ref, kIOHIDReportTypeOutput, 0, buf + 1, 410 len - 1) != kIOReturnSuccess) { 411 fido_log_debug("%s: IOHIDDeviceSetReport", __func__); 412 return (-1); 413 } 414 415 return (REPORT_LEN); 416 } 417