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
is_fido(int fd)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
copy_info_hidraw(fido_dev_info_t * di,const char * path)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
copy_info_uhid(fido_dev_info_t * di,const char * path)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
fido_hid_manifest(fido_dev_info_t * devlist,size_t ilen,size_t * olen)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 *
fido_hid_open(const char * path)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
fido_hid_close(void * handle)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
fido_hid_set_sigmask(void * handle,const fido_sigset_t * sigmask)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
fido_hid_read(void * handle,unsigned char * buf,size_t len,int ms)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
fido_hid_write(void * handle,const unsigned char * buf,size_t len)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
fido_hid_report_in_len(void * handle)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
fido_hid_report_out_len(void * handle)332 fido_hid_report_out_len(void *handle)
333 {
334 struct hid_freebsd *ctx = handle;
335
336 return (ctx->report_out_len);
337 }
338