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