xref: /freebsd/contrib/libfido2/src/hid_hidapi.c (revision 2ccfa855b2fc331819953e3de1b1c15ce5b95a7e)
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  * SPDX-License-Identifier: BSD-2-Clause
6  */
7 
8 #ifdef __linux__
9 #include <sys/ioctl.h>
10 #include <linux/hidraw.h>
11 #include <linux/input.h>
12 #include <fcntl.h>
13 #endif
14 
15 #include <errno.h>
16 #include <hidapi.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
fido_wcslen(const wchar_t * wcs)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 *
wcs_to_cs(const wchar_t * wcs)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
copy_info(fido_dev_info_t * di,const struct hid_device_info * d)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
get_report_descriptor(const char * path,struct hidraw_report_descriptor * hrd)101 get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd)
102 {
103 	int fd;
104 	int s = -1;
105 	int ok = -1;
106 
107 	if ((fd = fido_hid_unix_open(path)) == -1) {
108 		fido_log_debug("%s: fido_hid_unix_open", __func__);
109 		return -1;
110 	}
111 
112 	if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) < 0 || s < 0 ||
113 	    (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
114 		fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__);
115 		goto fail;
116 	}
117 
118 	hrd->size = (unsigned)s;
119 
120 	if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) < 0) {
121 		fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__);
122 		goto fail;
123 	}
124 
125 	ok = 0;
126 fail:
127 	if (fd != -1)
128 		close(fd);
129 
130 	return ok;
131 }
132 
133 static bool
is_fido(const struct hid_device_info * hdi)134 is_fido(const struct hid_device_info *hdi)
135 {
136 	uint32_t usage_page = 0;
137 	struct hidraw_report_descriptor *hrd;
138 
139 	if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
140 	    get_report_descriptor(hdi->path, hrd) < 0 ||
141 	    fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0)
142 		usage_page = 0;
143 
144 	free(hrd);
145 
146 	return usage_page == 0xf1d0;
147 }
148 #elif defined(_WIN32) || defined(__APPLE__)
149 static bool
is_fido(const struct hid_device_info * hdi)150 is_fido(const struct hid_device_info *hdi)
151 {
152 	return hdi->usage_page == 0xf1d0;
153 }
154 #else
155 static bool
is_fido(const struct hid_device_info * hdi)156 is_fido(const struct hid_device_info *hdi)
157 {
158 	(void)hdi;
159 	fido_log_debug("%s: assuming FIDO HID", __func__);
160 	return true;
161 }
162 #endif
163 
164 void *
fido_hid_open(const char * path)165 fido_hid_open(const char *path)
166 {
167 	struct hid_hidapi *ctx;
168 
169 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
170 		return (NULL);
171 	}
172 
173 	if ((ctx->handle = hid_open_path(path)) == NULL) {
174 		free(ctx);
175 		return (NULL);
176 	}
177 
178 	ctx->report_in_len = ctx->report_out_len = CTAP_MAX_REPORT_LEN;
179 
180 	return ctx;
181 }
182 
183 void
fido_hid_close(void * handle)184 fido_hid_close(void *handle)
185 {
186 	struct hid_hidapi *ctx = handle;
187 
188 	hid_close(ctx->handle);
189 	free(ctx);
190 }
191 
192 int
fido_hid_set_sigmask(void * handle,const fido_sigset_t * sigmask)193 fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
194 {
195 	(void)handle;
196 	(void)sigmask;
197 
198 	return (FIDO_ERR_INTERNAL);
199 }
200 
201 int
fido_hid_read(void * handle,unsigned char * buf,size_t len,int ms)202 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
203 {
204 	struct hid_hidapi *ctx = handle;
205 
206 	if (len != ctx->report_in_len) {
207 		fido_log_debug("%s: len %zu", __func__, len);
208 		return -1;
209 	}
210 
211 	return hid_read_timeout(ctx->handle, buf, len, ms);
212 }
213 
214 int
fido_hid_write(void * handle,const unsigned char * buf,size_t len)215 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
216 {
217 	struct hid_hidapi *ctx = handle;
218 
219 	if (len != ctx->report_out_len + 1) {
220 		fido_log_debug("%s: len %zu", __func__, len);
221 		return -1;
222 	}
223 
224 	return hid_write(ctx->handle, buf, len);
225 }
226 
227 int
fido_hid_manifest(fido_dev_info_t * devlist,size_t ilen,size_t * olen)228 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
229 {
230 	struct hid_device_info *hdi;
231 
232 	*olen = 0;
233 
234 	if (ilen == 0)
235 		return FIDO_OK; /* nothing to do */
236 	if (devlist == NULL)
237 		return FIDO_ERR_INVALID_ARGUMENT;
238 	if ((hdi = hid_enumerate(0, 0)) == NULL)
239 		return FIDO_OK; /* nothing to do */
240 
241 	for (struct hid_device_info *d = hdi; d != NULL; d = d->next) {
242 		if (is_fido(d) == false)
243 			continue;
244 		if (copy_info(&devlist[*olen], d) == 0) {
245 			if (++(*olen) == ilen)
246 				break;
247 		}
248 	}
249 
250 	hid_free_enumeration(hdi);
251 
252 	return FIDO_OK;
253 }
254 
255 size_t
fido_hid_report_in_len(void * handle)256 fido_hid_report_in_len(void *handle)
257 {
258 	struct hid_hidapi *ctx = handle;
259 
260 	return (ctx->report_in_len);
261 }
262 
263 size_t
fido_hid_report_out_len(void * handle)264 fido_hid_report_out_len(void *handle)
265 {
266 	struct hid_hidapi *ctx = handle;
267 
268 	return (ctx->report_out_len);
269 }
270