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