aboutsummaryrefslogtreecommitdiff
path: root/hw/usb
diff options
context:
space:
mode:
Diffstat (limited to 'hw/usb')
-rw-r--r--hw/usb/Kconfig5
-rw-r--r--hw/usb/core.c16
-rw-r--r--hw/usb/dev-hid.c26
-rw-r--r--hw/usb/dev-wacom.c12
-rw-r--r--hw/usb/hcd-ehci.c12
-rw-r--r--hw/usb/hcd-xhci.c5
-rw-r--r--hw/usb/host-libusb.c37
-rw-r--r--hw/usb/meson.build7
-rw-r--r--hw/usb/u2f-emulated.c405
-rw-r--r--hw/usb/u2f-passthru.c551
-rw-r--r--hw/usb/u2f.c352
-rw-r--r--hw/usb/u2f.h92
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 */