xref: /freebsd/contrib/libfido2/src/hid_netbsd.c (revision 2ccfa855)
10afa8e06SEd Maste /*
20afa8e06SEd Maste  * Copyright (c) 2020 Yubico AB. All rights reserved.
30afa8e06SEd Maste  * Use of this source code is governed by a BSD-style
40afa8e06SEd Maste  * license that can be found in the LICENSE file.
5*2ccfa855SEd Maste  * SPDX-License-Identifier: BSD-2-Clause
60afa8e06SEd Maste  */
70afa8e06SEd Maste 
80afa8e06SEd Maste #include <sys/types.h>
90afa8e06SEd Maste #include <sys/ioctl.h>
100afa8e06SEd Maste 
110afa8e06SEd Maste #include <dev/usb/usb.h>
120afa8e06SEd Maste #include <dev/usb/usbhid.h>
130afa8e06SEd Maste 
140afa8e06SEd Maste #include <errno.h>
150afa8e06SEd Maste #include <poll.h>
160afa8e06SEd Maste #include <signal.h>
170afa8e06SEd Maste #include <stdbool.h>
180afa8e06SEd Maste #include <stdio.h>
190afa8e06SEd Maste #include <stdlib.h>
200afa8e06SEd Maste #include <string.h>
210afa8e06SEd Maste #include <unistd.h>
220afa8e06SEd Maste 
230afa8e06SEd Maste #include "fido.h"
240afa8e06SEd Maste 
250afa8e06SEd Maste #define MAX_UHID	64
260afa8e06SEd Maste 
270afa8e06SEd Maste struct hid_netbsd {
280afa8e06SEd Maste 	int             fd;
290afa8e06SEd Maste 	size_t          report_in_len;
300afa8e06SEd Maste 	size_t          report_out_len;
310afa8e06SEd Maste 	sigset_t        sigmask;
320afa8e06SEd Maste 	const sigset_t *sigmaskp;
330afa8e06SEd Maste };
340afa8e06SEd Maste 
350afa8e06SEd Maste /* Hack to make this work with newer kernels even if /usr/include is old.  */
360afa8e06SEd Maste #if __NetBSD_Version__ < 901000000	/* 9.1 */
370afa8e06SEd Maste #define	USB_HID_GET_RAW	_IOR('h', 1, int)
380afa8e06SEd Maste #define	USB_HID_SET_RAW	_IOW('h', 2, int)
390afa8e06SEd Maste #endif
400afa8e06SEd Maste 
410afa8e06SEd Maste static bool
is_fido(int fd)420afa8e06SEd Maste is_fido(int fd)
430afa8e06SEd Maste {
440afa8e06SEd Maste 	struct usb_ctl_report_desc	ucrd;
450afa8e06SEd Maste 	uint32_t			usage_page = 0;
460afa8e06SEd Maste 	int				raw = 1;
470afa8e06SEd Maste 
480afa8e06SEd Maste 	memset(&ucrd, 0, sizeof(ucrd));
490afa8e06SEd Maste 
500afa8e06SEd Maste 	if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd) == -1) {
510afa8e06SEd Maste 		fido_log_error(errno, "%s: ioctl", __func__);
520afa8e06SEd Maste 		return (false);
530afa8e06SEd Maste 	}
540afa8e06SEd Maste 
550afa8e06SEd Maste 	if (ucrd.ucrd_size < 0 ||
560afa8e06SEd Maste 	    (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||
570afa8e06SEd Maste 	    fido_hid_get_usage(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,
580afa8e06SEd Maste 		&usage_page) < 0) {
590afa8e06SEd Maste 		fido_log_debug("%s: fido_hid_get_usage", __func__);
600afa8e06SEd Maste 		return (false);
610afa8e06SEd Maste 	}
620afa8e06SEd Maste 
630afa8e06SEd Maste 	if (usage_page != 0xf1d0)
640afa8e06SEd Maste 		return (false);
650afa8e06SEd Maste 
660afa8e06SEd Maste 	/*
670afa8e06SEd Maste 	 * This step is not strictly necessary -- NetBSD puts fido
680afa8e06SEd Maste 	 * devices into raw mode automatically by default, but in
690afa8e06SEd Maste 	 * principle that might change, and this serves as a test to
700afa8e06SEd Maste 	 * verify that we're running on a kernel with support for raw
710afa8e06SEd Maste 	 * mode at all so we don't get confused issuing writes that try
720afa8e06SEd Maste 	 * to set the report descriptor rather than transfer data on
730afa8e06SEd Maste 	 * the output interrupt pipe as we need.
740afa8e06SEd Maste 	 */
750afa8e06SEd Maste 	if (ioctl(fd, IOCTL_REQ(USB_HID_SET_RAW), &raw) == -1) {
760afa8e06SEd Maste 		fido_log_error(errno, "%s: unable to set raw", __func__);
770afa8e06SEd Maste 		return (false);
780afa8e06SEd Maste 	}
790afa8e06SEd Maste 
800afa8e06SEd Maste 	return (true);
810afa8e06SEd Maste }
820afa8e06SEd Maste 
830afa8e06SEd Maste static int
copy_info(fido_dev_info_t * di,const char * path)840afa8e06SEd Maste copy_info(fido_dev_info_t *di, const char *path)
850afa8e06SEd Maste {
860afa8e06SEd Maste 	int			fd = -1;
870afa8e06SEd Maste 	int			ok = -1;
880afa8e06SEd Maste 	struct usb_device_info	udi;
890afa8e06SEd Maste 
900afa8e06SEd Maste 	memset(di, 0, sizeof(*di));
910afa8e06SEd Maste 	memset(&udi, 0, sizeof(udi));
920afa8e06SEd Maste 
930afa8e06SEd Maste 	if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0)
940afa8e06SEd Maste 		goto fail;
950afa8e06SEd Maste 
960afa8e06SEd Maste 	if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
970afa8e06SEd Maste 		fido_log_error(errno, "%s: ioctl", __func__);
980afa8e06SEd Maste 		goto fail;
990afa8e06SEd Maste 	}
1000afa8e06SEd Maste 
1010afa8e06SEd Maste 	if ((di->path = strdup(path)) == NULL ||
1020afa8e06SEd Maste 	    (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
1030afa8e06SEd Maste 	    (di->product = strdup(udi.udi_product)) == NULL)
1040afa8e06SEd Maste 		goto fail;
1050afa8e06SEd Maste 
1060afa8e06SEd Maste 	di->vendor_id = (int16_t)udi.udi_vendorNo;
1070afa8e06SEd Maste 	di->product_id = (int16_t)udi.udi_productNo;
1080afa8e06SEd Maste 
1090afa8e06SEd Maste 	ok = 0;
1100afa8e06SEd Maste fail:
1110afa8e06SEd Maste 	if (fd != -1 && close(fd) == -1)
1120afa8e06SEd Maste 		fido_log_error(errno, "%s: close", __func__);
1130afa8e06SEd Maste 
1140afa8e06SEd Maste 	if (ok < 0) {
1150afa8e06SEd Maste 		free(di->path);
1160afa8e06SEd Maste 		free(di->manufacturer);
1170afa8e06SEd Maste 		free(di->product);
1180afa8e06SEd Maste 		explicit_bzero(di, sizeof(*di));
1190afa8e06SEd Maste 	}
1200afa8e06SEd Maste 
1210afa8e06SEd Maste 	return (ok);
1220afa8e06SEd Maste }
1230afa8e06SEd Maste 
1240afa8e06SEd Maste int
fido_hid_manifest(fido_dev_info_t * devlist,size_t ilen,size_t * olen)1250afa8e06SEd Maste fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
1260afa8e06SEd Maste {
1270afa8e06SEd Maste 	char	path[64];
1280afa8e06SEd Maste 	size_t	i;
1290afa8e06SEd Maste 
1300afa8e06SEd Maste 	*olen = 0;
1310afa8e06SEd Maste 
1320afa8e06SEd Maste 	if (ilen == 0)
1330afa8e06SEd Maste 		return (FIDO_OK); /* nothing to do */
1340afa8e06SEd Maste 
1350afa8e06SEd Maste 	if (devlist == NULL || olen == NULL)
1360afa8e06SEd Maste 		return (FIDO_ERR_INVALID_ARGUMENT);
1370afa8e06SEd Maste 
1380afa8e06SEd Maste 	for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) {
1390afa8e06SEd Maste 		snprintf(path, sizeof(path), "/dev/uhid%zu", i);
1400afa8e06SEd Maste 		if (copy_info(&devlist[*olen], path) == 0) {
1410afa8e06SEd Maste 			devlist[*olen].io = (fido_dev_io_t) {
1420afa8e06SEd Maste 				fido_hid_open,
1430afa8e06SEd Maste 				fido_hid_close,
1440afa8e06SEd Maste 				fido_hid_read,
1450afa8e06SEd Maste 				fido_hid_write,
1460afa8e06SEd Maste 			};
1470afa8e06SEd Maste 			++(*olen);
1480afa8e06SEd Maste 		}
1490afa8e06SEd Maste 	}
1500afa8e06SEd Maste 
1510afa8e06SEd Maste 	return (FIDO_OK);
1520afa8e06SEd Maste }
1530afa8e06SEd Maste 
1540afa8e06SEd Maste /*
1550afa8e06SEd Maste  * Workaround for NetBSD (as of 201910) bug that loses
1560afa8e06SEd Maste  * sync of DATA0/DATA1 sequence bit across uhid open/close.
1570afa8e06SEd Maste  * Send pings until we get a response - early pings with incorrect
1580afa8e06SEd Maste  * sequence bits will be ignored as duplicate packets by the device.
1590afa8e06SEd Maste  */
1600afa8e06SEd Maste static int
terrible_ping_kludge(struct hid_netbsd * ctx)1610afa8e06SEd Maste terrible_ping_kludge(struct hid_netbsd *ctx)
1620afa8e06SEd Maste {
1630afa8e06SEd Maste 	u_char data[256];
1640afa8e06SEd Maste 	int i, n;
1650afa8e06SEd Maste 	struct pollfd pfd;
1660afa8e06SEd Maste 
1670afa8e06SEd Maste 	if (sizeof(data) < ctx->report_out_len + 1)
1680afa8e06SEd Maste 		return -1;
1690afa8e06SEd Maste 	for (i = 0; i < 4; i++) {
1700afa8e06SEd Maste 		memset(data, 0, sizeof(data));
1710afa8e06SEd Maste 		/* broadcast channel ID */
1720afa8e06SEd Maste 		data[1] = 0xff;
1730afa8e06SEd Maste 		data[2] = 0xff;
1740afa8e06SEd Maste 		data[3] = 0xff;
1750afa8e06SEd Maste 		data[4] = 0xff;
1760afa8e06SEd Maste 		/* Ping command */
1770afa8e06SEd Maste 		data[5] = 0x81;
1780afa8e06SEd Maste 		/* One byte ping only, Vasili */
1790afa8e06SEd Maste 		data[6] = 0;
1800afa8e06SEd Maste 		data[7] = 1;
1810afa8e06SEd Maste 		fido_log_debug("%s: send ping %d", __func__, i);
1820afa8e06SEd Maste 		if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1)
1830afa8e06SEd Maste 			return -1;
1840afa8e06SEd Maste 		fido_log_debug("%s: wait reply", __func__);
1850afa8e06SEd Maste 		memset(&pfd, 0, sizeof(pfd));
1860afa8e06SEd Maste 		pfd.fd = ctx->fd;
1870afa8e06SEd Maste 		pfd.events = POLLIN;
1880afa8e06SEd Maste 		if ((n = poll(&pfd, 1, 100)) == -1) {
1890afa8e06SEd Maste 			fido_log_error(errno, "%s: poll", __func__);
1900afa8e06SEd Maste 			return -1;
1910afa8e06SEd Maste 		} else if (n == 0) {
1920afa8e06SEd Maste 			fido_log_debug("%s: timed out", __func__);
1930afa8e06SEd Maste 			continue;
1940afa8e06SEd Maste 		}
1950afa8e06SEd Maste 		if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1)
1960afa8e06SEd Maste 			return -1;
1970afa8e06SEd Maste 		/*
1980afa8e06SEd Maste 		 * Ping isn't always supported on the broadcast channel,
1990afa8e06SEd Maste 		 * so we might get an error, but we don't care - we're
2000afa8e06SEd Maste 		 * synched now.
2010afa8e06SEd Maste 		 */
2020afa8e06SEd Maste 		fido_log_xxd(data, ctx->report_out_len, "%s: got reply",
2030afa8e06SEd Maste 		    __func__);
2040afa8e06SEd Maste 		return 0;
2050afa8e06SEd Maste 	}
2060afa8e06SEd Maste 	fido_log_debug("%s: no response", __func__);
2070afa8e06SEd Maste 	return -1;
2080afa8e06SEd Maste }
2090afa8e06SEd Maste 
2100afa8e06SEd Maste void *
fido_hid_open(const char * path)2110afa8e06SEd Maste fido_hid_open(const char *path)
2120afa8e06SEd Maste {
2130afa8e06SEd Maste 	struct hid_netbsd		*ctx;
2140afa8e06SEd Maste 	struct usb_ctl_report_desc	 ucrd;
2150afa8e06SEd Maste 	int				 r;
2160afa8e06SEd Maste 
2170afa8e06SEd Maste 	memset(&ucrd, 0, sizeof(ucrd));
2180afa8e06SEd Maste 
2190afa8e06SEd Maste 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
2200afa8e06SEd Maste 	    (ctx->fd = fido_hid_unix_open(path)) == -1) {
2210afa8e06SEd Maste 		free(ctx);
2220afa8e06SEd Maste 		return (NULL);
2230afa8e06SEd Maste 	}
2240afa8e06SEd Maste 
2250afa8e06SEd Maste 	if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd)) == -1 ||
2260afa8e06SEd Maste 	    ucrd.ucrd_size < 0 ||
2270afa8e06SEd Maste 	    (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||
2280afa8e06SEd Maste 	    fido_hid_get_report_len(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,
2290afa8e06SEd Maste 		&ctx->report_in_len, &ctx->report_out_len) < 0) {
2300afa8e06SEd Maste 		if (r == -1)
2310afa8e06SEd Maste 			fido_log_error(errno, "%s: ioctl", __func__);
2320afa8e06SEd Maste 		fido_log_debug("%s: using default report sizes", __func__);
2330afa8e06SEd Maste 		ctx->report_in_len = CTAP_MAX_REPORT_LEN;
2340afa8e06SEd Maste 		ctx->report_out_len = CTAP_MAX_REPORT_LEN;
2350afa8e06SEd Maste 	}
2360afa8e06SEd Maste 
2370afa8e06SEd Maste 	/*
2380afa8e06SEd Maste 	 * NetBSD has a bug that causes it to lose
2390afa8e06SEd Maste 	 * track of the DATA0/DATA1 sequence toggle across uhid device
2400afa8e06SEd Maste 	 * open and close. This is a terrible hack to work around it.
2410afa8e06SEd Maste 	 */
2420afa8e06SEd Maste 	if (!is_fido(ctx->fd) || terrible_ping_kludge(ctx) != 0) {
2430afa8e06SEd Maste 		fido_hid_close(ctx);
2440afa8e06SEd Maste 		return NULL;
2450afa8e06SEd Maste 	}
2460afa8e06SEd Maste 
2470afa8e06SEd Maste 	return (ctx);
2480afa8e06SEd Maste }
2490afa8e06SEd Maste 
2500afa8e06SEd Maste void
fido_hid_close(void * handle)2510afa8e06SEd Maste fido_hid_close(void *handle)
2520afa8e06SEd Maste {
2530afa8e06SEd Maste 	struct hid_netbsd *ctx = handle;
2540afa8e06SEd Maste 
2550afa8e06SEd Maste 	if (close(ctx->fd) == -1)
2560afa8e06SEd Maste 		fido_log_error(errno, "%s: close", __func__);
2570afa8e06SEd Maste 
2580afa8e06SEd Maste 	free(ctx);
2590afa8e06SEd Maste }
2600afa8e06SEd Maste 
2610afa8e06SEd Maste int
fido_hid_set_sigmask(void * handle,const fido_sigset_t * sigmask)2620afa8e06SEd Maste fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
2630afa8e06SEd Maste {
2640afa8e06SEd Maste 	struct hid_netbsd *ctx = handle;
2650afa8e06SEd Maste 
2660afa8e06SEd Maste 	ctx->sigmask = *sigmask;
2670afa8e06SEd Maste 	ctx->sigmaskp = &ctx->sigmask;
2680afa8e06SEd Maste 
2690afa8e06SEd Maste 	return (FIDO_OK);
2700afa8e06SEd Maste }
2710afa8e06SEd Maste 
2720afa8e06SEd Maste int
fido_hid_read(void * handle,unsigned char * buf,size_t len,int ms)2730afa8e06SEd Maste fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
2740afa8e06SEd Maste {
2750afa8e06SEd Maste 	struct hid_netbsd	*ctx = handle;
2760afa8e06SEd Maste 	ssize_t			 r;
2770afa8e06SEd Maste 
2780afa8e06SEd Maste 	if (len != ctx->report_in_len) {
2790afa8e06SEd Maste 		fido_log_debug("%s: len %zu", __func__, len);
2800afa8e06SEd Maste 		return (-1);
2810afa8e06SEd Maste 	}
2820afa8e06SEd Maste 
2830afa8e06SEd Maste 	if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
2840afa8e06SEd Maste 		fido_log_debug("%s: fd not ready", __func__);
2850afa8e06SEd Maste 		return (-1);
2860afa8e06SEd Maste 	}
2870afa8e06SEd Maste 
2880afa8e06SEd Maste 	if ((r = read(ctx->fd, buf, len)) == -1) {
2890afa8e06SEd Maste 		fido_log_error(errno, "%s: read", __func__);
2900afa8e06SEd Maste 		return (-1);
2910afa8e06SEd Maste 	}
2920afa8e06SEd Maste 
2930afa8e06SEd Maste 	if (r < 0 || (size_t)r != len) {
2940afa8e06SEd Maste 		fido_log_error(errno, "%s: %zd != %zu", __func__, r, len);
2950afa8e06SEd Maste 		return (-1);
2960afa8e06SEd Maste 	}
2970afa8e06SEd Maste 
2980afa8e06SEd Maste 	return ((int)r);
2990afa8e06SEd Maste }
3000afa8e06SEd Maste 
3010afa8e06SEd Maste int
fido_hid_write(void * handle,const unsigned char * buf,size_t len)3020afa8e06SEd Maste fido_hid_write(void *handle, const unsigned char *buf, size_t len)
3030afa8e06SEd Maste {
3040afa8e06SEd Maste 	struct hid_netbsd	*ctx = handle;
3050afa8e06SEd Maste 	ssize_t			 r;
3060afa8e06SEd Maste 
3070afa8e06SEd Maste 	if (len != ctx->report_out_len + 1) {
3080afa8e06SEd Maste 		fido_log_debug("%s: len %zu", __func__, len);
3090afa8e06SEd Maste 		return (-1);
3100afa8e06SEd Maste 	}
3110afa8e06SEd Maste 
3120afa8e06SEd Maste 	if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) {
3130afa8e06SEd Maste 		fido_log_error(errno, "%s: write", __func__);
3140afa8e06SEd Maste 		return (-1);
3150afa8e06SEd Maste 	}
3160afa8e06SEd Maste 
3170afa8e06SEd Maste 	if (r < 0 || (size_t)r != len - 1) {
3180afa8e06SEd Maste 		fido_log_error(errno, "%s: %zd != %zu", __func__, r, len - 1);
3190afa8e06SEd Maste 		return (-1);
3200afa8e06SEd Maste 	}
3210afa8e06SEd Maste 
3220afa8e06SEd Maste 	return ((int)len);
3230afa8e06SEd Maste }
3240afa8e06SEd Maste 
3250afa8e06SEd Maste size_t
fido_hid_report_in_len(void * handle)3260afa8e06SEd Maste fido_hid_report_in_len(void *handle)
3270afa8e06SEd Maste {
3280afa8e06SEd Maste 	struct hid_netbsd *ctx = handle;
3290afa8e06SEd Maste 
3300afa8e06SEd Maste 	return (ctx->report_in_len);
3310afa8e06SEd Maste }
3320afa8e06SEd Maste 
3330afa8e06SEd Maste size_t
fido_hid_report_out_len(void * handle)3340afa8e06SEd Maste fido_hid_report_out_len(void *handle)
3350afa8e06SEd Maste {
3360afa8e06SEd Maste 	struct hid_netbsd *ctx = handle;
3370afa8e06SEd Maste 
3380afa8e06SEd Maste 	return (ctx->report_out_len);
3390afa8e06SEd Maste }
340