xref: /freebsd/contrib/libfido2/src/hid_freebsd.c (revision 4731124cace5e7a0224e29784617d2856e5c59ab)
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 
9 #include <dev/usb/usb_ioctl.h>
10 #include <dev/usb/usbhid.h>
11 
12 #include <errno.h>
13 #include <unistd.h>
14 
15 #include "fido.h"
16 
17 #define MAX_UHID	64
18 
19 struct hid_freebsd {
20 	int             fd;
21 	size_t          report_in_len;
22 	size_t          report_out_len;
23 	sigset_t        sigmask;
24 	const sigset_t *sigmaskp;
25 };
26 
27 static bool
28 is_fido(int fd)
29 {
30 	char				buf[64];
31 	struct usb_gen_descriptor	ugd;
32 	uint32_t			usage_page = 0;
33 
34 	memset(&buf, 0, sizeof(buf));
35 	memset(&ugd, 0, sizeof(ugd));
36 
37 	ugd.ugd_report_type = UHID_FEATURE_REPORT;
38 	ugd.ugd_data = buf;
39 	ugd.ugd_maxlen = sizeof(buf);
40 
41 	if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ugd) == -1) {
42 		fido_log_error(errno, "%s: ioctl", __func__);
43 		return (false);
44 	}
45 	if (ugd.ugd_actlen > sizeof(buf) || fido_hid_get_usage(ugd.ugd_data,
46 	    ugd.ugd_actlen, &usage_page) < 0) {
47 		fido_log_debug("%s: fido_hid_get_usage", __func__);
48 		return (false);
49 	}
50 
51 	return (usage_page == 0xf1d0);
52 }
53 
54 static int
55 copy_info(fido_dev_info_t *di, const char *path)
56 {
57 	int			fd = -1;
58 	int			ok = -1;
59 	struct usb_device_info	udi;
60 
61 	memset(di, 0, sizeof(*di));
62 	memset(&udi, 0, sizeof(udi));
63 
64 	if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0)
65 		goto fail;
66 
67 	if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
68 		fido_log_error(errno, "%s: ioctl", __func__);
69 		strlcpy(udi.udi_vendor, "FreeBSD", sizeof(udi.udi_vendor));
70 		strlcpy(udi.udi_product, "uhid(4)", sizeof(udi.udi_product));
71 		udi.udi_vendorNo = 0x0b5d; /* stolen from PCI_VENDOR_OPENBSD */
72 	}
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 		goto fail;
78 
79 	di->vendor_id = (int16_t)udi.udi_vendorNo;
80 	di->product_id = (int16_t)udi.udi_productNo;
81 
82 	ok = 0;
83 fail:
84 	if (fd != -1)
85 		close(fd);
86 
87 	if (ok < 0) {
88 		free(di->path);
89 		free(di->manufacturer);
90 		free(di->product);
91 		explicit_bzero(di, sizeof(*di));
92 	}
93 
94 	return (ok);
95 }
96 
97 int
98 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
99 {
100 	char	path[64];
101 	size_t	i;
102 
103 	*olen = 0;
104 
105 	if (ilen == 0)
106 		return (FIDO_OK); /* nothing to do */
107 
108 	if (devlist == NULL || olen == NULL)
109 		return (FIDO_ERR_INVALID_ARGUMENT);
110 
111 	for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) {
112 		snprintf(path, sizeof(path), "/dev/uhid%zu", i);
113 		if (copy_info(&devlist[*olen], path) == 0) {
114 			devlist[*olen].io = (fido_dev_io_t) {
115 				fido_hid_open,
116 				fido_hid_close,
117 				fido_hid_read,
118 				fido_hid_write,
119 			};
120 			++(*olen);
121 		}
122 	}
123 
124 	return (FIDO_OK);
125 }
126 
127 void *
128 fido_hid_open(const char *path)
129 {
130 	char				 buf[64];
131 	struct hid_freebsd		*ctx;
132 	struct usb_gen_descriptor	 ugd;
133 	int				 r;
134 
135 	memset(&buf, 0, sizeof(buf));
136 	memset(&ugd, 0, sizeof(ugd));
137 
138 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
139 		return (NULL);
140 
141 	if ((ctx->fd = fido_hid_unix_open(path)) == -1) {
142 		free(ctx);
143 		return (NULL);
144 	}
145 
146 	ugd.ugd_report_type = UHID_FEATURE_REPORT;
147 	ugd.ugd_data = buf;
148 	ugd.ugd_maxlen = sizeof(buf);
149 
150 	if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ugd) == -1) ||
151 	    ugd.ugd_actlen > sizeof(buf) ||
152 	    fido_hid_get_report_len(ugd.ugd_data, ugd.ugd_actlen,
153 	    &ctx->report_in_len, &ctx->report_out_len) < 0) {
154 		if (r == -1)
155 			fido_log_error(errno, "%s: ioctl", __func__);
156 		fido_log_debug("%s: using default report sizes", __func__);
157 		ctx->report_in_len = CTAP_MAX_REPORT_LEN;
158 		ctx->report_out_len = CTAP_MAX_REPORT_LEN;
159 	}
160 
161 	return (ctx);
162 }
163 
164 void
165 fido_hid_close(void *handle)
166 {
167 	struct hid_freebsd *ctx = handle;
168 
169 	if (close(ctx->fd) == -1)
170 		fido_log_error(errno, "%s: close", __func__);
171 
172 	free(ctx);
173 }
174 
175 int
176 fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
177 {
178 	struct hid_freebsd *ctx = handle;
179 
180 	ctx->sigmask = *sigmask;
181 	ctx->sigmaskp = &ctx->sigmask;
182 
183 	return (FIDO_OK);
184 }
185 
186 int
187 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
188 {
189 	struct hid_freebsd	*ctx = handle;
190 	ssize_t			 r;
191 
192 	if (len != ctx->report_in_len) {
193 		fido_log_debug("%s: len %zu", __func__, len);
194 		return (-1);
195 	}
196 
197 	if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
198 		fido_log_debug("%s: fd not ready", __func__);
199 		return (-1);
200 	}
201 
202 	if ((r = read(ctx->fd, buf, len)) == -1) {
203 		fido_log_error(errno, "%s: read", __func__);
204 		return (-1);
205 	}
206 
207 	if (r < 0 || (size_t)r != len) {
208 		fido_log_debug("%s: %zd != %zu", __func__, r, len);
209 		return (-1);
210 	}
211 
212 	return ((int)r);
213 }
214 
215 int
216 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
217 {
218 	struct hid_freebsd	*ctx = handle;
219 	ssize_t			 r;
220 
221 	if (len != ctx->report_out_len + 1) {
222 		fido_log_debug("%s: len %zu", __func__, len);
223 		return (-1);
224 	}
225 
226 	if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) {
227 		fido_log_error(errno, "%s: write", __func__);
228 		return (-1);
229 	}
230 
231 	if (r < 0 || (size_t)r != len - 1) {
232 		fido_log_debug("%s: %zd != %zu", __func__, r, len - 1);
233 		return (-1);
234 	}
235 
236 	return ((int)len);
237 }
238 
239 size_t
240 fido_hid_report_in_len(void *handle)
241 {
242 	struct hid_freebsd *ctx = handle;
243 
244 	return (ctx->report_in_len);
245 }
246 
247 size_t
248 fido_hid_report_out_len(void *handle)
249 {
250 	struct hid_freebsd *ctx = handle;
251 
252 	return (ctx->report_out_len);
253 }
254