xref: /freebsd/contrib/libfido2/src/hid_freebsd.c (revision 61e21613)
1 /*
2  * Copyright (c) 2020-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/param.h>
9 
10 #include <dev/usb/usb_ioctl.h>
11 #include <dev/usb/usbhid.h>
12 #if __FreeBSD_version >= 1300500
13 #include <dev/hid/hidraw.h>
14 #define USE_HIDRAW /* see usbhid(4) and hidraw(4) on FreeBSD 13+ */
15 #endif
16 
17 #include <errno.h>
18 #include <unistd.h>
19 
20 #include "fido.h"
21 
22 #if defined(__MidnightBSD__)
23 #define UHID_VENDOR    "MidnightBSD"
24 #else
25 #define UHID_VENDOR    "FreeBSD"
26 #endif
27 
28 #define MAX_UHID	64
29 
30 struct hid_freebsd {
31 	int             fd;
32 	size_t          report_in_len;
33 	size_t          report_out_len;
34 	sigset_t        sigmask;
35 	const sigset_t *sigmaskp;
36 };
37 
38 static bool
39 is_fido(int fd)
40 {
41 	char				buf[64];
42 	struct usb_gen_descriptor	ugd;
43 	uint32_t			usage_page = 0;
44 
45 	memset(&buf, 0, sizeof(buf));
46 	memset(&ugd, 0, sizeof(ugd));
47 
48 	ugd.ugd_report_type = UHID_FEATURE_REPORT;
49 	ugd.ugd_data = buf;
50 	ugd.ugd_maxlen = sizeof(buf);
51 
52 	if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ugd) == -1) {
53 		fido_log_error(errno, "%s: ioctl", __func__);
54 		return (false);
55 	}
56 	if (ugd.ugd_actlen > sizeof(buf) || fido_hid_get_usage(ugd.ugd_data,
57 	    ugd.ugd_actlen, &usage_page) < 0) {
58 		fido_log_debug("%s: fido_hid_get_usage", __func__);
59 		return (false);
60 	}
61 
62 	return (usage_page == 0xf1d0);
63 }
64 
65 #ifdef USE_HIDRAW
66 static int
67 copy_info_hidraw(fido_dev_info_t *di, const char *path)
68 {
69 	int			fd = -1;
70 	int			ok = -1;
71 	struct usb_device_info	udi;
72 	struct hidraw_devinfo	devinfo;
73 	char			rawname[129];
74 
75 	memset(di, 0, sizeof(*di));
76 	memset(&udi, 0, sizeof(udi));
77 	memset(&devinfo, 0, sizeof(devinfo));
78 	memset(rawname, 0, sizeof(rawname));
79 
80 	if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0)
81 		goto fail;
82 
83 	if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
84 		if (ioctl(fd, IOCTL_REQ(HIDIOCGRAWINFO), &devinfo) == -1 ||
85 		    ioctl(fd, IOCTL_REQ(HIDIOCGRAWNAME(128)), rawname) == -1 ||
86 		    (di->path = strdup(path)) == NULL ||
87 		    (di->manufacturer = strdup(UHID_VENDOR)) == NULL ||
88 		    (di->product = strdup(rawname)) == NULL)
89 			goto fail;
90 		di->vendor_id = devinfo.vendor;
91 		di->product_id = devinfo.product;
92 	} else {
93 		if ((di->path = strdup(path)) == NULL ||
94 		    (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
95 		    (di->product = strdup(udi.udi_product)) == NULL)
96 			goto fail;
97 		di->vendor_id = (int16_t)udi.udi_vendorNo;
98 		di->product_id = (int16_t)udi.udi_productNo;
99 	}
100 
101 	ok = 0;
102 fail:
103 	if (fd != -1 && close(fd) == -1)
104 		fido_log_error(errno, "%s: close %s", __func__, path);
105 
106 	if (ok < 0) {
107 		free(di->path);
108 		free(di->manufacturer);
109 		free(di->product);
110 		explicit_bzero(di, sizeof(*di));
111 	}
112 
113 	return (ok);
114 }
115 #endif /* USE_HIDRAW */
116 
117 static int
118 copy_info_uhid(fido_dev_info_t *di, const char *path)
119 {
120 	int			fd = -1;
121 	int			ok = -1;
122 	struct usb_device_info	udi;
123 
124 	memset(di, 0, sizeof(*di));
125 	memset(&udi, 0, sizeof(udi));
126 
127 	if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0)
128 		goto fail;
129 
130 	if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
131 		fido_log_error(errno, "%s: ioctl", __func__);
132 		strlcpy(udi.udi_vendor, UHID_VENDOR, sizeof(udi.udi_vendor));
133 		strlcpy(udi.udi_product, "uhid(4)", sizeof(udi.udi_product));
134 		udi.udi_vendorNo = 0x0b5d; /* stolen from PCI_VENDOR_OPENBSD */
135 	}
136 
137 	if ((di->path = strdup(path)) == NULL ||
138 	    (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
139 	    (di->product = strdup(udi.udi_product)) == NULL)
140 		goto fail;
141 	di->vendor_id = (int16_t)udi.udi_vendorNo;
142 	di->product_id = (int16_t)udi.udi_productNo;
143 
144 	ok = 0;
145 fail:
146 	if (fd != -1 && close(fd) == -1)
147 		fido_log_error(errno, "%s: close %s", __func__, path);
148 
149 	if (ok < 0) {
150 		free(di->path);
151 		free(di->manufacturer);
152 		free(di->product);
153 		explicit_bzero(di, sizeof(*di));
154 	}
155 
156 	return (ok);
157 }
158 
159 int
160 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
161 {
162 	char	path[64];
163 	size_t	i;
164 
165 	if (ilen == 0)
166 		return (FIDO_OK); /* nothing to do */
167 
168 	if (devlist == NULL || olen == NULL)
169 		return (FIDO_ERR_INVALID_ARGUMENT);
170 
171 	*olen = 0;
172 
173 #ifdef USE_HIDRAW
174 	for (i = 0; i < MAX_UHID && *olen < ilen; i++) {
175 		snprintf(path, sizeof(path), "/dev/hidraw%zu", i);
176 		if (copy_info_hidraw(&devlist[*olen], path) == 0) {
177 			devlist[*olen].io = (fido_dev_io_t) {
178 				fido_hid_open,
179 				fido_hid_close,
180 				fido_hid_read,
181 				fido_hid_write,
182 			};
183 			++(*olen);
184 		}
185 	}
186 	/* hidraw(4) is preferred over uhid(4) */
187 	if (*olen != 0)
188 		return (FIDO_OK);
189 #endif /* USE_HIDRAW */
190 
191 	for (i = 0; i < MAX_UHID && *olen < ilen; i++) {
192 		snprintf(path, sizeof(path), "/dev/uhid%zu", i);
193 		if (copy_info_uhid(&devlist[*olen], path) == 0) {
194 			devlist[*olen].io = (fido_dev_io_t) {
195 				fido_hid_open,
196 				fido_hid_close,
197 				fido_hid_read,
198 				fido_hid_write,
199 			};
200 			++(*olen);
201 		}
202 	}
203 
204 	return (FIDO_OK);
205 }
206 
207 void *
208 fido_hid_open(const char *path)
209 {
210 	char				 buf[64];
211 	struct hid_freebsd		*ctx;
212 	struct usb_gen_descriptor	 ugd;
213 	int				 r;
214 
215 	memset(&buf, 0, sizeof(buf));
216 	memset(&ugd, 0, sizeof(ugd));
217 
218 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
219 		return (NULL);
220 
221 	if ((ctx->fd = fido_hid_unix_open(path)) == -1) {
222 		free(ctx);
223 		return (NULL);
224 	}
225 
226 	ugd.ugd_report_type = UHID_FEATURE_REPORT;
227 	ugd.ugd_data = buf;
228 	ugd.ugd_maxlen = sizeof(buf);
229 
230 	/*
231 	 * N.B. if ctx->fd is an hidraw(4) device, the ioctl() below puts it in
232 	 * uhid(4) compat mode, which we need to keep fido_hid_write() as-is.
233 	 */
234 	if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ugd) == -1) ||
235 	    ugd.ugd_actlen > sizeof(buf) ||
236 	    fido_hid_get_report_len(ugd.ugd_data, ugd.ugd_actlen,
237 	    &ctx->report_in_len, &ctx->report_out_len) < 0) {
238 		if (r == -1)
239 			fido_log_error(errno, "%s: ioctl", __func__);
240 		fido_log_debug("%s: using default report sizes", __func__);
241 		ctx->report_in_len = CTAP_MAX_REPORT_LEN;
242 		ctx->report_out_len = CTAP_MAX_REPORT_LEN;
243 	}
244 
245 	return (ctx);
246 }
247 
248 void
249 fido_hid_close(void *handle)
250 {
251 	struct hid_freebsd *ctx = handle;
252 
253 	if (close(ctx->fd) == -1)
254 		fido_log_error(errno, "%s: close", __func__);
255 
256 	free(ctx);
257 }
258 
259 int
260 fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
261 {
262 	struct hid_freebsd *ctx = handle;
263 
264 	ctx->sigmask = *sigmask;
265 	ctx->sigmaskp = &ctx->sigmask;
266 
267 	return (FIDO_OK);
268 }
269 
270 int
271 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
272 {
273 	struct hid_freebsd	*ctx = handle;
274 	ssize_t			 r;
275 
276 	if (len != ctx->report_in_len) {
277 		fido_log_debug("%s: len %zu", __func__, len);
278 		return (-1);
279 	}
280 
281 	if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
282 		fido_log_debug("%s: fd not ready", __func__);
283 		return (-1);
284 	}
285 
286 	if ((r = read(ctx->fd, buf, len)) == -1) {
287 		fido_log_error(errno, "%s: read", __func__);
288 		return (-1);
289 	}
290 
291 	if (r < 0 || (size_t)r != len) {
292 		fido_log_debug("%s: %zd != %zu", __func__, r, len);
293 		return (-1);
294 	}
295 
296 	return ((int)r);
297 }
298 
299 int
300 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
301 {
302 	struct hid_freebsd	*ctx = handle;
303 	ssize_t			 r;
304 
305 	if (len != ctx->report_out_len + 1) {
306 		fido_log_debug("%s: len %zu", __func__, len);
307 		return (-1);
308 	}
309 
310 	if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) {
311 		fido_log_error(errno, "%s: write", __func__);
312 		return (-1);
313 	}
314 
315 	if (r < 0 || (size_t)r != len - 1) {
316 		fido_log_debug("%s: %zd != %zu", __func__, r, len - 1);
317 		return (-1);
318 	}
319 
320 	return ((int)len);
321 }
322 
323 size_t
324 fido_hid_report_in_len(void *handle)
325 {
326 	struct hid_freebsd *ctx = handle;
327 
328 	return (ctx->report_in_len);
329 }
330 
331 size_t
332 fido_hid_report_out_len(void *handle)
333 {
334 	struct hid_freebsd *ctx = handle;
335 
336 	return (ctx->report_out_len);
337 }
338