summaryrefslogtreecommitdiff
path: root/src/hid_linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/hid_linux.c')
-rw-r--r--src/hid_linux.c344
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
21static int
22get_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
38static int
39get_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
61static int
62get_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
98static int
99get_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;
125fail:
126 if (fd != -1)
127 close(fd);
128
129 return (ok);
130}
131
132static bool
133is_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
149static int
150parse_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
182static int
183copy_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;
228fail:
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
242int
243fido_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;
276fail:
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
285void *
286fido_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
299void
300fido_hid_close(void *handle)
301{
302 int *fd = handle;
303
304 close(*fd);
305 free(fd);
306}
307
308int
309fido_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
327int
328fido_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}