]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libfido2/src/hid_openbsd.c
zfs: merge openzfs/zfs@a582d5299
[FreeBSD/FreeBSD.git] / contrib / libfido2 / src / hid_openbsd.c
1 /*
2  * Copyright (c) 2019 Google LLC. 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
9 #include <sys/ioctl.h>
10 #include <dev/usb/usb.h>
11
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <poll.h>
15 #include <signal.h>
16 #include <unistd.h>
17
18 #include "fido.h"
19
20 #define MAX_UHID        64
21
22 struct hid_openbsd {
23         int fd;
24         size_t report_in_len;
25         size_t report_out_len;
26 };
27
28 int
29 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
30 {
31         size_t i;
32         char path[64];
33         int fd;
34         struct usb_device_info udi;
35         fido_dev_info_t *di;
36
37         if (ilen == 0)
38                 return (FIDO_OK); /* nothing to do */
39
40         if (devlist == NULL || olen == NULL)
41                 return (FIDO_ERR_INVALID_ARGUMENT);
42
43         for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) {
44                 snprintf(path, sizeof(path), "/dev/fido/%zu", i);
45                 if ((fd = fido_hid_unix_open(path)) == -1)
46                         continue;
47                 memset(&udi, 0, sizeof(udi));
48                 if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
49                         fido_log_error(errno, "%s: get device info %s",
50                             __func__, path);
51                         if (close(fd) == -1)
52                                 fido_log_error(errno, "%s: close", __func__);
53                         continue;
54                 }
55                 if (close(fd) == -1)
56                         fido_log_error(errno, "%s: close", __func__);
57
58                 fido_log_debug("%s: %s: bus = 0x%02x, addr = 0x%02x",
59                     __func__, path, udi.udi_bus, udi.udi_addr);
60                 fido_log_debug("%s: %s: vendor = \"%s\", product = \"%s\"",
61                     __func__, path, udi.udi_vendor, udi.udi_product);
62                 fido_log_debug("%s: %s: productNo = 0x%04x, vendorNo = 0x%04x, "
63                     "releaseNo = 0x%04x", __func__, path, udi.udi_productNo,
64                     udi.udi_vendorNo, udi.udi_releaseNo);
65
66                 di = &devlist[*olen];
67                 memset(di, 0, sizeof(*di));
68                 di->io = (fido_dev_io_t) {
69                         fido_hid_open,
70                         fido_hid_close,
71                         fido_hid_read,
72                         fido_hid_write,
73                 };
74                 if ((di->path = strdup(path)) == NULL ||
75                     (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
76                     (di->product = strdup(udi.udi_product)) == NULL) {
77                         free(di->path);
78                         free(di->manufacturer);
79                         free(di->product);
80                         explicit_bzero(di, sizeof(*di));
81                         return FIDO_ERR_INTERNAL;
82                 }
83                 di->vendor_id = (int16_t)udi.udi_vendorNo;
84                 di->product_id = (int16_t)udi.udi_productNo;
85                 (*olen)++;
86         }
87
88         return FIDO_OK;
89 }
90
91 /*
92  * Workaround for OpenBSD <=6.6-current (as of 201910) bug that loses
93  * sync of DATA0/DATA1 sequence bit across uhid open/close.
94  * Send pings until we get a response - early pings with incorrect
95  * sequence bits will be ignored as duplicate packets by the device.
96  */
97 static int
98 terrible_ping_kludge(struct hid_openbsd *ctx)
99 {
100         u_char data[256];
101         int i, n;
102         struct pollfd pfd;
103
104         if (sizeof(data) < ctx->report_out_len + 1)
105                 return -1;
106         for (i = 0; i < 4; i++) {
107                 memset(data, 0, sizeof(data));
108                 /* broadcast channel ID */
109                 data[1] = 0xff;
110                 data[2] = 0xff;
111                 data[3] = 0xff;
112                 data[4] = 0xff;
113                 /* Ping command */
114                 data[5] = 0x81;
115                 /* One byte ping only, Vasili */
116                 data[6] = 0;
117                 data[7] = 1;
118                 fido_log_debug("%s: send ping %d", __func__, i);
119                 if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1)
120                         return -1;
121                 fido_log_debug("%s: wait reply", __func__);
122                 memset(&pfd, 0, sizeof(pfd));
123                 pfd.fd = ctx->fd;
124                 pfd.events = POLLIN;
125                 if ((n = poll(&pfd, 1, 100)) == -1) {
126                         fido_log_error(errno, "%s: poll", __func__);
127                         return -1;
128                 } else if (n == 0) {
129                         fido_log_debug("%s: timed out", __func__);
130                         continue;
131                 }
132                 if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1)
133                         return -1;
134                 /*
135                  * Ping isn't always supported on the broadcast channel,
136                  * so we might get an error, but we don't care - we're
137                  * synched now.
138                  */
139                 fido_log_xxd(data, ctx->report_out_len, "%s: got reply",
140                     __func__);
141                 return 0;
142         }
143         fido_log_debug("%s: no response", __func__);
144         return -1;
145 }
146
147 void *
148 fido_hid_open(const char *path)
149 {
150         struct hid_openbsd *ret = NULL;
151
152         if ((ret = calloc(1, sizeof(*ret))) == NULL ||
153             (ret->fd = fido_hid_unix_open(path)) == -1) {
154                 free(ret);
155                 return (NULL);
156         }
157         ret->report_in_len = ret->report_out_len = CTAP_MAX_REPORT_LEN;
158         fido_log_debug("%s: inlen = %zu outlen = %zu", __func__,
159             ret->report_in_len, ret->report_out_len);
160
161         /*
162          * OpenBSD (as of 201910) has a bug that causes it to lose
163          * track of the DATA0/DATA1 sequence toggle across uhid device
164          * open and close. This is a terrible hack to work around it.
165          */
166         if (terrible_ping_kludge(ret) != 0) {
167                 fido_hid_close(ret);
168                 return NULL;
169         }
170
171         return (ret);
172 }
173
174 void
175 fido_hid_close(void *handle)
176 {
177         struct hid_openbsd *ctx = (struct hid_openbsd *)handle;
178
179         if (close(ctx->fd) == -1)
180                 fido_log_error(errno, "%s: close", __func__);
181
182         free(ctx);
183 }
184
185 int
186 fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
187 {
188         (void)handle;
189         (void)sigmask;
190
191         return (FIDO_ERR_INTERNAL);
192 }
193
194 int
195 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
196 {
197         struct hid_openbsd *ctx = (struct hid_openbsd *)handle;
198         ssize_t r;
199
200         (void)ms; /* XXX */
201
202         if (len != ctx->report_in_len) {
203                 fido_log_debug("%s: invalid len: got %zu, want %zu", __func__,
204                     len, ctx->report_in_len);
205                 return (-1);
206         }
207
208         if ((r = read(ctx->fd, buf, len)) == -1) {
209                 fido_log_error(errno, "%s: read", __func__);
210                 return (-1);
211         }
212
213         if (r < 0 || (size_t)r != len) {
214                 fido_log_debug("%s: %zd != %zu", __func__, r, len);
215                 return (-1);
216         }
217
218         return ((int)len);
219 }
220
221 int
222 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
223 {
224         struct hid_openbsd *ctx = (struct hid_openbsd *)handle;
225         ssize_t r;
226
227         if (len != ctx->report_out_len + 1) {
228                 fido_log_debug("%s: invalid len: got %zu, want %zu", __func__,
229                     len, ctx->report_out_len);
230                 return (-1);
231         }
232
233         if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) {
234                 fido_log_error(errno, "%s: write", __func__);
235                 return (-1);
236         }
237
238         if (r < 0 || (size_t)r != len - 1) {
239                 fido_log_debug("%s: %zd != %zu", __func__, r, len - 1);
240                 return (-1);
241         }
242
243         return ((int)len);
244 }
245
246 size_t
247 fido_hid_report_in_len(void *handle)
248 {
249         struct hid_openbsd *ctx = handle;
250
251         return (ctx->report_in_len);
252 }
253
254 size_t
255 fido_hid_report_out_len(void *handle)
256 {
257         struct hid_openbsd *ctx = handle;
258
259         return (ctx->report_out_len);
260 }