xref: /freebsd/contrib/libfido2/src/hid_netbsd.c (revision 3332f1b444d4a73238e9f59cca27bfc95fe936bd)
1 /*
2  * Copyright (c) 2020 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 #include <sys/ioctl.h>
9 
10 #include <dev/usb/usb.h>
11 #include <dev/usb/usbhid.h>
12 
13 #include <errno.h>
14 #include <poll.h>
15 #include <signal.h>
16 #include <stdbool.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <unistd.h>
21 
22 #include "fido.h"
23 
24 #define MAX_UHID	64
25 
26 struct hid_netbsd {
27 	int             fd;
28 	size_t          report_in_len;
29 	size_t          report_out_len;
30 	sigset_t        sigmask;
31 	const sigset_t *sigmaskp;
32 };
33 
34 /* Hack to make this work with newer kernels even if /usr/include is old.  */
35 #if __NetBSD_Version__ < 901000000	/* 9.1 */
36 #define	USB_HID_GET_RAW	_IOR('h', 1, int)
37 #define	USB_HID_SET_RAW	_IOW('h', 2, int)
38 #endif
39 
40 static bool
41 is_fido(int fd)
42 {
43 	struct usb_ctl_report_desc	ucrd;
44 	uint32_t			usage_page = 0;
45 	int				raw = 1;
46 
47 	memset(&ucrd, 0, sizeof(ucrd));
48 
49 	if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd) == -1) {
50 		fido_log_error(errno, "%s: ioctl", __func__);
51 		return (false);
52 	}
53 
54 	if (ucrd.ucrd_size < 0 ||
55 	    (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||
56 	    fido_hid_get_usage(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,
57 		&usage_page) < 0) {
58 		fido_log_debug("%s: fido_hid_get_usage", __func__);
59 		return (false);
60 	}
61 
62 	if (usage_page != 0xf1d0)
63 		return (false);
64 
65 	/*
66 	 * This step is not strictly necessary -- NetBSD puts fido
67 	 * devices into raw mode automatically by default, but in
68 	 * principle that might change, and this serves as a test to
69 	 * verify that we're running on a kernel with support for raw
70 	 * mode at all so we don't get confused issuing writes that try
71 	 * to set the report descriptor rather than transfer data on
72 	 * the output interrupt pipe as we need.
73 	 */
74 	if (ioctl(fd, IOCTL_REQ(USB_HID_SET_RAW), &raw) == -1) {
75 		fido_log_error(errno, "%s: unable to set raw", __func__);
76 		return (false);
77 	}
78 
79 	return (true);
80 }
81 
82 static int
83 copy_info(fido_dev_info_t *di, const char *path)
84 {
85 	int			fd = -1;
86 	int			ok = -1;
87 	struct usb_device_info	udi;
88 
89 	memset(di, 0, sizeof(*di));
90 	memset(&udi, 0, sizeof(udi));
91 
92 	if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0)
93 		goto fail;
94 
95 	if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
96 		fido_log_error(errno, "%s: ioctl", __func__);
97 		goto fail;
98 	}
99 
100 	if ((di->path = strdup(path)) == NULL ||
101 	    (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
102 	    (di->product = strdup(udi.udi_product)) == NULL)
103 		goto fail;
104 
105 	di->vendor_id = (int16_t)udi.udi_vendorNo;
106 	di->product_id = (int16_t)udi.udi_productNo;
107 
108 	ok = 0;
109 fail:
110 	if (fd != -1 && close(fd) == -1)
111 		fido_log_error(errno, "%s: close", __func__);
112 
113 	if (ok < 0) {
114 		free(di->path);
115 		free(di->manufacturer);
116 		free(di->product);
117 		explicit_bzero(di, sizeof(*di));
118 	}
119 
120 	return (ok);
121 }
122 
123 int
124 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
125 {
126 	char	path[64];
127 	size_t	i;
128 
129 	*olen = 0;
130 
131 	if (ilen == 0)
132 		return (FIDO_OK); /* nothing to do */
133 
134 	if (devlist == NULL || olen == NULL)
135 		return (FIDO_ERR_INVALID_ARGUMENT);
136 
137 	for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) {
138 		snprintf(path, sizeof(path), "/dev/uhid%zu", i);
139 		if (copy_info(&devlist[*olen], path) == 0) {
140 			devlist[*olen].io = (fido_dev_io_t) {
141 				fido_hid_open,
142 				fido_hid_close,
143 				fido_hid_read,
144 				fido_hid_write,
145 			};
146 			++(*olen);
147 		}
148 	}
149 
150 	return (FIDO_OK);
151 }
152 
153 /*
154  * Workaround for NetBSD (as of 201910) bug that loses
155  * sync of DATA0/DATA1 sequence bit across uhid open/close.
156  * Send pings until we get a response - early pings with incorrect
157  * sequence bits will be ignored as duplicate packets by the device.
158  */
159 static int
160 terrible_ping_kludge(struct hid_netbsd *ctx)
161 {
162 	u_char data[256];
163 	int i, n;
164 	struct pollfd pfd;
165 
166 	if (sizeof(data) < ctx->report_out_len + 1)
167 		return -1;
168 	for (i = 0; i < 4; i++) {
169 		memset(data, 0, sizeof(data));
170 		/* broadcast channel ID */
171 		data[1] = 0xff;
172 		data[2] = 0xff;
173 		data[3] = 0xff;
174 		data[4] = 0xff;
175 		/* Ping command */
176 		data[5] = 0x81;
177 		/* One byte ping only, Vasili */
178 		data[6] = 0;
179 		data[7] = 1;
180 		fido_log_debug("%s: send ping %d", __func__, i);
181 		if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1)
182 			return -1;
183 		fido_log_debug("%s: wait reply", __func__);
184 		memset(&pfd, 0, sizeof(pfd));
185 		pfd.fd = ctx->fd;
186 		pfd.events = POLLIN;
187 		if ((n = poll(&pfd, 1, 100)) == -1) {
188 			fido_log_error(errno, "%s: poll", __func__);
189 			return -1;
190 		} else if (n == 0) {
191 			fido_log_debug("%s: timed out", __func__);
192 			continue;
193 		}
194 		if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1)
195 			return -1;
196 		/*
197 		 * Ping isn't always supported on the broadcast channel,
198 		 * so we might get an error, but we don't care - we're
199 		 * synched now.
200 		 */
201 		fido_log_xxd(data, ctx->report_out_len, "%s: got reply",
202 		    __func__);
203 		return 0;
204 	}
205 	fido_log_debug("%s: no response", __func__);
206 	return -1;
207 }
208 
209 void *
210 fido_hid_open(const char *path)
211 {
212 	struct hid_netbsd		*ctx;
213 	struct usb_ctl_report_desc	 ucrd;
214 	int				 r;
215 
216 	memset(&ucrd, 0, sizeof(ucrd));
217 
218 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
219 	    (ctx->fd = fido_hid_unix_open(path)) == -1) {
220 		free(ctx);
221 		return (NULL);
222 	}
223 
224 	if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd)) == -1 ||
225 	    ucrd.ucrd_size < 0 ||
226 	    (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||
227 	    fido_hid_get_report_len(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,
228 		&ctx->report_in_len, &ctx->report_out_len) < 0) {
229 		if (r == -1)
230 			fido_log_error(errno, "%s: ioctl", __func__);
231 		fido_log_debug("%s: using default report sizes", __func__);
232 		ctx->report_in_len = CTAP_MAX_REPORT_LEN;
233 		ctx->report_out_len = CTAP_MAX_REPORT_LEN;
234 	}
235 
236 	/*
237 	 * NetBSD has a bug that causes it to lose
238 	 * track of the DATA0/DATA1 sequence toggle across uhid device
239 	 * open and close. This is a terrible hack to work around it.
240 	 */
241 	if (!is_fido(ctx->fd) || terrible_ping_kludge(ctx) != 0) {
242 		fido_hid_close(ctx);
243 		return NULL;
244 	}
245 
246 	return (ctx);
247 }
248 
249 void
250 fido_hid_close(void *handle)
251 {
252 	struct hid_netbsd *ctx = handle;
253 
254 	if (close(ctx->fd) == -1)
255 		fido_log_error(errno, "%s: close", __func__);
256 
257 	free(ctx);
258 }
259 
260 int
261 fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
262 {
263 	struct hid_netbsd *ctx = handle;
264 
265 	ctx->sigmask = *sigmask;
266 	ctx->sigmaskp = &ctx->sigmask;
267 
268 	return (FIDO_OK);
269 }
270 
271 int
272 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
273 {
274 	struct hid_netbsd	*ctx = handle;
275 	ssize_t			 r;
276 
277 	if (len != ctx->report_in_len) {
278 		fido_log_debug("%s: len %zu", __func__, len);
279 		return (-1);
280 	}
281 
282 	if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
283 		fido_log_debug("%s: fd not ready", __func__);
284 		return (-1);
285 	}
286 
287 	if ((r = read(ctx->fd, buf, len)) == -1) {
288 		fido_log_error(errno, "%s: read", __func__);
289 		return (-1);
290 	}
291 
292 	if (r < 0 || (size_t)r != len) {
293 		fido_log_error(errno, "%s: %zd != %zu", __func__, r, len);
294 		return (-1);
295 	}
296 
297 	return ((int)r);
298 }
299 
300 int
301 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
302 {
303 	struct hid_netbsd	*ctx = handle;
304 	ssize_t			 r;
305 
306 	if (len != ctx->report_out_len + 1) {
307 		fido_log_debug("%s: len %zu", __func__, len);
308 		return (-1);
309 	}
310 
311 	if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) {
312 		fido_log_error(errno, "%s: write", __func__);
313 		return (-1);
314 	}
315 
316 	if (r < 0 || (size_t)r != len - 1) {
317 		fido_log_error(errno, "%s: %zd != %zu", __func__, r, len - 1);
318 		return (-1);
319 	}
320 
321 	return ((int)len);
322 }
323 
324 size_t
325 fido_hid_report_in_len(void *handle)
326 {
327 	struct hid_netbsd *ctx = handle;
328 
329 	return (ctx->report_in_len);
330 }
331 
332 size_t
333 fido_hid_report_out_len(void *handle)
334 {
335 	struct hid_netbsd *ctx = handle;
336 
337 	return (ctx->report_out_len);
338 }
339