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 <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 struct hid_osx {
27 IOHIDDeviceRef ref;
28 CFStringRef loop_id;
29 int report_pipe[2];
30 size_t report_in_len;
31 size_t report_out_len;
32 unsigned char report[CTAP_MAX_REPORT_LEN];
33 };
34
35 static int
get_int32(IOHIDDeviceRef dev,CFStringRef key,int32_t * v)36 get_int32(IOHIDDeviceRef dev, CFStringRef key, int32_t *v)
37 {
38 CFTypeRef ref;
39
40 if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
41 CFGetTypeID(ref) != CFNumberGetTypeID()) {
42 fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
43 return (-1);
44 }
45
46 if (CFNumberGetType(ref) != kCFNumberSInt32Type &&
47 CFNumberGetType(ref) != kCFNumberSInt64Type) {
48 fido_log_debug("%s: CFNumberGetType", __func__);
49 return (-1);
50 }
51
52 if (CFNumberGetValue(ref, kCFNumberSInt32Type, v) == false) {
53 fido_log_debug("%s: CFNumberGetValue", __func__);
54 return (-1);
55 }
56
57 return (0);
58 }
59
60 static int
get_utf8(IOHIDDeviceRef dev,CFStringRef key,void * buf,size_t len)61 get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len)
62 {
63 CFTypeRef ref;
64
65 memset(buf, 0, len);
66
67 if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
68 CFGetTypeID(ref) != CFStringGetTypeID()) {
69 fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
70 return (-1);
71 }
72
73 if (CFStringGetCString(ref, buf, (long)len,
74 kCFStringEncodingUTF8) == false) {
75 fido_log_debug("%s: CFStringGetCString", __func__);
76 return (-1);
77 }
78
79 return (0);
80 }
81
82 static int
get_report_len(IOHIDDeviceRef dev,int dir,size_t * report_len)83 get_report_len(IOHIDDeviceRef dev, int dir, size_t *report_len)
84 {
85 CFStringRef key;
86 int32_t v;
87
88 if (dir == 0)
89 key = CFSTR(kIOHIDMaxInputReportSizeKey);
90 else
91 key = CFSTR(kIOHIDMaxOutputReportSizeKey);
92
93 if (get_int32(dev, key, &v) < 0) {
94 fido_log_debug("%s: get_int32/%d", __func__, dir);
95 return (-1);
96 }
97
98 if ((*report_len = (size_t)v) > CTAP_MAX_REPORT_LEN) {
99 fido_log_debug("%s: report_len=%zu", __func__, *report_len);
100 return (-1);
101 }
102
103 return (0);
104 }
105
106 static int
get_id(IOHIDDeviceRef dev,int16_t * vendor_id,int16_t * product_id)107 get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id)
108 {
109 int32_t vendor;
110 int32_t product;
111
112 if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 ||
113 vendor > UINT16_MAX) {
114 fido_log_debug("%s: get_int32 vendor", __func__);
115 return (-1);
116 }
117
118 if (get_int32(dev, CFSTR(kIOHIDProductIDKey), &product) < 0 ||
119 product > UINT16_MAX) {
120 fido_log_debug("%s: get_int32 product", __func__);
121 return (-1);
122 }
123
124 *vendor_id = (int16_t)vendor;
125 *product_id = (int16_t)product;
126
127 return (0);
128 }
129
130 static int
get_str(IOHIDDeviceRef dev,char ** manufacturer,char ** product)131 get_str(IOHIDDeviceRef dev, char **manufacturer, char **product)
132 {
133 char buf[512];
134 int ok = -1;
135
136 *manufacturer = NULL;
137 *product = NULL;
138
139 if (get_utf8(dev, CFSTR(kIOHIDManufacturerKey), buf, sizeof(buf)) < 0)
140 *manufacturer = strdup("");
141 else
142 *manufacturer = strdup(buf);
143
144 if (get_utf8(dev, CFSTR(kIOHIDProductKey), buf, sizeof(buf)) < 0)
145 *product = strdup("");
146 else
147 *product = strdup(buf);
148
149 if (*manufacturer == NULL || *product == NULL) {
150 fido_log_debug("%s: strdup", __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 *
get_path(IOHIDDeviceRef dev)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
is_fido(IOHIDDeviceRef dev)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
copy_info(fido_dev_info_t * di,IOHIDDeviceRef dev)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
fido_hid_manifest(fido_dev_info_t * devlist,size_t ilen,size_t * olen)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
report_callback(void * context,IOReturn result,void * dev,IOHIDReportType type,uint32_t id,uint8_t * ptr,CFIndex len)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
removal_callback(void * context,IOReturn result,void * sender)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
set_nonblock(int fd)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
disable_sigpipe(int fd)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 *
fido_hid_open(const char * path)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(kIOMainPortDefault,
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
fido_hid_close(void * handle)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
fido_hid_set_sigmask(void * handle,const fido_sigset_t * sigmask)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
fido_hid_read(void * handle,unsigned char * buf,size_t len,int ms)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
fido_hid_write(void * handle,const unsigned char * buf,size_t len)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
fido_hid_report_in_len(void * handle)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
fido_hid_report_out_len(void * handle)566 fido_hid_report_out_len(void *handle)
567 {
568 struct hid_osx *ctx = handle;
569
570 return (ctx->report_out_len);
571 }
572