xref: /netbsd-src/external/bsd/libfido2/dist/src/hid_hidapi.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
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 #ifdef __linux__
8 #include <sys/ioctl.h>
9 #include <linux/hidraw.h>
10 #include <linux/input.h>
11 #include <fcntl.h>
12 #endif
13 
14 #include <hidapi.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <wchar.h>
18 
19 #include "fido.h"
20 
21 struct hid_hidapi {
22 	void *handle;
23 	size_t report_in_len;
24 	size_t report_out_len;
25 };
26 
27 static size_t
28 fido_wcslen(const wchar_t *wcs)
29 {
30 	size_t l = 0;
31 	while (*wcs++ != L'\0')
32 		l++;
33 	return l;
34 }
35 
36 static char *
37 wcs_to_cs(const wchar_t *wcs)
38 {
39 	char *cs;
40 	size_t i;
41 
42 	if (wcs == NULL || (cs = calloc(fido_wcslen(wcs) + 1, 1)) == NULL)
43 		return NULL;
44 
45 	for (i = 0; i < fido_wcslen(wcs); i++) {
46 		if (wcs[i] >= 128) {
47 			/* give up on parsing non-ASCII text */
48 			free(cs);
49 			return strdup("hidapi device");
50 		}
51 		cs[i] = (char)wcs[i];
52 	}
53 
54 	return cs;
55 }
56 
57 static int
58 copy_info(fido_dev_info_t *di, const struct hid_device_info *d)
59 {
60 	memset(di, 0, sizeof(*di));
61 
62 	if (d->path != NULL)
63 		di->path = strdup(d->path);
64 	else
65 		di->path = strdup("");
66 
67 	if (d->manufacturer_string != NULL)
68 		di->manufacturer = wcs_to_cs(d->manufacturer_string);
69 	else
70 		di->manufacturer = strdup("");
71 
72 	if (d->product_string != NULL)
73 		di->product = wcs_to_cs(d->product_string);
74 	else
75 		di->product = strdup("");
76 
77 	if (di->path == NULL ||
78 	    di->manufacturer == NULL ||
79 	    di->product == NULL) {
80 		free(di->path);
81 		free(di->manufacturer);
82 		free(di->product);
83 		explicit_bzero(di, sizeof(*di));
84 		return -1;
85 	}
86 
87 	di->product_id = (int16_t)d->product_id;
88 	di->vendor_id = (int16_t)d->vendor_id;
89 	di->io = (fido_dev_io_t) {
90 		&fido_hid_open,
91 		&fido_hid_close,
92 		&fido_hid_read,
93 		&fido_hid_write,
94 	};
95 
96 	return 0;
97 }
98 
99 #ifdef __linux__
100 static int
101 get_key_len(uint8_t tag, uint8_t *key, size_t *key_len)
102 {
103 	*key = tag & 0xfc;
104 	if ((*key & 0xf0) == 0xf0) {
105 		fido_log_debug("%s: *key=0x%02x", __func__, *key);
106 		return -1;
107 	}
108 
109 	*key_len = tag & 0x3;
110 	if (*key_len == 3) {
111 		*key_len = 4;
112 	}
113 
114 	return 0;
115 }
116 
117 static int
118 get_key_val(const void *body, size_t key_len, uint32_t *val)
119 {
120 	const uint8_t *ptr = body;
121 
122 	switch (key_len) {
123 	case 0:
124 		*val = 0;
125 		break;
126 	case 1:
127 		*val = ptr[0];
128 		break;
129 	case 2:
130 		*val = (uint32_t)((ptr[1] << 8) | ptr[0]);
131 		break;
132 	default:
133 		fido_log_debug("%s: key_len=%zu", __func__, key_len);
134 		return -1;
135 	}
136 
137 	return 0;
138 }
139 
140 static int
141 get_usage_info(const struct hidraw_report_descriptor *hrd, uint32_t *usage_page,
142     uint32_t *usage)
143 {
144 	const uint8_t *ptr = hrd->value;
145 	size_t len = hrd->size;
146 
147 	while (len > 0) {
148 		const uint8_t tag = ptr[0];
149 
150 		ptr++;
151 		len--;
152 
153 		uint8_t  key;
154 		size_t   key_len;
155 		uint32_t key_val;
156 
157 		if (get_key_len(tag, &key, &key_len) < 0 || key_len > len ||
158 		    get_key_val(ptr, key_len, &key_val) < 0) {
159 			return -1;
160 		}
161 
162 		if (key == 0x4) {
163 			*usage_page = key_val;
164 		} else if (key == 0x8) {
165 			*usage = key_val;
166 		}
167 
168 		ptr += key_len;
169 		len -= key_len;
170 	}
171 
172 	return 0;
173 }
174 
175 static int
176 get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd)
177 {
178 	int fd;
179 	int s = -1;
180 	int ok = -1;
181 
182 	if ((fd = open(path, O_RDONLY)) < 0) {
183 		fido_log_debug("%s: open", __func__);
184 		return -1;
185 	}
186 
187 	if (ioctl(fd, HIDIOCGRDESCSIZE, &s) < 0 || s < 0 ||
188 	    (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
189 		fido_log_debug("%s: ioctl HIDIOCGRDESCSIZE", __func__);
190 		goto fail;
191 	}
192 
193 	hrd->size = (unsigned)s;
194 
195 	if (ioctl(fd, HIDIOCGRDESC, hrd) < 0) {
196 		fido_log_debug("%s: ioctl HIDIOCGRDESC", __func__);
197 		goto fail;
198 	}
199 
200 	ok = 0;
201 fail:
202 	if (fd != -1)
203 		close(fd);
204 
205 	return ok;
206 }
207 
208 static bool
209 is_fido(const struct hid_device_info *hdi)
210 {
211 	uint32_t usage = 0;
212 	uint32_t usage_page = 0;
213 	struct hidraw_report_descriptor hrd;
214 
215 	memset(&hrd, 0, sizeof(hrd));
216 
217 	if (get_report_descriptor(hdi->path, &hrd) < 0 ||
218 	    get_usage_info(&hrd, &usage_page, &usage) < 0) {
219 		return false;
220 	}
221 
222 	return usage_page == 0xf1d0;
223 }
224 #elif defined(_WIN32) || defined(__APPLE__)
225 static bool
226 is_fido(const struct hid_device_info *hdi)
227 {
228 	return hdi->usage_page == 0xf1d0;
229 }
230 #else
231 static bool
232 is_fido(const struct hid_device_info *hdi)
233 {
234 	(void)hdi;
235 	fido_log_debug("%s: assuming FIDO HID", __func__);
236 	return true;
237 }
238 #endif
239 
240 void *
241 fido_hid_open(const char *path)
242 {
243 	struct hid_hidapi *ctx;
244 
245 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
246 		return (NULL);
247 	}
248 
249 	if ((ctx->handle = hid_open_path(path)) == NULL) {
250 		free(ctx);
251 		return (NULL);
252 	}
253 
254 	ctx->report_in_len = ctx->report_out_len = CTAP_MAX_REPORT_LEN;
255 
256 	return ctx;
257 }
258 
259 void
260 fido_hid_close(void *handle)
261 {
262 	struct hid_hidapi *ctx = handle;
263 
264 	hid_close(ctx->handle);
265 	free(ctx);
266 }
267 
268 int
269 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
270 {
271 	struct hid_hidapi *ctx = handle;
272 
273 	if (len != ctx->report_in_len) {
274 		fido_log_debug("%s: len %zu", __func__, len);
275 		return -1;
276 	}
277 
278 	return hid_read_timeout(ctx->handle, buf, len, ms);
279 }
280 
281 int
282 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
283 {
284 	struct hid_hidapi *ctx = handle;
285 
286 	if (len != ctx->report_out_len + 1) {
287 		fido_log_debug("%s: len %zu", __func__, len);
288 		return -1;
289 	}
290 
291 	return hid_write(ctx->handle, buf, len);
292 }
293 
294 int
295 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
296 {
297 	struct hid_device_info *hdi;
298 
299 	*olen = 0;
300 
301 	if (ilen == 0)
302 		return FIDO_OK; /* nothing to do */
303 	if (devlist == NULL)
304 		return FIDO_ERR_INVALID_ARGUMENT;
305 	if ((hdi = hid_enumerate(0, 0)) == NULL)
306 		return FIDO_OK; /* nothing to do */
307 
308 	for (struct hid_device_info *d = hdi; d != NULL; d = d->next) {
309 		if (is_fido(d) == false)
310 			continue;
311 		if (copy_info(&devlist[*olen], d) == 0) {
312 			if (++(*olen) == ilen)
313 				break;
314 		}
315 	}
316 
317 	hid_free_enumeration(hdi);
318 
319 	return FIDO_OK;
320 }
321 
322 size_t
323 fido_hid_report_in_len(void *handle)
324 {
325 	struct hid_hidapi *ctx = handle;
326 
327 	return (ctx->report_in_len);
328 }
329 
330 size_t
331 fido_hid_report_out_len(void *handle)
332 {
333 	struct hid_hidapi *ctx = handle;
334 
335 	return (ctx->report_out_len);
336 }
337