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_U2FHID_LEN 64 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 fd; 37 struct usb_device_info udi; 38 fido_dev_info_t *di; 39 40 if (ilen == 0) 41 return (FIDO_OK); /* nothing to do */ 42 43 if (devlist == NULL || olen == NULL) 44 return (FIDO_ERR_INVALID_ARGUMENT); 45 46 for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) { 47 snprintf(path, sizeof(path), "/dev/fido/%zu", i); 48 if ((fd = open(path, O_RDWR)) == -1) { 49 if (errno != ENOENT && errno != ENXIO) { 50 fido_log_debug("%s: open %s: %s", __func__, 51 path, strerror(errno)); 52 } 53 continue; 54 } 55 close(fd); 56 57 memset(&udi, 0, sizeof(udi)); 58 strlcpy(udi.udi_vendor, "OpenBSD", sizeof(udi.udi_vendor)); 59 strlcpy(udi.udi_product, "fido(4)", sizeof(udi.udi_product)); 60 udi.udi_vendorNo = 0x0b5d; /* stolen from PCI_VENDOR_OPENBSD */ 61 62 fido_log_debug("%s: %s: vendor = \"%s\", product = \"%s\"", 63 __func__, path, udi.udi_vendor, udi.udi_product); 64 65 di = &devlist[*olen]; 66 memset(di, 0, sizeof(*di)); 67 if ((di->path = strdup(path)) == NULL || 68 (di->manufacturer = strdup(udi.udi_vendor)) == NULL || 69 (di->product = strdup(udi.udi_product)) == NULL) { 70 free(di->path); 71 free(di->manufacturer); 72 free(di->product); 73 explicit_bzero(di, sizeof(*di)); 74 return FIDO_ERR_INTERNAL; 75 } 76 di->vendor_id = udi.udi_vendorNo; 77 di->product_id = udi.udi_productNo; 78 di->io = (fido_dev_io_t) { 79 fido_hid_open, 80 fido_hid_close, 81 fido_hid_read, 82 fido_hid_write, 83 NULL, 84 NULL, 85 }; 86 (*olen)++; 87 } 88 89 return FIDO_OK; 90 } 91 92 /* 93 * Workaround for OpenBSD <=6.6-current (as of 201910) bug that loses 94 * sync of DATA0/DATA1 sequence bit across uhid open/close. 95 * Send pings until we get a response - early pings with incorrect 96 * sequence bits will be ignored as duplicate packets by the device. 97 */ 98 static int 99 terrible_ping_kludge(struct hid_openbsd *ctx) 100 { 101 u_char data[256]; 102 int i, n; 103 struct pollfd pfd; 104 105 if (sizeof(data) < ctx->report_out_len + 1) 106 return -1; 107 for (i = 0; i < 4; i++) { 108 memset(data, 0, sizeof(data)); 109 /* broadcast channel ID */ 110 data[1] = 0xff; 111 data[2] = 0xff; 112 data[3] = 0xff; 113 data[4] = 0xff; 114 /* Ping command */ 115 data[5] = 0x81; 116 /* One byte ping only, Vasili */ 117 data[6] = 0; 118 data[7] = 1; 119 fido_log_debug("%s: send ping %d", __func__, i); 120 if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1) 121 return -1; 122 fido_log_debug("%s: wait reply", __func__); 123 memset(&pfd, 0, sizeof(pfd)); 124 pfd.fd = ctx->fd; 125 pfd.events = POLLIN; 126 if ((n = poll(&pfd, 1, 100)) == -1) { 127 fido_log_debug("%s: poll: %s", __func__, strerror(errno)); 128 return -1; 129 } else if (n == 0) { 130 fido_log_debug("%s: timed out", __func__); 131 continue; 132 } 133 if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1) 134 return -1; 135 /* 136 * Ping isn't always supported on the broadcast channel, 137 * so we might get an error, but we don't care - we're 138 * synched now. 139 */ 140 fido_log_debug("%s: got reply", __func__); 141 fido_log_xxd(data, ctx->report_out_len); 142 return 0; 143 } 144 fido_log_debug("%s: no response", __func__); 145 return -1; 146 } 147 148 void * 149 fido_hid_open(const char *path) 150 { 151 struct hid_openbsd *ret = NULL; 152 153 if ((ret = calloc(1, sizeof(*ret))) == NULL || 154 (ret->fd = open(path, O_RDWR)) < 0) { 155 free(ret); 156 return (NULL); 157 } 158 ret->report_in_len = ret->report_out_len = MAX_U2FHID_LEN; 159 fido_log_debug("%s: inlen = %zu outlen = %zu", __func__, 160 ret->report_in_len, ret->report_out_len); 161 162 /* 163 * OpenBSD (as of 201910) has a bug that causes it to lose 164 * track of the DATA0/DATA1 sequence toggle across uhid device 165 * open and close. This is a terrible hack to work around it. 166 */ 167 if (terrible_ping_kludge(ret) != 0) { 168 fido_hid_close(ret); 169 return NULL; 170 } 171 172 return (ret); 173 } 174 175 void 176 fido_hid_close(void *handle) 177 { 178 struct hid_openbsd *ctx = (struct hid_openbsd *)handle; 179 180 close(ctx->fd); 181 free(ctx); 182 } 183 184 int 185 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) 186 { 187 struct hid_openbsd *ctx = (struct hid_openbsd *)handle; 188 ssize_t r; 189 190 (void)ms; /* XXX */ 191 192 if (len != ctx->report_in_len) { 193 fido_log_debug("%s: invalid len: got %zu, want %zu", __func__, 194 len, ctx->report_in_len); 195 return (-1); 196 } 197 if ((r = read(ctx->fd, buf, len)) == -1 || (size_t)r != len) { 198 fido_log_debug("%s: read: %s", __func__, strerror(errno)); 199 return (-1); 200 } 201 return ((int)len); 202 } 203 204 int 205 fido_hid_write(void *handle, const unsigned char *buf, size_t len) 206 { 207 struct hid_openbsd *ctx = (struct hid_openbsd *)handle; 208 ssize_t r; 209 210 if (len != ctx->report_out_len + 1) { 211 fido_log_debug("%s: invalid len: got %zu, want %zu", __func__, 212 len, ctx->report_out_len); 213 return (-1); 214 } 215 if ((r = write(ctx->fd, buf + 1, len - 1)) == -1 || 216 (size_t)r != len - 1) { 217 fido_log_debug("%s: write: %s", __func__, strerror(errno)); 218 return (-1); 219 } 220 return ((int)len); 221 } 222