]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libfido2/src/hid_netbsd.c
MFV f83ac37f1e66: libbsdxml (expat) 2.4.3.
[FreeBSD/FreeBSD.git] / contrib / libfido2 / src / hid_netbsd.c
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 }