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 #include <sys/types.h> 8 9 #include <sys/ioctl.h> 10 #include <dev/usb/usb.h> 11 #include <dev/usb/usbhid.h> 12 13 #include <errno.h> 14 #include <fcntl.h> 15 #include <string.h> 16 #include <unistd.h> 17 #include <usbhid.h> 18 #include <poll.h> 19 20 #include "fido.h" 21 22 #define MAX_UHID 64 23 #define MAX_REPORT_LEN (sizeof(((struct usb_ctl_report *)(NULL))->ucr_data)) 24 25 struct hid_openbsd { 26 int fd; 27 size_t report_in_len; 28 size_t report_out_len; 29 }; 30 31 int 32 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) 33 { 34 size_t i; 35 char path[64]; 36 int is_fido, fd; 37 struct usb_device_info udi; 38 report_desc_t rdesc = NULL; 39 hid_data_t hdata = NULL; 40 hid_item_t hitem; 41 fido_dev_info_t *di; 42 43 if (ilen == 0) 44 return (FIDO_OK); /* nothing to do */ 45 46 if (devlist == NULL || olen == NULL) 47 return (FIDO_ERR_INVALID_ARGUMENT); 48 49 for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) { 50 snprintf(path, sizeof(path), "/dev/uhid%zu", i); 51 if ((fd = open(path, O_RDWR)) == -1) { 52 if (errno != ENOENT && errno != ENXIO) { 53 fido_log_debug("%s: open %s: %s", __func__, 54 path, strerror(errno)); 55 } 56 continue; 57 } 58 memset(&udi, 0, sizeof(udi)); 59 if (ioctl(fd, USB_GET_DEVICEINFO, &udi) != 0) { 60 fido_log_debug("%s: get device info %s: %s", __func__, 61 path, strerror(errno)); 62 close(fd); 63 continue; 64 } 65 if ((rdesc = hid_get_report_desc(fd)) == NULL) { 66 fido_log_debug("%s: failed to get report descriptor: %s", 67 __func__, path); 68 close(fd); 69 continue; 70 } 71 if ((hdata = hid_start_parse(rdesc, 72 1<<hid_collection, -1)) == NULL) { 73 fido_log_debug("%s: failed to parse report descriptor: %s", 74 __func__, path); 75 hid_dispose_report_desc(rdesc); 76 close(fd); 77 continue; 78 } 79 is_fido = 0; 80 for (is_fido = 0; !is_fido;) { 81 memset(&hitem, 0, sizeof(hitem)); 82 if (hid_get_item(hdata, &hitem) <= 0) 83 break; 84 if ((hitem._usage_page & 0xFFFF0000) == 0xf1d00000) 85 is_fido = 1; 86 } 87 hid_end_parse(hdata); 88 hid_dispose_report_desc(rdesc); 89 close(fd); 90 91 if (!is_fido) 92 continue; 93 94 fido_log_debug("%s: %s: bus = 0x%02x, addr = 0x%02x", 95 __func__, path, udi.udi_bus, udi.udi_addr); 96 fido_log_debug("%s: %s: vendor = \"%s\", product = \"%s\"", 97 __func__, path, udi.udi_vendor, udi.udi_product); 98 fido_log_debug("%s: %s: productNo = 0x%04x, vendorNo = 0x%04x, " 99 "releaseNo = 0x%04x", __func__, path, udi.udi_productNo, 100 udi.udi_vendorNo, udi.udi_releaseNo); 101 102 di = &devlist[*olen]; 103 memset(di, 0, sizeof(*di)); 104 di->io = (fido_dev_io_t) { 105 fido_hid_open, 106 fido_hid_close, 107 fido_hid_read, 108 fido_hid_write, 109 }; 110 if ((di->path = strdup(path)) == NULL || 111 (di->manufacturer = strdup(udi.udi_vendor)) == NULL || 112 (di->product = strdup(udi.udi_product)) == NULL) { 113 free(di->path); 114 free(di->manufacturer); 115 free(di->product); 116 explicit_bzero(di, sizeof(*di)); 117 return FIDO_ERR_INTERNAL; 118 } 119 di->vendor_id = udi.udi_vendorNo; 120 di->product_id = udi.udi_productNo; 121 (*olen)++; 122 } 123 124 return FIDO_OK; 125 } 126 127 /* 128 * Workaround for OpenBSD <=6.6-current (as of 201910) bug that loses 129 * sync of DATA0/DATA1 sequence bit across uhid open/close. 130 * Send pings until we get a response - early pings with incorrect 131 * sequence bits will be ignored as duplicate packets by the device. 132 */ 133 static int 134 terrible_ping_kludge(struct hid_openbsd *ctx) 135 { 136 u_char data[256]; 137 int i, n; 138 struct pollfd pfd; 139 140 if (sizeof(data) < ctx->report_out_len + 1) 141 return -1; 142 for (i = 0; i < 4; i++) { 143 memset(data, 0, sizeof(data)); 144 /* broadcast channel ID */ 145 data[1] = 0xff; 146 data[2] = 0xff; 147 data[3] = 0xff; 148 data[4] = 0xff; 149 /* Ping command */ 150 data[5] = 0x81; 151 /* One byte ping only, Vasili */ 152 data[6] = 0; 153 data[7] = 1; 154 fido_log_debug("%s: send ping %d", __func__, i); 155 if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1) 156 return -1; 157 fido_log_debug("%s: wait reply", __func__); 158 memset(&pfd, 0, sizeof(pfd)); 159 pfd.fd = ctx->fd; 160 pfd.events = POLLIN; 161 if ((n = poll(&pfd, 1, 100)) == -1) { 162 fido_log_debug("%s: poll: %s", __func__, strerror(errno)); 163 return -1; 164 } else if (n == 0) { 165 fido_log_debug("%s: timed out", __func__); 166 continue; 167 } 168 if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1) 169 return -1; 170 /* 171 * Ping isn't always supported on the broadcast channel, 172 * so we might get an error, but we don't care - we're 173 * synched now. 174 */ 175 fido_log_debug("%s: got reply", __func__); 176 fido_log_xxd(data, ctx->report_out_len); 177 return 0; 178 } 179 fido_log_debug("%s: no response", __func__); 180 return -1; 181 } 182 183 void * 184 fido_hid_open(const char *path) 185 { 186 struct hid_openbsd *ret = NULL; 187 report_desc_t rdesc = NULL; 188 int len, usb_report_id = 0; 189 190 if ((ret = calloc(1, sizeof(*ret))) == NULL || 191 (ret->fd = open(path, O_RDWR)) < 0) { 192 free(ret); 193 return (NULL); 194 } 195 if (ioctl(ret->fd, USB_GET_REPORT_ID, &usb_report_id) != 0) { 196 fido_log_debug("%s: failed to get report ID: %s", __func__, 197 strerror(errno)); 198 goto fail; 199 } 200 if ((rdesc = hid_get_report_desc(ret->fd)) == NULL) { 201 fido_log_debug("%s: failed to get report descriptor", __func__); 202 goto fail; 203 } 204 if ((len = hid_report_size(rdesc, hid_input, usb_report_id)) <= 0 || 205 (size_t)len > MAX_REPORT_LEN) { 206 fido_log_debug("%s: bad input report size %d", __func__, len); 207 goto fail; 208 } 209 ret->report_in_len = (size_t)len; 210 if ((len = hid_report_size(rdesc, hid_output, usb_report_id)) <= 0 || 211 (size_t)len > MAX_REPORT_LEN) { 212 fido_log_debug("%s: bad output report size %d", __func__, len); 213 fail: 214 hid_dispose_report_desc(rdesc); 215 close(ret->fd); 216 free(ret); 217 return NULL; 218 } 219 ret->report_out_len = (size_t)len; 220 hid_dispose_report_desc(rdesc); 221 fido_log_debug("%s: USB report ID %d, inlen = %zu outlen = %zu", 222 __func__, usb_report_id, ret->report_in_len, ret->report_out_len); 223 224 /* 225 * OpenBSD (as of 201910) has a bug that causes it to lose 226 * track of the DATA0/DATA1 sequence toggle across uhid device 227 * open and close. This is a terrible hack to work around it. 228 */ 229 if (terrible_ping_kludge(ret) != 0) { 230 fido_hid_close(ret); 231 return NULL; 232 } 233 234 return (ret); 235 } 236 237 void 238 fido_hid_close(void *handle) 239 { 240 struct hid_openbsd *ctx = (struct hid_openbsd *)handle; 241 242 close(ctx->fd); 243 free(ctx); 244 } 245 246 int 247 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) 248 { 249 struct hid_openbsd *ctx = (struct hid_openbsd *)handle; 250 ssize_t r; 251 252 (void)ms; /* XXX */ 253 254 if (len != ctx->report_in_len) { 255 fido_log_debug("%s: invalid len: got %zu, want %zu", __func__, 256 len, ctx->report_in_len); 257 return (-1); 258 } 259 if ((r = read(ctx->fd, buf, len)) == -1 || (size_t)r != len) { 260 fido_log_debug("%s: read: %s", __func__, strerror(errno)); 261 return (-1); 262 } 263 return ((int)len); 264 } 265 266 int 267 fido_hid_write(void *handle, const unsigned char *buf, size_t len) 268 { 269 struct hid_openbsd *ctx = (struct hid_openbsd *)handle; 270 ssize_t r; 271 272 if (len != ctx->report_out_len + 1) { 273 fido_log_debug("%s: invalid len: got %zu, want %zu", __func__, 274 len, ctx->report_out_len); 275 return (-1); 276 } 277 if ((r = write(ctx->fd, buf + 1, len - 1)) == -1 || 278 (size_t)r != len - 1) { 279 fido_log_debug("%s: write: %s", __func__, strerror(errno)); 280 return (-1); 281 } 282 return ((int)len); 283 } 284