diff options
Diffstat (limited to 'src/hid_linux.c')
-rw-r--r-- | src/hid_linux.c | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/src/hid_linux.c b/src/hid_linux.c new file mode 100644 index 0000000..c7cabc9 --- /dev/null +++ b/src/hid_linux.c | |||
@@ -0,0 +1,344 @@ | |||
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 | |||
17 | #include "fido.h" | ||
18 | |||
19 | #define REPORT_LEN 65 | ||
20 | |||
21 | static int | ||
22 | get_key_len(uint8_t tag, uint8_t *key, size_t *key_len) | ||
23 | { | ||
24 | *key = tag & 0xfc; | ||
25 | if ((*key & 0xf0) == 0xf0) { | ||
26 | fido_log_debug("%s: *key=0x%02x", __func__, *key); | ||
27 | return (-1); | ||
28 | } | ||
29 | |||
30 | *key_len = tag & 0x3; | ||
31 | if (*key_len == 3) { | ||
32 | *key_len = 4; | ||
33 | } | ||
34 | |||
35 | return (0); | ||
36 | } | ||
37 | |||
38 | static int | ||
39 | get_key_val(const void *body, size_t key_len, uint32_t *val) | ||
40 | { | ||
41 | const uint8_t *ptr = body; | ||
42 | |||
43 | switch (key_len) { | ||
44 | case 0: | ||
45 | *val = 0; | ||
46 | break; | ||
47 | case 1: | ||
48 | *val = ptr[0]; | ||
49 | break; | ||
50 | case 2: | ||
51 | *val = (uint32_t)((ptr[1] << 8) | ptr[0]); | ||
52 | break; | ||
53 | default: | ||
54 | fido_log_debug("%s: key_len=%zu", __func__, key_len); | ||
55 | return (-1); | ||
56 | } | ||
57 | |||
58 | return (0); | ||
59 | } | ||
60 | |||
61 | static int | ||
62 | get_usage_info(const struct hidraw_report_descriptor *hrd, uint32_t *usage_page, | ||
63 | uint32_t *usage) | ||
64 | { | ||
65 | const uint8_t *ptr; | ||
66 | size_t len; | ||
67 | |||
68 | ptr = hrd->value; | ||
69 | len = hrd->size; | ||
70 | |||
71 | while (len > 0) { | ||
72 | const uint8_t tag = ptr[0]; | ||
73 | ptr++; | ||
74 | len--; | ||
75 | |||
76 | uint8_t key; | ||
77 | size_t key_len; | ||
78 | uint32_t key_val; | ||
79 | |||
80 | if (get_key_len(tag, &key, &key_len) < 0 || key_len > len || | ||
81 | get_key_val(ptr, key_len, &key_val) < 0) { | ||
82 | return (-1); | ||
83 | } | ||
84 | |||
85 | if (key == 0x4) { | ||
86 | *usage_page = key_val; | ||
87 | } else if (key == 0x8) { | ||
88 | *usage = key_val; | ||
89 | } | ||
90 | |||
91 | ptr += key_len; | ||
92 | len -= key_len; | ||
93 | } | ||
94 | |||
95 | return (0); | ||
96 | } | ||
97 | |||
98 | static int | ||
99 | get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd) | ||
100 | { | ||
101 | int r; | ||
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 ((r = 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 ((r = 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_dev_info_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 | if (++(*olen) == ilen) | ||
271 | break; | ||
272 | } | ||
273 | } | ||
274 | |||
275 | r = FIDO_OK; | ||
276 | fail: | ||
277 | if (udev_enum != NULL) | ||
278 | udev_enumerate_unref(udev_enum); | ||
279 | if (udev != NULL) | ||
280 | udev_unref(udev); | ||
281 | |||
282 | return (r); | ||
283 | } | ||
284 | |||
285 | void * | ||
286 | fido_hid_open(const char *path) | ||
287 | { | ||
288 | int *fd; | ||
289 | |||
290 | if ((fd = malloc(sizeof(*fd))) == NULL || | ||
291 | (*fd = open(path, O_RDWR)) < 0) { | ||
292 | free(fd); | ||
293 | return (NULL); | ||
294 | } | ||
295 | |||
296 | return (fd); | ||
297 | } | ||
298 | |||
299 | void | ||
300 | fido_hid_close(void *handle) | ||
301 | { | ||
302 | int *fd = handle; | ||
303 | |||
304 | close(*fd); | ||
305 | free(fd); | ||
306 | } | ||
307 | |||
308 | int | ||
309 | fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) | ||
310 | { | ||
311 | int *fd = handle; | ||
312 | ssize_t r; | ||
313 | |||
314 | (void)ms; /* XXX */ | ||
315 | |||
316 | if (len != REPORT_LEN - 1) { | ||
317 | fido_log_debug("%s: invalid len", __func__); | ||
318 | return (-1); | ||
319 | } | ||
320 | |||
321 | if ((r = read(*fd, buf, len)) < 0 || r != REPORT_LEN - 1) | ||
322 | return (-1); | ||
323 | |||
324 | return (REPORT_LEN - 1); | ||
325 | } | ||
326 | |||
327 | int | ||
328 | fido_hid_write(void *handle, const unsigned char *buf, size_t len) | ||
329 | { | ||
330 | int *fd = handle; | ||
331 | ssize_t r; | ||
332 | |||
333 | if (len != REPORT_LEN) { | ||
334 | fido_log_debug("%s: invalid len", __func__); | ||
335 | return (-1); | ||
336 | } | ||
337 | |||
338 | if ((r = write(*fd, buf, len)) < 0 || r != REPORT_LEN) { | ||
339 | fido_log_debug("%s: write", __func__); | ||
340 | return (-1); | ||
341 | } | ||
342 | |||
343 | return (REPORT_LEN); | ||
344 | } | ||