xref: /netbsd-src/external/bsd/libfido2/dist/src/hid_linux.c (revision 154bfe8e089c1a0a4e9ed8414f08d3da90949162)
1 /*
2  * Copyright (c) 2019 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 <sys/ioctl.h>
10 #include <linux/hidraw.h>
11 
12 #include <fcntl.h>
13 #include <libudev.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <errno.h>
17 
18 #include "fido.h"
19 
20 #define REPORT_LEN	65
21 
22 static int
23 get_key_len(uint8_t tag, uint8_t *key, size_t *key_len)
24 {
25 	*key = tag & 0xfc;
26 	if ((*key & 0xf0) == 0xf0) {
27 		fido_log_debug("%s: *key=0x%02x", __func__, *key);
28 		return (-1);
29 	}
30 
31 	*key_len = tag & 0x3;
32 	if (*key_len == 3) {
33 		*key_len = 4;
34 	}
35 
36 	return (0);
37 }
38 
39 static int
40 get_key_val(const void *body, size_t key_len, uint32_t *val)
41 {
42 	const uint8_t *ptr = body;
43 
44 	switch (key_len) {
45 	case 0:
46 		*val = 0;
47 		break;
48 	case 1:
49 		*val = ptr[0];
50 		break;
51 	case 2:
52 		*val = (uint32_t)((ptr[1] << 8) | ptr[0]);
53 		break;
54 	default:
55 		fido_log_debug("%s: key_len=%zu", __func__, key_len);
56 		return (-1);
57 	}
58 
59 	return (0);
60 }
61 
62 static int
63 get_usage_info(const struct hidraw_report_descriptor *hrd, uint32_t *usage_page,
64     uint32_t *usage)
65 {
66 	const uint8_t	*ptr;
67 	size_t		 len;
68 
69 	ptr = hrd->value;
70 	len = hrd->size;
71 
72 	while (len > 0) {
73 		const uint8_t tag = ptr[0];
74 		ptr++;
75 		len--;
76 
77 		uint8_t  key;
78 		size_t   key_len;
79 		uint32_t key_val;
80 
81 		if (get_key_len(tag, &key, &key_len) < 0 || key_len > len ||
82 		    get_key_val(ptr, key_len, &key_val) < 0) {
83 			return (-1);
84 		}
85 
86 		if (key == 0x4) {
87 			*usage_page = key_val;
88 		} else if (key == 0x8) {
89 			*usage = key_val;
90 		}
91 
92 		ptr += key_len;
93 		len -= key_len;
94 	}
95 
96 	return (0);
97 }
98 
99 static int
100 get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd)
101 {
102 	int	s = -1;
103 	int	fd;
104 	int	ok = -1;
105 
106 	if ((fd = open(path, O_RDONLY)) < 0) {
107 		fido_log_debug("%s: open", __func__);
108 		return (-1);
109 	}
110 
111 	if (ioctl(fd, HIDIOCGRDESCSIZE, &s) < 0 || s < 0 ||
112 	    (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
113 		fido_log_debug("%s: ioctl HIDIOCGRDESCSIZE", __func__);
114 		goto fail;
115 	}
116 
117 	hrd->size = s;
118 
119 	if (ioctl(fd, HIDIOCGRDESC, hrd) < 0) {
120 		fido_log_debug("%s: ioctl HIDIOCGRDESC", __func__);
121 		goto fail;
122 	}
123 
124 	ok = 0;
125 fail:
126 	if (fd != -1)
127 		close(fd);
128 
129 	return (ok);
130 }
131 
132 static bool
133 is_fido(const char *path)
134 {
135 	uint32_t			usage = 0;
136 	uint32_t			usage_page = 0;
137 	struct hidraw_report_descriptor	hrd;
138 
139 	memset(&hrd, 0, sizeof(hrd));
140 
141 	if (get_report_descriptor(path, &hrd) < 0 ||
142 	    get_usage_info(&hrd, &usage_page, &usage) < 0) {
143 		return (false);
144 	}
145 
146 	return (usage_page == 0xf1d0);
147 }
148 
149 static int
150 parse_uevent(struct udev_device *dev, int16_t *vendor_id, int16_t *product_id)
151 {
152 	const char		*uevent;
153 	char			*cp;
154 	char			*p;
155 	char			*s;
156 	int			 ok = -1;
157 	short unsigned int	 x;
158 	short unsigned int	 y;
159 
160 	if ((uevent = udev_device_get_sysattr_value(dev, "uevent")) == NULL)
161 		return (-1);
162 
163 	if ((s = cp = strdup(uevent)) == NULL)
164 		return (-1);
165 
166 	for ((p = strsep(&cp, "\n")); p && *p != '\0'; (p = strsep(&cp, "\n"))) {
167 		if (strncmp(p, "HID_ID=", 7) == 0) {
168 			if (sscanf(p + 7, "%*x:%hx:%hx", &x, &y) == 2) {
169 				*vendor_id = (int16_t)x;
170 				*product_id = (int16_t)y;
171 				ok = 0;
172 			}
173 			break;
174 		}
175 	}
176 
177 	free(s);
178 
179 	return (ok);
180 }
181 
182 static int
183 copy_info(fido_dev_info_t *di, struct udev *udev,
184     struct udev_list_entry *udev_entry)
185 {
186 	const char		*name;
187 	const char		*path;
188 	const char		*manufacturer;
189 	const char		*product;
190 	struct udev_device	*dev = NULL;
191 	struct udev_device	*hid_parent;
192 	struct udev_device	*usb_parent;
193 	int			 ok = -1;
194 
195 	memset(di, 0, sizeof(*di));
196 
197 	if ((name = udev_list_entry_get_name(udev_entry)) == NULL ||
198 	    (dev = udev_device_new_from_syspath(udev, name)) == NULL ||
199 	    (path = udev_device_get_devnode(dev)) == NULL ||
200 	    is_fido(path) == 0)
201 		goto fail;
202 
203 	if ((hid_parent = udev_device_get_parent_with_subsystem_devtype(dev,
204 	    "hid", NULL)) == NULL)
205 		goto fail;
206 
207 	if ((usb_parent = udev_device_get_parent_with_subsystem_devtype(dev,
208 	    "usb", "usb_device")) == NULL)
209 		goto fail;
210 
211 	if (parse_uevent(hid_parent, &di->vendor_id, &di->product_id) < 0 ||
212 	    (manufacturer = udev_device_get_sysattr_value(usb_parent,
213 	    "manufacturer")) == NULL ||
214 	    (product = udev_device_get_sysattr_value(usb_parent,
215 	    "product")) == NULL)
216 		goto fail;
217 
218 	di->path = strdup(path);
219 	di->manufacturer = strdup(manufacturer);
220 	di->product = strdup(product);
221 
222 	if (di->path == NULL ||
223 	    di->manufacturer == NULL ||
224 	    di->product == NULL)
225 		goto fail;
226 
227 	ok = 0;
228 fail:
229 	if (dev != NULL)
230 		udev_device_unref(dev);
231 
232 	if (ok < 0) {
233 		free(di->path);
234 		free(di->manufacturer);
235 		free(di->product);
236 		explicit_bzero(di, sizeof(*di));
237 	}
238 
239 	return (ok);
240 }
241 
242 int
243 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
244 {
245 	struct udev		*udev = NULL;
246 	struct udev_enumerate	*udev_enum = NULL;
247 	struct udev_list_entry	*udev_list;
248 	struct udev_list_entry	*udev_entry;
249 	int			 r = FIDO_ERR_INTERNAL;
250 
251 	*olen = 0;
252 
253 	if (ilen == 0)
254 		return (FIDO_OK); /* nothing to do */
255 
256 	if (devlist == NULL)
257 		return (FIDO_ERR_INVALID_ARGUMENT);
258 
259 	if ((udev = udev_new()) == NULL ||
260 	    (udev_enum = udev_enumerate_new(udev)) == NULL)
261 		goto fail;
262 
263 	if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 ||
264 	    udev_enumerate_scan_devices(udev_enum) < 0 ||
265 	    (udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL)
266 		goto fail;
267 
268 	udev_list_entry_foreach(udev_entry, udev_list) {
269 		if (copy_info(&devlist[*olen], udev, udev_entry) == 0) {
270 			devlist[*olen].io = (fido_dev_io_t) {
271 				fido_hid_open,
272 				fido_hid_close,
273 				fido_hid_read,
274 				fido_hid_write,
275 			};
276 			if (++(*olen) == ilen)
277 				break;
278 		}
279 	}
280 
281 	r = FIDO_OK;
282 fail:
283 	if (udev_enum != NULL)
284 		udev_enumerate_unref(udev_enum);
285 	if (udev != NULL)
286 		udev_unref(udev);
287 
288 	return (r);
289 }
290 
291 void *
292 fido_hid_open(const char *path)
293 {
294 	int *fd;
295 
296 	if ((fd = malloc(sizeof(*fd))) == NULL ||
297 	    (*fd = open(path, O_RDWR)) < 0) {
298 		free(fd);
299 		return (NULL);
300 	}
301 
302 	return (fd);
303 }
304 
305 void
306 fido_hid_close(void *handle)
307 {
308 	int *fd = handle;
309 
310 	close(*fd);
311 	free(fd);
312 }
313 
314 int
315 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
316 {
317 	int	*fd = handle;
318 	ssize_t	 r;
319 
320 	(void)ms; /* XXX */
321 
322 	if (len != REPORT_LEN - 1) {
323 		fido_log_debug("%s: invalid len", __func__);
324 		return (-1);
325 	}
326 
327 	if ((r = read(*fd, buf, len)) < 0 || r != REPORT_LEN - 1)
328 		return (-1);
329 
330 	return (REPORT_LEN - 1);
331 }
332 
333 int
334 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
335 {
336 	int	*fd = handle;
337 	ssize_t	 r;
338 
339 	if (len != REPORT_LEN) {
340 		fido_log_debug("%s: invalid len", __func__);
341 		return (-1);
342 	}
343 
344 	if ((r = write(*fd, buf, len)) < 0 || r != REPORT_LEN) {
345 		fido_log_debug("%s: write", __func__);
346 		return (-1);
347 	}
348 
349 	return (REPORT_LEN);
350 }
351