]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libfido2/src/hid_osx.c
bsddialog: import version 0.0.2
[FreeBSD/FreeBSD.git] / contrib / libfido2 / src / hid_osx.c
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 }