diff options
Diffstat (limited to 'hw/usb')
-rw-r--r-- | hw/usb/Kconfig | 5 | ||||
-rw-r--r-- | hw/usb/core.c | 16 | ||||
-rw-r--r-- | hw/usb/dev-hid.c | 26 | ||||
-rw-r--r-- | hw/usb/dev-wacom.c | 12 | ||||
-rw-r--r-- | hw/usb/hcd-ehci.c | 12 | ||||
-rw-r--r-- | hw/usb/hcd-xhci.c | 5 | ||||
-rw-r--r-- | hw/usb/host-libusb.c | 37 | ||||
-rw-r--r-- | hw/usb/meson.build | 7 | ||||
-rw-r--r-- | hw/usb/u2f-emulated.c | 405 | ||||
-rw-r--r-- | hw/usb/u2f-passthru.c | 551 | ||||
-rw-r--r-- | hw/usb/u2f.c | 352 | ||||
-rw-r--r-- | hw/usb/u2f.h | 92 |
12 files changed, 1481 insertions, 39 deletions
diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig index 5e63dc7..3fc8fbe 100644 --- a/hw/usb/Kconfig +++ b/hw/usb/Kconfig @@ -96,6 +96,11 @@ config USB_STORAGE_MTP default y depends on USB +config USB_U2F + bool + default y + depends on USB + config IMX_USBPHY bool default y diff --git a/hw/usb/core.c b/hw/usb/core.c index 5abd128..5234dcc 100644 --- a/hw/usb/core.c +++ b/hw/usb/core.c @@ -129,6 +129,7 @@ void usb_wakeup(USBEndpoint *ep, unsigned int stream) static void do_token_setup(USBDevice *s, USBPacket *p) { int request, value, index; + unsigned int setup_len; if (p->iov.size != 8) { p->status = USB_RET_STALL; @@ -138,14 +139,15 @@ static void do_token_setup(USBDevice *s, USBPacket *p) usb_packet_copy(p, s->setup_buf, p->iov.size); s->setup_index = 0; p->actual_length = 0; - s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; - if (s->setup_len > sizeof(s->data_buf)) { + setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; + if (setup_len > sizeof(s->data_buf)) { fprintf(stderr, "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n", - s->setup_len, sizeof(s->data_buf)); + setup_len, sizeof(s->data_buf)); p->status = USB_RET_STALL; return; } + s->setup_len = setup_len; request = (s->setup_buf[0] << 8) | s->setup_buf[1]; value = (s->setup_buf[3] << 8) | s->setup_buf[2]; @@ -259,26 +261,28 @@ static void do_token_out(USBDevice *s, USBPacket *p) static void do_parameter(USBDevice *s, USBPacket *p) { int i, request, value, index; + unsigned int setup_len; for (i = 0; i < 8; i++) { s->setup_buf[i] = p->parameter >> (i*8); } s->setup_state = SETUP_STATE_PARAM; - s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; s->setup_index = 0; request = (s->setup_buf[0] << 8) | s->setup_buf[1]; value = (s->setup_buf[3] << 8) | s->setup_buf[2]; index = (s->setup_buf[5] << 8) | s->setup_buf[4]; - if (s->setup_len > sizeof(s->data_buf)) { + setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; + if (setup_len > sizeof(s->data_buf)) { fprintf(stderr, "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n", - s->setup_len, sizeof(s->data_buf)); + setup_len, sizeof(s->data_buf)); p->status = USB_RET_STALL; return; } + s->setup_len = setup_len; if (p->pid == USB_TOKEN_OUT) { usb_packet_copy(p, s->data_buf, s->setup_len); diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c index 89f63b6..c73f7b2 100644 --- a/hw/usb/dev-hid.c +++ b/hw/usb/dev-hid.c @@ -32,21 +32,9 @@ #include "qemu/module.h" #include "qemu/timer.h" #include "hw/input/hid.h" +#include "hw/usb/hid.h" #include "hw/qdev-properties.h" -/* HID interface requests */ -#define GET_REPORT 0xa101 -#define GET_IDLE 0xa102 -#define GET_PROTOCOL 0xa103 -#define SET_REPORT 0x2109 -#define SET_IDLE 0x210a -#define SET_PROTOCOL 0x210b - -/* HID descriptor types */ -#define USB_DT_HID 0x21 -#define USB_DT_REPORT 0x22 -#define USB_DT_PHY 0x23 - typedef struct USBHIDState { USBDevice dev; USBEndpoint *intr; @@ -618,38 +606,38 @@ static void usb_hid_handle_control(USBDevice *dev, USBPacket *p, goto fail; } break; - case GET_REPORT: + case HID_GET_REPORT: if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { p->actual_length = hid_pointer_poll(hs, data, length); } else if (hs->kind == HID_KEYBOARD) { p->actual_length = hid_keyboard_poll(hs, data, length); } break; - case SET_REPORT: + case HID_SET_REPORT: if (hs->kind == HID_KEYBOARD) { p->actual_length = hid_keyboard_write(hs, data, length); } else { goto fail; } break; - case GET_PROTOCOL: + case HID_GET_PROTOCOL: if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) { goto fail; } data[0] = hs->protocol; p->actual_length = 1; break; - case SET_PROTOCOL: + case HID_SET_PROTOCOL: if (hs->kind != HID_KEYBOARD && hs->kind != HID_MOUSE) { goto fail; } hs->protocol = value; break; - case GET_IDLE: + case HID_GET_IDLE: data[0] = hs->idle; p->actual_length = 1; break; - case SET_IDLE: + case HID_SET_IDLE: hs->idle = (uint8_t) (value >> 8); hid_set_next_idle(hs); if (hs->kind == HID_MOUSE || hs->kind == HID_TABLET) { diff --git a/hw/usb/dev-wacom.c b/hw/usb/dev-wacom.c index 8aba44b..76fc5a5 100644 --- a/hw/usb/dev-wacom.c +++ b/hw/usb/dev-wacom.c @@ -29,6 +29,7 @@ #include "qemu/osdep.h" #include "ui/console.h" #include "hw/usb.h" +#include "hw/usb/hid.h" #include "migration/vmstate.h" #include "qemu/module.h" #include "desc.h" @@ -37,13 +38,6 @@ #define WACOM_GET_REPORT 0x2101 #define WACOM_SET_REPORT 0x2109 -/* HID interface requests */ -#define HID_GET_REPORT 0xa101 -#define HID_GET_IDLE 0xa102 -#define HID_GET_PROTOCOL 0xa103 -#define HID_SET_IDLE 0x210a -#define HID_SET_PROTOCOL 0x210b - typedef struct USBWacomState { USBDevice dev; USBEndpoint *intr; @@ -86,11 +80,11 @@ static const USBDescIface desc_iface_wacom = { /* HID descriptor */ .data = (uint8_t[]) { 0x09, /* u8 bLength */ - 0x21, /* u8 bDescriptorType */ + USB_DT_HID, /* u8 bDescriptorType */ 0x01, 0x10, /* u16 HID_class */ 0x00, /* u8 country_code */ 0x01, /* u8 num_descriptors */ - 0x22, /* u8 type: Report */ + USB_DT_REPORT, /* u8 type: Report */ 0x6e, 0, /* u16 len */ }, }, diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c index 1495e8f..2b99544 100644 --- a/hw/usb/hcd-ehci.c +++ b/hw/usb/hcd-ehci.c @@ -352,7 +352,6 @@ static void ehci_trace_sitd(EHCIState *s, hwaddr addr, static void ehci_trace_guest_bug(EHCIState *s, const char *message) { trace_usb_ehci_guest_bug(message); - warn_report("%s", message); } static inline bool ehci_enabled(EHCIState *s) @@ -1373,7 +1372,10 @@ static int ehci_execute(EHCIPacket *p, const char *action) spd = (p->pid == USB_TOKEN_IN && NLPTR_TBIT(p->qtd.altnext) == 0); usb_packet_setup(&p->packet, p->pid, ep, 0, p->qtdaddr, spd, (p->qtd.token & QTD_TOKEN_IOC) != 0); - usb_packet_map(&p->packet, &p->sgl); + if (usb_packet_map(&p->packet, &p->sgl)) { + qemu_sglist_destroy(&p->sgl); + return -1; + } p->async = EHCI_ASYNC_INITIALIZED; } @@ -1445,6 +1447,7 @@ static int ehci_process_itd(EHCIState *ehci, dev = ehci_find_device(ehci, devaddr); if (dev == NULL) { ehci_trace_guest_bug(ehci, "no device found"); + qemu_sglist_destroy(&ehci->isgl); return -1; } pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT; @@ -1452,7 +1455,10 @@ static int ehci_process_itd(EHCIState *ehci, if (ep && ep->type == USB_ENDPOINT_XFER_ISOC) { usb_packet_setup(&ehci->ipacket, pid, ep, 0, addr, false, (itd->transact[i] & ITD_XACT_IOC) != 0); - usb_packet_map(&ehci->ipacket, &ehci->isgl); + if (usb_packet_map(&ehci->ipacket, &ehci->isgl)) { + qemu_sglist_destroy(&ehci->isgl); + return -1; + } usb_handle_packet(dev, &ehci->ipacket); usb_packet_unmap(&ehci->ipacket, &ehci->isgl); } else { diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c index 67a18fe..46a2186 100644 --- a/hw/usb/hcd-xhci.c +++ b/hw/usb/hcd-xhci.c @@ -1615,7 +1615,10 @@ static int xhci_setup_packet(XHCITransfer *xfer) xhci_xfer_create_sgl(xfer, dir == USB_TOKEN_IN); /* Also sets int_req */ usb_packet_setup(&xfer->packet, dir, ep, xfer->streamid, xfer->trbs[0].addr, false, xfer->int_req); - usb_packet_map(&xfer->packet, &xfer->sgl); + if (usb_packet_map(&xfer->packet, &xfer->sgl)) { + qemu_sglist_destroy(&xfer->sgl); + return -1; + } DPRINTF("xhci: setup packet pid 0x%x addr %d ep %d\n", xfer->packet.pid, ep->dev->addr, ep->nr); return 0; diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c index c474551..08604f7 100644 --- a/hw/usb/host-libusb.c +++ b/hw/usb/host-libusb.c @@ -39,6 +39,11 @@ #endif #include <libusb.h> +#ifdef CONFIG_LINUX +#include <sys/ioctl.h> +#include <linux/usbdevice_fs.h> +#endif + #include "qapi/error.h" #include "migration/vmstate.h" #include "monitor/monitor.h" @@ -885,6 +890,7 @@ static void usb_host_ep_update(USBHostDevice *s) static int usb_host_open(USBHostDevice *s, libusb_device *dev, int hostfd) { USBDevice *udev = USB_DEVICE(s); + int libusb_speed; int bus_num = 0; int addr = 0; int rc; @@ -935,7 +941,36 @@ static int usb_host_open(USBHostDevice *s, libusb_device *dev, int hostfd) usb_ep_init(udev); usb_host_ep_update(s); - udev->speed = speed_map[libusb_get_device_speed(dev)]; + libusb_speed = libusb_get_device_speed(dev); +#ifdef CONFIG_LINUX + if (hostfd && libusb_speed == 0) { + /* + * Workaround libusb bug: libusb_get_device_speed() does not + * work for libusb_wrap_sys_device() devices in v1.0.23. + * + * Speeds are defined in linux/usb/ch9.h, file not included + * due to name conflicts. + */ + int rc = ioctl(hostfd, USBDEVFS_GET_SPEED, NULL); + switch (rc) { + case 1: /* low */ + libusb_speed = LIBUSB_SPEED_LOW; + break; + case 2: /* full */ + libusb_speed = LIBUSB_SPEED_FULL; + break; + case 3: /* high */ + case 4: /* wireless */ + libusb_speed = LIBUSB_SPEED_HIGH; + break; + case 5: /* super */ + case 6: /* super plus */ + libusb_speed = LIBUSB_SPEED_SUPER; + break; + } + } +#endif + udev->speed = speed_map[libusb_speed]; usb_host_speed_compat(s); if (s->ddesc.iProduct) { diff --git a/hw/usb/meson.build b/hw/usb/meson.build index 3c44a1b..b7c7ff2 100644 --- a/hw/usb/meson.build +++ b/hw/usb/meson.build @@ -50,6 +50,13 @@ if config_host.has_key('CONFIG_SMARTCARD') hw_usb_modules += {'smartcard': usbsmartcard_ss} endif +# U2F +softmmu_ss.add(when: 'CONFIG_USB_U2F', if_true: files('u2f.c')) +softmmu_ss.add(when: ['CONFIG_LINUX', 'CONFIG_USB_U2F'], if_true: [libudev, files('u2f-passthru.c')]) +if u2f.found() + softmmu_ss.add(when: 'CONFIG_USB_U2F', if_true: [u2f, files('u2f-emulated.c')]) +endif + # usb redirect if config_host.has_key('CONFIG_USB_REDIR') usbredir_ss = ss.source_set() diff --git a/hw/usb/u2f-emulated.c b/hw/usb/u2f-emulated.c new file mode 100644 index 0000000..9e1b829 --- /dev/null +++ b/hw/usb/u2f-emulated.c @@ -0,0 +1,405 @@ +/* + * U2F USB Emulated device. + * + * Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr> + * Written by César Belley <cesar.belley@lse.epita.fr> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/thread.h" +#include "qemu/main-loop.h" +#include "qapi/error.h" +#include "hw/usb.h" +#include "hw/qdev-properties.h" + +#include <u2f-emu/u2f-emu.h> + +#include "u2f.h" + +/* Counter which sync with a file */ +struct synced_counter { + /* Emulated device counter */ + struct u2f_emu_vdev_counter vdev_counter; + + /* Private attributes */ + uint32_t value; + FILE *fp; +}; + +static void counter_increment(struct u2f_emu_vdev_counter *vdev_counter) +{ + struct synced_counter *counter = (struct synced_counter *)vdev_counter; + ++counter->value; + + /* Write back */ + if (fseek(counter->fp, 0, SEEK_SET) == -1) { + return; + } + fprintf(counter->fp, "%u\n", counter->value); +} + +static uint32_t counter_read(struct u2f_emu_vdev_counter *vdev_counter) +{ + struct synced_counter *counter = (struct synced_counter *)vdev_counter; + return counter->value; +} + +typedef struct U2FEmulatedState U2FEmulatedState; + +#define PENDING_OUT_NUM 32 + +struct U2FEmulatedState { + U2FKeyState base; + + /* U2F virtual emulated device */ + u2f_emu_vdev *vdev; + QemuMutex vdev_mutex; + + /* Properties */ + char *dir; + char *cert; + char *privkey; + char *entropy; + char *counter; + struct synced_counter synced_counter; + + /* Pending packets received from the guest */ + uint8_t pending_out[PENDING_OUT_NUM][U2FHID_PACKET_SIZE]; + uint8_t pending_out_start; + uint8_t pending_out_end; + uint8_t pending_out_num; + QemuMutex pending_out_mutex; + + /* Emulation thread and sync */ + QemuCond key_cond; + QemuMutex key_mutex; + QemuThread key_thread; + bool stop_thread; + EventNotifier notifier; +}; + +#define TYPE_U2F_EMULATED "u2f-emulated" +#define EMULATED_U2F_KEY(obj) \ + OBJECT_CHECK(U2FEmulatedState, (obj), TYPE_U2F_EMULATED) + +static void u2f_emulated_reset(U2FEmulatedState *key) +{ + key->pending_out_start = 0; + key->pending_out_end = 0; + key->pending_out_num = 0; +} + +static void u2f_pending_out_add(U2FEmulatedState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + int index; + + if (key->pending_out_num >= PENDING_OUT_NUM) { + return; + } + + index = key->pending_out_end; + key->pending_out_end = (index + 1) % PENDING_OUT_NUM; + ++key->pending_out_num; + + memcpy(&key->pending_out[index], packet, U2FHID_PACKET_SIZE); +} + +static uint8_t *u2f_pending_out_get(U2FEmulatedState *key) +{ + int index; + + if (key->pending_out_num == 0) { + return NULL; + } + + index = key->pending_out_start; + key->pending_out_start = (index + 1) % PENDING_OUT_NUM; + --key->pending_out_num; + + return key->pending_out[index]; +} + +static void u2f_emulated_recv_from_guest(U2FKeyState *base, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + U2FEmulatedState *key = EMULATED_U2F_KEY(base); + + qemu_mutex_lock(&key->pending_out_mutex); + u2f_pending_out_add(key, packet); + qemu_mutex_unlock(&key->pending_out_mutex); + + qemu_mutex_lock(&key->key_mutex); + qemu_cond_signal(&key->key_cond); + qemu_mutex_unlock(&key->key_mutex); +} + +static void *u2f_emulated_thread(void* arg) +{ + U2FEmulatedState *key = arg; + uint8_t packet[U2FHID_PACKET_SIZE]; + uint8_t *packet_out = NULL; + + + while (true) { + /* Wait signal */ + qemu_mutex_lock(&key->key_mutex); + qemu_cond_wait(&key->key_cond, &key->key_mutex); + qemu_mutex_unlock(&key->key_mutex); + + /* Exit thread check */ + if (key->stop_thread) { + key->stop_thread = false; + break; + } + + qemu_mutex_lock(&key->pending_out_mutex); + packet_out = u2f_pending_out_get(key); + if (packet_out == NULL) { + qemu_mutex_unlock(&key->pending_out_mutex); + continue; + } + memcpy(packet, packet_out, U2FHID_PACKET_SIZE); + qemu_mutex_unlock(&key->pending_out_mutex); + + qemu_mutex_lock(&key->vdev_mutex); + u2f_emu_vdev_send(key->vdev, U2F_EMU_USB, packet, + U2FHID_PACKET_SIZE); + + /* Notify response */ + if (u2f_emu_vdev_has_response(key->vdev, U2F_EMU_USB)) { + event_notifier_set(&key->notifier); + } + qemu_mutex_unlock(&key->vdev_mutex); + } + return NULL; +} + +static ssize_t u2f_emulated_read(const char *path, char *buffer, + size_t buffer_len) +{ + int fd; + ssize_t ret; + + fd = qemu_open(path, O_RDONLY); + if (fd < 0) { + return -1; + } + + ret = read(fd, buffer, buffer_len); + close(fd); + + return ret; +} + +static bool u2f_emulated_setup_counter(const char *path, + struct synced_counter *counter) +{ + int fd, ret; + FILE *fp; + + fd = qemu_open(path, O_RDWR); + if (fd < 0) { + return false; + } + fp = fdopen(fd, "r+"); + if (fp == NULL) { + close(fd); + return false; + } + ret = fscanf(fp, "%u", &counter->value); + if (ret == EOF) { + fclose(fp); + return false; + } + counter->fp = fp; + counter->vdev_counter.counter_increment = counter_increment; + counter->vdev_counter.counter_read = counter_read; + + return true; +} + +static u2f_emu_rc u2f_emulated_setup_vdev_manualy(U2FEmulatedState *key) +{ + ssize_t ret; + char cert_pem[4096], privkey_pem[2048]; + struct u2f_emu_vdev_setup setup_info; + + /* Certificate */ + ret = u2f_emulated_read(key->cert, cert_pem, sizeof(cert_pem)); + if (ret < 0) { + return -1; + } + + /* Private key */ + ret = u2f_emulated_read(key->privkey, privkey_pem, sizeof(privkey_pem)); + if (ret < 0) { + return -1; + } + + /* Entropy */ + ret = u2f_emulated_read(key->entropy, (char *)&setup_info.entropy, + sizeof(setup_info.entropy)); + if (ret < 0) { + return -1; + } + + /* Counter */ + if (!u2f_emulated_setup_counter(key->counter, &key->synced_counter)) { + return -1; + } + + /* Setup */ + setup_info.certificate = cert_pem; + setup_info.private_key = privkey_pem; + setup_info.counter = (struct u2f_emu_vdev_counter *)&key->synced_counter; + + return u2f_emu_vdev_new(&key->vdev, &setup_info); +} + +static void u2f_emulated_event_handler(EventNotifier *notifier) +{ + U2FEmulatedState *key = container_of(notifier, U2FEmulatedState, notifier); + size_t packet_size; + uint8_t *packet_in = NULL; + + event_notifier_test_and_clear(&key->notifier); + qemu_mutex_lock(&key->vdev_mutex); + while (u2f_emu_vdev_has_response(key->vdev, U2F_EMU_USB)) { + packet_size = u2f_emu_vdev_get_response(key->vdev, U2F_EMU_USB, + &packet_in); + if (packet_size == U2FHID_PACKET_SIZE) { + u2f_send_to_guest(&key->base, packet_in); + } + u2f_emu_vdev_free_response(packet_in); + } + qemu_mutex_unlock(&key->vdev_mutex); +} + +static void u2f_emulated_realize(U2FKeyState *base, Error **errp) +{ + U2FEmulatedState *key = EMULATED_U2F_KEY(base); + u2f_emu_rc rc; + + if (key->cert != NULL || key->privkey != NULL || key->entropy != NULL + || key->counter != NULL) { + if (key->cert != NULL && key->privkey != NULL + && key->entropy != NULL && key->counter != NULL) { + rc = u2f_emulated_setup_vdev_manualy(key); + } else { + error_setg(errp, "%s: cert, priv, entropy and counter " + "parameters must be provided to manualy configure " + "the emulated device", TYPE_U2F_EMULATED); + return; + } + } else if (key->dir != NULL) { + rc = u2f_emu_vdev_new_from_dir(&key->vdev, key->dir); + } else { + rc = u2f_emu_vdev_new_ephemeral(&key->vdev); + } + + if (rc != U2F_EMU_OK) { + error_setg(errp, "%s: Failed to setup the key", TYPE_U2F_EMULATED); + return; + } + + if (event_notifier_init(&key->notifier, false) < 0) { + error_setg(errp, "%s: Failed to initialize notifier", + TYPE_U2F_EMULATED); + return; + } + /* Notifier */ + event_notifier_set_handler(&key->notifier, u2f_emulated_event_handler); + + /* Synchronization */ + qemu_cond_init(&key->key_cond); + qemu_mutex_init(&key->vdev_mutex); + qemu_mutex_init(&key->pending_out_mutex); + qemu_mutex_init(&key->key_mutex); + u2f_emulated_reset(key); + + /* Thread */ + key->stop_thread = false; + qemu_thread_create(&key->key_thread, "u2f-key", u2f_emulated_thread, + key, QEMU_THREAD_JOINABLE); +} + +static void u2f_emulated_unrealize(U2FKeyState *base) +{ + U2FEmulatedState *key = EMULATED_U2F_KEY(base); + + /* Thread */ + key->stop_thread = true; + qemu_cond_signal(&key->key_cond); + qemu_thread_join(&key->key_thread); + + /* Notifier */ + event_notifier_set_handler(&key->notifier, NULL); + event_notifier_cleanup(&key->notifier); + + /* Synchronization */ + qemu_cond_destroy(&key->key_cond); + qemu_mutex_destroy(&key->vdev_mutex); + qemu_mutex_destroy(&key->key_mutex); + qemu_mutex_destroy(&key->pending_out_mutex); + + /* Vdev */ + u2f_emu_vdev_free(key->vdev); + if (key->synced_counter.fp != NULL) { + fclose(key->synced_counter.fp); + } +} + +static Property u2f_emulated_properties[] = { + DEFINE_PROP_STRING("dir", U2FEmulatedState, dir), + DEFINE_PROP_STRING("cert", U2FEmulatedState, cert), + DEFINE_PROP_STRING("privkey", U2FEmulatedState, privkey), + DEFINE_PROP_STRING("entropy", U2FEmulatedState, entropy), + DEFINE_PROP_STRING("counter", U2FEmulatedState, counter), + DEFINE_PROP_END_OF_LIST(), +}; + +static void u2f_emulated_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + U2FKeyClass *kc = U2F_KEY_CLASS(klass); + + kc->realize = u2f_emulated_realize; + kc->unrealize = u2f_emulated_unrealize; + kc->recv_from_guest = u2f_emulated_recv_from_guest; + dc->desc = "QEMU U2F emulated key"; + device_class_set_props(dc, u2f_emulated_properties); +} + +static const TypeInfo u2f_key_emulated_info = { + .name = TYPE_U2F_EMULATED, + .parent = TYPE_U2F_KEY, + .instance_size = sizeof(U2FEmulatedState), + .class_init = u2f_emulated_class_init +}; + +static void u2f_key_emulated_register_types(void) +{ + type_register_static(&u2f_key_emulated_info); +} + +type_init(u2f_key_emulated_register_types) diff --git a/hw/usb/u2f-passthru.c b/hw/usb/u2f-passthru.c new file mode 100644 index 0000000..e9c8aa4 --- /dev/null +++ b/hw/usb/u2f-passthru.c @@ -0,0 +1,551 @@ +/* + * U2F USB Passthru device. + * + * Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr> + * Written by César Belley <cesar.belley@lse.epita.fr> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/main-loop.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "hw/qdev-properties.h" +#include "hw/usb.h" +#include "migration/vmstate.h" + +#include "u2f.h" + +#ifdef CONFIG_LIBUDEV +#include <libudev.h> +#endif +#include <linux/hidraw.h> +#include <sys/ioctl.h> + +#define NONCE_SIZE 8 +#define BROADCAST_CID 0xFFFFFFFF +#define TRANSACTION_TIMEOUT 120000 + +struct transaction { + uint32_t cid; + uint16_t resp_bcnt; + uint16_t resp_size; + + /* Nonce for broadcast isolation */ + uint8_t nonce[NONCE_SIZE]; +}; + +typedef struct U2FPassthruState U2FPassthruState; + +#define CURRENT_TRANSACTIONS_NUM 4 + +struct U2FPassthruState { + U2FKeyState base; + + /* Host device */ + char *hidraw; + int hidraw_fd; + + /* Current Transactions */ + struct transaction current_transactions[CURRENT_TRANSACTIONS_NUM]; + uint8_t current_transactions_start; + uint8_t current_transactions_end; + uint8_t current_transactions_num; + + /* Transaction time checking */ + int64_t last_transaction_time; + QEMUTimer timer; +}; + +#define TYPE_U2F_PASSTHRU "u2f-passthru" +#define PASSTHRU_U2F_KEY(obj) \ + OBJECT_CHECK(U2FPassthruState, (obj), TYPE_U2F_PASSTHRU) + +/* Init packet sizes */ +#define PACKET_INIT_HEADER_SIZE 7 +#define PACKET_INIT_DATA_SIZE (U2FHID_PACKET_SIZE - PACKET_INIT_HEADER_SIZE) + +/* Cont packet sizes */ +#define PACKET_CONT_HEADER_SIZE 5 +#define PACKET_CONT_DATA_SIZE (U2FHID_PACKET_SIZE - PACKET_CONT_HEADER_SIZE) + +struct packet_init { + uint32_t cid; + uint8_t cmd; + uint8_t bcnth; + uint8_t bcntl; + uint8_t data[PACKET_INIT_DATA_SIZE]; +} QEMU_PACKED; + +static inline uint32_t packet_get_cid(const void *packet) +{ + return *((uint32_t *)packet); +} + +static inline bool packet_is_init(const void *packet) +{ + return ((uint8_t *)packet)[4] & (1 << 7); +} + +static inline uint16_t packet_init_get_bcnt( + const struct packet_init *packet_init) +{ + uint16_t bcnt = 0; + bcnt |= packet_init->bcnth << 8; + bcnt |= packet_init->bcntl; + + return bcnt; +} + +static void u2f_passthru_reset(U2FPassthruState *key) +{ + timer_del(&key->timer); + qemu_set_fd_handler(key->hidraw_fd, NULL, NULL, key); + key->last_transaction_time = 0; + key->current_transactions_start = 0; + key->current_transactions_end = 0; + key->current_transactions_num = 0; +} + +static void u2f_timeout_check(void *opaque) +{ + U2FPassthruState *key = opaque; + int64_t time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); + + if (time > key->last_transaction_time + TRANSACTION_TIMEOUT) { + u2f_passthru_reset(key); + } else { + timer_mod(&key->timer, time + TRANSACTION_TIMEOUT / 4); + } +} + +static int u2f_transaction_get_index(U2FPassthruState *key, uint32_t cid) +{ + for (int i = 0; i < key->current_transactions_num; ++i) { + int index = (key->current_transactions_start + i) + % CURRENT_TRANSACTIONS_NUM; + if (cid == key->current_transactions[index].cid) { + return index; + } + } + return -1; +} + +static struct transaction *u2f_transaction_get(U2FPassthruState *key, + uint32_t cid) +{ + int index = u2f_transaction_get_index(key, cid); + if (index < 0) { + return NULL; + } + return &key->current_transactions[index]; +} + +static struct transaction *u2f_transaction_get_from_nonce(U2FPassthruState *key, + const uint8_t nonce[NONCE_SIZE]) +{ + for (int i = 0; i < key->current_transactions_num; ++i) { + int index = (key->current_transactions_start + i) + % CURRENT_TRANSACTIONS_NUM; + if (key->current_transactions[index].cid == BROADCAST_CID + && memcmp(nonce, key->current_transactions[index].nonce, + NONCE_SIZE) == 0) { + return &key->current_transactions[index]; + } + } + return NULL; +} + +static void u2f_transaction_close(U2FPassthruState *key, uint32_t cid) +{ + int index, next_index; + index = u2f_transaction_get_index(key, cid); + if (index < 0) { + return; + } + next_index = (index + 1) % CURRENT_TRANSACTIONS_NUM; + + /* Rearrange to ensure the oldest is at the start position */ + while (next_index != key->current_transactions_end) { + memcpy(&key->current_transactions[index], + &key->current_transactions[next_index], + sizeof(struct transaction)); + + index = next_index; + next_index = (index + 1) % CURRENT_TRANSACTIONS_NUM; + } + + key->current_transactions_end = index; + --key->current_transactions_num; + + if (key->current_transactions_num == 0) { + u2f_passthru_reset(key); + } +} + +static void u2f_transaction_add(U2FPassthruState *key, uint32_t cid, + const uint8_t nonce[NONCE_SIZE]) +{ + uint8_t index; + struct transaction *transaction; + + if (key->current_transactions_num >= CURRENT_TRANSACTIONS_NUM) { + /* Close the oldest transaction */ + index = key->current_transactions_start; + transaction = &key->current_transactions[index]; + u2f_transaction_close(key, transaction->cid); + } + + /* Index */ + index = key->current_transactions_end; + key->current_transactions_end = (index + 1) % CURRENT_TRANSACTIONS_NUM; + ++key->current_transactions_num; + + /* Transaction */ + transaction = &key->current_transactions[index]; + transaction->cid = cid; + transaction->resp_bcnt = 0; + transaction->resp_size = 0; + + /* Nonce */ + if (nonce != NULL) { + memcpy(transaction->nonce, nonce, NONCE_SIZE); + } +} + +static void u2f_passthru_read(void *opaque); + +static void u2f_transaction_start(U2FPassthruState *key, + const struct packet_init *packet_init) +{ + int64_t time; + + /* Transaction */ + if (packet_init->cid == BROADCAST_CID) { + u2f_transaction_add(key, packet_init->cid, packet_init->data); + } else { + u2f_transaction_add(key, packet_init->cid, NULL); + } + + /* Time */ + time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); + if (key->last_transaction_time == 0) { + qemu_set_fd_handler(key->hidraw_fd, u2f_passthru_read, NULL, key); + timer_init_ms(&key->timer, QEMU_CLOCK_VIRTUAL, u2f_timeout_check, key); + timer_mod(&key->timer, time + TRANSACTION_TIMEOUT / 4); + } + key->last_transaction_time = time; +} + +static void u2f_passthru_recv_from_host(U2FPassthruState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + struct transaction *transaction; + uint32_t cid; + + /* Retrieve transaction */ + cid = packet_get_cid(packet); + if (cid == BROADCAST_CID) { + struct packet_init *packet_init; + if (!packet_is_init(packet)) { + return; + } + packet_init = (struct packet_init *)packet; + transaction = u2f_transaction_get_from_nonce(key, packet_init->data); + } else { + transaction = u2f_transaction_get(key, cid); + } + + /* Ignore no started transaction */ + if (transaction == NULL) { + return; + } + + if (packet_is_init(packet)) { + struct packet_init *packet_init = (struct packet_init *)packet; + transaction->resp_bcnt = packet_init_get_bcnt(packet_init); + transaction->resp_size = PACKET_INIT_DATA_SIZE; + + if (packet_init->cid == BROADCAST_CID) { + /* Nonce checking for legitimate response */ + if (memcmp(transaction->nonce, packet_init->data, NONCE_SIZE) + != 0) { + return; + } + } + } else { + transaction->resp_size += PACKET_CONT_DATA_SIZE; + } + + /* Transaction end check */ + if (transaction->resp_size >= transaction->resp_bcnt) { + u2f_transaction_close(key, cid); + } + u2f_send_to_guest(&key->base, packet); +} + +static void u2f_passthru_read(void *opaque) +{ + U2FPassthruState *key = opaque; + U2FKeyState *base = &key->base; + uint8_t packet[2 * U2FHID_PACKET_SIZE]; + int ret; + + /* Full size base queue check */ + if (base->pending_in_num >= U2FHID_PENDING_IN_NUM) { + return; + } + + ret = read(key->hidraw_fd, packet, sizeof(packet)); + if (ret < 0) { + /* Detach */ + if (base->dev.attached) { + usb_device_detach(&base->dev); + u2f_passthru_reset(key); + } + return; + } + if (ret != U2FHID_PACKET_SIZE) { + return; + } + u2f_passthru_recv_from_host(key, packet); +} + +static void u2f_passthru_recv_from_guest(U2FKeyState *base, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + U2FPassthruState *key = PASSTHRU_U2F_KEY(base); + uint8_t host_packet[U2FHID_PACKET_SIZE + 1]; + ssize_t written; + + if (packet_is_init(packet)) { + u2f_transaction_start(key, (struct packet_init *)packet); + } + + host_packet[0] = 0; + memcpy(host_packet + 1, packet, U2FHID_PACKET_SIZE); + + written = write(key->hidraw_fd, host_packet, sizeof(host_packet)); + if (written != sizeof(host_packet)) { + error_report("%s: Bad written size (req 0x%zu, val 0x%zd)", + TYPE_U2F_PASSTHRU, sizeof(host_packet), written); + } +} + +static bool u2f_passthru_is_u2f_device(int fd) +{ + int ret, rdesc_size; + struct hidraw_report_descriptor rdesc; + const uint8_t u2f_hid_report_desc_header[] = { + 0x06, 0xd0, 0xf1, /* Usage Page (FIDO) */ + 0x09, 0x01, /* Usage (FIDO) */ + }; + + /* Get report descriptor size */ + ret = ioctl(fd, HIDIOCGRDESCSIZE, &rdesc_size); + if (ret < 0 || rdesc_size < sizeof(u2f_hid_report_desc_header)) { + return false; + } + + /* Get report descriptor */ + memset(&rdesc, 0x0, sizeof(rdesc)); + rdesc.size = rdesc_size; + ret = ioctl(fd, HIDIOCGRDESC, &rdesc); + if (ret < 0) { + return false; + } + + /* Header bytes cover specific U2F rdesc values */ + return memcmp(u2f_hid_report_desc_header, rdesc.value, + sizeof(u2f_hid_report_desc_header)) == 0; +} + +#ifdef CONFIG_LIBUDEV +static int u2f_passthru_open_from_device(struct udev_device *device) +{ + const char *devnode = udev_device_get_devnode(device); + + int fd = qemu_open(devnode, O_RDWR); + if (fd < 0) { + return -1; + } else if (!u2f_passthru_is_u2f_device(fd)) { + qemu_close(fd); + return -1; + } + return fd; +} + +static int u2f_passthru_open_from_enumerate(struct udev *udev, + struct udev_enumerate *enumerate) +{ + struct udev_list_entry *devices, *entry; + int ret, fd; + + ret = udev_enumerate_scan_devices(enumerate); + if (ret < 0) { + return -1; + } + + devices = udev_enumerate_get_list_entry(enumerate); + udev_list_entry_foreach(entry, devices) { + struct udev_device *device; + const char *syspath = udev_list_entry_get_name(entry); + + if (syspath == NULL) { + continue; + } + + device = udev_device_new_from_syspath(udev, syspath); + if (device == NULL) { + continue; + } + + fd = u2f_passthru_open_from_device(device); + udev_device_unref(device); + if (fd >= 0) { + return fd; + } + } + return -1; +} + +static int u2f_passthru_open_from_scan(void) +{ + struct udev *udev; + struct udev_enumerate *enumerate; + int ret, fd = -1; + + udev = udev_new(); + if (udev == NULL) { + return -1; + } + + enumerate = udev_enumerate_new(udev); + if (enumerate == NULL) { + udev_unref(udev); + return -1; + } + + ret = udev_enumerate_add_match_subsystem(enumerate, "hidraw"); + if (ret >= 0) { + fd = u2f_passthru_open_from_enumerate(udev, enumerate); + } + + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return fd; +} +#endif + +static void u2f_passthru_unrealize(U2FKeyState *base) +{ + U2FPassthruState *key = PASSTHRU_U2F_KEY(base); + + u2f_passthru_reset(key); + qemu_close(key->hidraw_fd); +} + +static void u2f_passthru_realize(U2FKeyState *base, Error **errp) +{ + U2FPassthruState *key = PASSTHRU_U2F_KEY(base); + int fd; + + if (key->hidraw == NULL) { +#ifdef CONFIG_LIBUDEV + fd = u2f_passthru_open_from_scan(); + if (fd < 0) { + error_setg(errp, "%s: Failed to find a U2F USB device", + TYPE_U2F_PASSTHRU); + return; + } +#else + error_setg(errp, "%s: Missing hidraw", TYPE_U2F_PASSTHRU); + return; +#endif + } else { + fd = qemu_open(key->hidraw, O_RDWR); + if (fd < 0) { + error_setg(errp, "%s: Failed to open %s", TYPE_U2F_PASSTHRU, + key->hidraw); + return; + } + + if (!u2f_passthru_is_u2f_device(fd)) { + qemu_close(fd); + error_setg(errp, "%s: Passed hidraw does not represent " + "a U2F HID device", TYPE_U2F_PASSTHRU); + return; + } + } + key->hidraw_fd = fd; + u2f_passthru_reset(key); +} + +static int u2f_passthru_post_load(void *opaque, int version_id) +{ + U2FPassthruState *key = opaque; + u2f_passthru_reset(key); + return 0; +} + +static const VMStateDescription u2f_passthru_vmstate = { + .name = "u2f-key-passthru", + .version_id = 1, + .minimum_version_id = 1, + .post_load = u2f_passthru_post_load, + .fields = (VMStateField[]) { + VMSTATE_U2F_KEY(base, U2FPassthruState), + VMSTATE_END_OF_LIST() + } +}; + +static Property u2f_passthru_properties[] = { + DEFINE_PROP_STRING("hidraw", U2FPassthruState, hidraw), + DEFINE_PROP_END_OF_LIST(), +}; + +static void u2f_passthru_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + U2FKeyClass *kc = U2F_KEY_CLASS(klass); + + kc->realize = u2f_passthru_realize; + kc->unrealize = u2f_passthru_unrealize; + kc->recv_from_guest = u2f_passthru_recv_from_guest; + dc->desc = "QEMU U2F passthrough key"; + dc->vmsd = &u2f_passthru_vmstate; + device_class_set_props(dc, u2f_passthru_properties); +} + +static const TypeInfo u2f_key_passthru_info = { + .name = TYPE_U2F_PASSTHRU, + .parent = TYPE_U2F_KEY, + .instance_size = sizeof(U2FPassthruState), + .class_init = u2f_passthru_class_init +}; + +static void u2f_key_passthru_register_types(void) +{ + type_register_static(&u2f_key_passthru_info); +} + +type_init(u2f_key_passthru_register_types) diff --git a/hw/usb/u2f.c b/hw/usb/u2f.c new file mode 100644 index 0000000..bc09191 --- /dev/null +++ b/hw/usb/u2f.c @@ -0,0 +1,352 @@ +/* + * U2F USB device. + * + * Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr> + * Written by César Belley <cesar.belley@lse.epita.fr> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "hw/usb.h" +#include "hw/usb/hid.h" +#include "migration/vmstate.h" +#include "desc.h" + +#include "u2f.h" + +/* U2F key Vendor / Product */ +#define U2F_KEY_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */ +#define U2F_KEY_PRODUCT_NUM 0x0005 + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, + STR_CONFIG, + STR_INTERFACE +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU", + [STR_PRODUCT] = "U2F USB key", + [STR_SERIALNUMBER] = "0", + [STR_CONFIG] = "U2F key config", + [STR_INTERFACE] = "U2F key interface" +}; + +static const USBDescIface desc_iface_u2f_key = { + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0x0, + .bInterfaceProtocol = 0x0, + .ndesc = 1, + .descs = (USBDescOther[]) { + { + /* HID descriptor */ + .data = (uint8_t[]) { + 0x09, /* u8 bLength */ + USB_DT_HID, /* u8 bDescriptorType */ + 0x10, 0x01, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + USB_DT_REPORT, /* u8 type: Report */ + 0x22, 0, /* u16 len */ + }, + }, + }, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = U2FHID_PACKET_SIZE, + .bInterval = 0x05, + }, { + .bEndpointAddress = USB_DIR_OUT | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = U2FHID_PACKET_SIZE, + .bInterval = 0x05, + }, + }, + +}; + +static const USBDescDevice desc_device_u2f_key = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = U2FHID_PACKET_SIZE, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG, + .bmAttributes = USB_CFG_ATT_ONE, + .bMaxPower = 15, + .nif = 1, + .ifs = &desc_iface_u2f_key, + }, + }, +}; + +static const USBDesc desc_u2f_key = { + .id = { + .idVendor = U2F_KEY_VENDOR_NUM, + .idProduct = U2F_KEY_PRODUCT_NUM, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_u2f_key, + .str = desc_strings, +}; + +static const uint8_t u2f_key_hid_report_desc[] = { + 0x06, 0xd0, 0xf1, /* Usage Page (FIDO) */ + 0x09, 0x01, /* Usage (FIDO) */ + 0xa1, 0x01, /* Collection (HID Application) */ + 0x09, 0x20, /* Usage (FIDO data in) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xFF, 0x00, /* Logical Maximum (0xff) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (0x40) */ + 0x81, 0x02, /* Input (Data, Variable, Absolute) */ + 0x09, 0x21, /* Usage (FIDO data out) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xFF, 0x00, /* Logical Maximum (0xFF) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (0x40) */ + 0x91, 0x02, /* Output (Data, Variable, Absolute) */ + 0xC0 /* End Collection */ +}; + +static void u2f_key_reset(U2FKeyState *key) +{ + key->pending_in_start = 0; + key->pending_in_end = 0; + key->pending_in_num = 0; +} + +static void u2f_key_handle_reset(USBDevice *dev) +{ + U2FKeyState *key = U2F_KEY(dev); + + u2f_key_reset(key); +} + +static void u2f_key_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + U2FKeyState *key = U2F_KEY(dev); + int ret; + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return; + } + + switch (request) { + case InterfaceRequest | USB_REQ_GET_DESCRIPTOR: + switch (value >> 8) { + case 0x22: + memcpy(data, u2f_key_hid_report_desc, + sizeof(u2f_key_hid_report_desc)); + p->actual_length = sizeof(u2f_key_hid_report_desc); + break; + default: + goto fail; + } + break; + case HID_GET_IDLE: + data[0] = key->idle; + p->actual_length = 1; + break; + case HID_SET_IDLE: + key->idle = (uint8_t)(value >> 8); + break; + default: + fail: + p->status = USB_RET_STALL; + break; + } + +} + +static void u2f_key_recv_from_guest(U2FKeyState *key, USBPacket *p) +{ + U2FKeyClass *kc = U2F_KEY_GET_CLASS(key); + uint8_t packet[U2FHID_PACKET_SIZE]; + + if (kc->recv_from_guest == NULL || p->iov.size != U2FHID_PACKET_SIZE) { + return; + } + + usb_packet_copy(p, packet, p->iov.size); + kc->recv_from_guest(key, packet); +} + +static void u2f_pending_in_add(U2FKeyState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + uint8_t index; + + if (key->pending_in_num >= U2FHID_PENDING_IN_NUM) { + return; + } + + index = key->pending_in_end; + key->pending_in_end = (index + 1) % U2FHID_PENDING_IN_NUM; + ++key->pending_in_num; + + memcpy(key->pending_in[index], packet, U2FHID_PACKET_SIZE); +} + +static uint8_t *u2f_pending_in_get(U2FKeyState *key) +{ + uint8_t index; + + if (key->pending_in_num == 0) { + return NULL; + } + + index = key->pending_in_start; + key->pending_in_start = (index + 1) % U2FHID_PENDING_IN_NUM; + --key->pending_in_num; + + return key->pending_in[index]; +} + +static void u2f_key_handle_data(USBDevice *dev, USBPacket *p) +{ + U2FKeyState *key = U2F_KEY(dev); + uint8_t *packet_in; + + /* Endpoint number check */ + if (p->ep->nr != 1) { + p->status = USB_RET_STALL; + return; + } + + switch (p->pid) { + case USB_TOKEN_OUT: + u2f_key_recv_from_guest(key, p); + break; + case USB_TOKEN_IN: + packet_in = u2f_pending_in_get(key); + if (packet_in == NULL) { + p->status = USB_RET_NAK; + return; + } + usb_packet_copy(p, packet_in, U2FHID_PACKET_SIZE); + break; + default: + p->status = USB_RET_STALL; + break; + } +} + +void u2f_send_to_guest(U2FKeyState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]) +{ + u2f_pending_in_add(key, packet); + usb_wakeup(key->ep, 0); +} + +static void u2f_key_unrealize(USBDevice *dev) +{ + U2FKeyState *key = U2F_KEY(dev); + U2FKeyClass *kc = U2F_KEY_GET_CLASS(key); + + if (kc->unrealize != NULL) { + kc->unrealize(key); + } +} + +static void u2f_key_realize(USBDevice *dev, Error **errp) +{ + U2FKeyState *key = U2F_KEY(dev); + U2FKeyClass *kc = U2F_KEY_GET_CLASS(key); + Error *local_err = NULL; + + usb_desc_create_serial(dev); + usb_desc_init(dev); + u2f_key_reset(key); + + if (kc->realize != NULL) { + kc->realize(key, &local_err); + if (local_err != NULL) { + error_propagate(errp, local_err); + return; + } + } + key->ep = usb_ep_get(dev, USB_TOKEN_IN, 1); +} + +const VMStateDescription vmstate_u2f_key = { + .name = "u2f-key", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_USB_DEVICE(dev, U2FKeyState), + VMSTATE_UINT8(idle, U2FKeyState), + VMSTATE_UINT8_2DARRAY(pending_in, U2FKeyState, + U2FHID_PENDING_IN_NUM, U2FHID_PACKET_SIZE), + VMSTATE_UINT8(pending_in_start, U2FKeyState), + VMSTATE_UINT8(pending_in_end, U2FKeyState), + VMSTATE_UINT8(pending_in_num, U2FKeyState), + VMSTATE_END_OF_LIST() + } +}; + +static void u2f_key_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->product_desc = "QEMU U2F USB key"; + uc->usb_desc = &desc_u2f_key; + uc->handle_reset = u2f_key_handle_reset; + uc->handle_control = u2f_key_handle_control; + uc->handle_data = u2f_key_handle_data; + uc->handle_attach = usb_desc_attach; + uc->realize = u2f_key_realize; + uc->unrealize = u2f_key_unrealize; + dc->desc = "QEMU U2F key"; + dc->vmsd = &vmstate_u2f_key; +} + +static const TypeInfo u2f_key_info = { + .name = TYPE_U2F_KEY, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(U2FKeyState), + .abstract = true, + .class_size = sizeof(U2FKeyClass), + .class_init = u2f_key_class_init, +}; + +static void u2f_key_register_types(void) +{ + type_register_static(&u2f_key_info); + usb_legacy_register(TYPE_U2F_KEY, "u2f-key", NULL); +} + +type_init(u2f_key_register_types) diff --git a/hw/usb/u2f.h b/hw/usb/u2f.h new file mode 100644 index 0000000..db30f35 --- /dev/null +++ b/hw/usb/u2f.h @@ -0,0 +1,92 @@ +/* + * U2F USB device. + * + * Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr> + * Written by César Belley <cesar.belley@lse.epita.fr> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef U2F_H +#define U2F_H + +#include "hw/qdev-core.h" + +#define U2FHID_PACKET_SIZE 64 +#define U2FHID_PENDING_IN_NUM 32 + +typedef struct U2FKeyState U2FKeyState; +typedef struct U2FKeyInfo U2FKeyInfo; + +#define TYPE_U2F_KEY "u2f-key" +#define U2F_KEY(obj) \ + OBJECT_CHECK(U2FKeyState, (obj), TYPE_U2F_KEY) +#define U2F_KEY_CLASS(klass) \ + OBJECT_CLASS_CHECK(U2FKeyClass, (klass), TYPE_U2F_KEY) +#define U2F_KEY_GET_CLASS(obj) \ + OBJECT_GET_CLASS(U2FKeyClass, (obj), TYPE_U2F_KEY) + +/* + * Callbacks to be used by the U2F key base device (i.e. hw/u2f.c) + * to interact with its variants (i.e. hw/u2f-*.c) + */ +typedef struct U2FKeyClass { + /*< private >*/ + USBDeviceClass parent_class; + + /*< public >*/ + void (*recv_from_guest)(U2FKeyState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]); + void (*realize)(U2FKeyState *key, Error **errp); + void (*unrealize)(U2FKeyState *key); +} U2FKeyClass; + +/* + * State of the U2F key base device (i.e. hw/u2f.c) + */ +typedef struct U2FKeyState { + USBDevice dev; + USBEndpoint *ep; + uint8_t idle; + + /* Pending packets to be send to the guest */ + uint8_t pending_in[U2FHID_PENDING_IN_NUM][U2FHID_PACKET_SIZE]; + uint8_t pending_in_start; + uint8_t pending_in_end; + uint8_t pending_in_num; +} U2FKeyState; + +/* + * API to be used by the U2F key device variants (i.e. hw/u2f-*.c) + * to interact with the the U2F key base device (i.e. hw/u2f.c) + */ +void u2f_send_to_guest(U2FKeyState *key, + const uint8_t packet[U2FHID_PACKET_SIZE]); + +extern const VMStateDescription vmstate_u2f_key; + +#define VMSTATE_U2F_KEY(_field, _state) { \ + .name = (stringify(_field)), \ + .size = sizeof(U2FKeyState), \ + .vmsd = &vmstate_u2f_key, \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, U2FKeyState), \ +} + +#endif /* U2F_H */ |