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