aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2020-08-31 19:39:12 +0100
committerPeter Maydell <peter.maydell@linaro.org>2020-08-31 19:39:13 +0100
commit2f4c51c0f384d7888a04b4815861e6d5fd244d75 (patch)
treed01ddd9e8821d9ab4f8a99c1e3b0fc3b92ff9691
parent4bc08c61416cf9244175807b8752caf739f7681f (diff)
parentb946434f2659a182afc17e155be6791ebfb302eb (diff)
downloadqemu-2f4c51c0f384d7888a04b4815861e6d5fd244d75.zip
qemu-2f4c51c0f384d7888a04b4815861e6d5fd244d75.tar.gz
qemu-2f4c51c0f384d7888a04b4815861e6d5fd244d75.tar.bz2
Merge remote-tracking branch 'remotes/kraxel/tags/usb-20200831-pull-request' into staging
usb: usb_packet_map fixes for ehci and xhci. usb: setup_len fix (CVE-2020-14364). usb: u2f key support (GSoC). * v2: 32bit build fixed. * v3: libu2f-emu dependency fixed. # gpg: Signature made Mon 31 Aug 2020 09:32:49 BST # gpg: using RSA key 4CB6D8EED3E87138 # gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [full] # gpg: aka "Gerd Hoffmann <gerd@kraxel.org>" [full] # gpg: aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [full] # Primary key fingerprint: A032 8CFF B93A 17A7 9901 FE7D 4CB6 D8EE D3E8 7138 * remotes/kraxel/tags/usb-20200831-pull-request: usb: fix setup_len init (CVE-2020-14364) usb-host: workaround libusb bug hw/usb: Add U2F device autoscan to passthru mode hw/usb: Add U2F device check to passthru mode scripts: Add u2f-setup-gen script docs/qdev-device-use.txt: Add USB U2F key to the QDEV devices examples docs/system: Add U2F key to the USB devices examples meson: Add U2F key to meson hw/usb: Add U2F key emulated mode hw/usb: Add U2F key passthru mode hw/usb: Add U2F key base class implementation hw/usb: Add U2F key base class docs: Add USB U2F key device documentation hw/usb: Regroup USB HID protocol values ehci: drop pointless warn_report for guest bugs. hw: ehci: check return value of 'usb_packet_map' hw: ehci: destroy sglist in error path hw: xhci: check return value of 'usb_packet_map' Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rwxr-xr-xconfigure8
-rw-r--r--docs/qdev-device-use.txt1
-rw-r--r--docs/system/usb.rst3
-rw-r--r--docs/u2f.txt110
-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
-rw-r--r--include/hw/usb/hid.h17
-rw-r--r--meson.build7
-rw-r--r--meson_options.txt1
-rwxr-xr-xscripts/u2f-setup-gen.py170
20 files changed, 1797 insertions, 40 deletions
diff --git a/configure b/configure
index 6ecaff4..8dc9816 100755
--- a/configure
+++ b/configure
@@ -495,6 +495,7 @@ trace_file="trace"
spice=""
rbd=""
smartcard=""
+u2f="auto"
libusb=""
usb_redir=""
opengl=""
@@ -1415,6 +1416,10 @@ for opt do
;;
--enable-smartcard) smartcard="yes"
;;
+ --disable-u2f) u2f="disabled"
+ ;;
+ --enable-u2f) u2f="enabled"
+ ;;
--disable-libusb) libusb="no"
;;
--enable-libusb) libusb="yes"
@@ -1945,6 +1950,7 @@ disabled with --disable-FEATURE, default is enabled if available:
libiscsi iscsi support
libnfs nfs support
smartcard smartcard support (libcacard)
+ u2f U2F support (u2f-emu)
libusb libusb (for usb passthrough)
live-block-migration Block migration in the main migration stream
usb-redir usb network redirection support
@@ -8229,7 +8235,7 @@ NINJA=${ninja:-$PWD/ninjatool} $meson setup \
-Db_coverage=$(if test "$gcov" = yes; then echo true; else echo false; fi) \
-Dsdl=$sdl -Dsdl_image=$sdl_image \
-Dvnc=$vnc -Dvnc_sasl=$vnc_sasl -Dvnc_jpeg=$vnc_jpeg -Dvnc_png=$vnc_png \
- -Dgettext=$gettext -Dxkbcommon=$xkbcommon \
+ -Dgettext=$gettext -Dxkbcommon=$xkbcommon -Du2f=$u2f\
$cross_arg \
"$PWD" "$source_path"
diff --git a/docs/qdev-device-use.txt b/docs/qdev-device-use.txt
index f8d0d2f..9889521 100644
--- a/docs/qdev-device-use.txt
+++ b/docs/qdev-device-use.txt
@@ -325,6 +325,7 @@ The new way is -device DEVNAME,DEV-OPTS... Details depend on DRIVER:
* mouse -device usb-mouse
* tablet -device usb-tablet
* wacom-tablet -device usb-wacom-tablet
+* u2f -device u2f-{emulated,passthru}
* braille See "Character Devices"
=== Watchdog Devices ===
diff --git a/docs/system/usb.rst b/docs/system/usb.rst
index ddfa828..9a2f192 100644
--- a/docs/system/usb.rst
+++ b/docs/system/usb.rst
@@ -81,6 +81,9 @@ option or the ``device_add`` monitor command. Available devices are:
``usb-audio``
USB audio device
+``u2f-{emulated,passthru}``
+ Universal Second Factor device
+
.. _host_005fusb_005fdevices:
Using host USB devices on a Linux host
diff --git a/docs/u2f.txt b/docs/u2f.txt
new file mode 100644
index 0000000..8f44994
--- /dev/null
+++ b/docs/u2f.txt
@@ -0,0 +1,110 @@
+QEMU U2F Key Device Documentation.
+
+Contents
+1. USB U2F key device
+2. Building
+3. Using u2f-emulated
+4. Using u2f-passthru
+5. Libu2f-emu
+
+1. USB U2F key device
+
+U2F is an open authentication standard that enables relying parties
+exposed to the internet to offer a strong second factor option for end
+user authentication.
+
+The standard brings many advantages to both parties, client and server,
+allowing to reduce over-reliance on passwords, it increases authentication
+security and simplifies passwords.
+
+The second factor is materialized by a device implementing the U2F
+protocol. In case of a USB U2F security key, it is a USB HID device
+that implements the U2F protocol.
+
+In Qemu, the USB U2F key device offers a dedicated support of U2F, allowing
+guest USB FIDO/U2F security keys operating in two possible modes:
+pass-through and emulated.
+
+The pass-through mode consists of passing all requests made from the guest
+to the physical security key connected to the host machine and vice versa.
+In addition, the dedicated pass-through allows to have a U2F security key
+shared on several guests which is not possible with a simple host device
+assignment pass-through.
+
+The emulated mode consists of completely emulating the behavior of an
+U2F device through software part. Libu2f-emu is used for that.
+
+
+2. Building
+
+To ensure the build of the u2f-emulated device variant which depends
+on libu2f-emu: configuring and building:
+
+ ./configure --enable-u2f && make
+
+The pass-through mode is built by default on Linux. To take advantage
+of the autoscan option it provides, make sure you have a working libudev
+installed on the host.
+
+
+3. Using u2f-emulated
+
+To work, an emulated U2F device must have four elements:
+ * ec x509 certificate
+ * ec private key
+ * counter (four bytes value)
+ * 48 bytes of entropy (random bits)
+
+To use this type of device, this one has to be configured, and these
+four elements must be passed one way or another.
+
+Assuming that you have a working libu2f-emu installed on the host.
+There are three possible ways of configurations:
+ * ephemeral
+ * setup directory
+ * manual
+
+Ephemeral is the simplest way to configure, it lets the device generate
+all the elements it needs for a single use of the lifetime of the device.
+
+ qemu -usb -device u2f-emulated
+
+Setup directory allows to configure the device from a directory containing
+four files:
+ * certificate.pem: ec x509 certificate
+ * private-key.pem: ec private key
+ * counter: counter value
+ * entropy: 48 bytes of entropy
+
+ qemu -usb -device u2f-emulated,dir=$dir
+
+Manual allows to configure the device more finely by specifying each
+of the elements necessary for the device:
+ * cert
+ * priv
+ * counter
+ * entropy
+
+ qemu -usb -device u2f-emulated,cert=$DIR1/$FILE1,priv=$DIR2/$FILE2,counter=$DIR3/$FILE3,entropy=$DIR4/$FILE4
+
+
+4. Using u2f-passthru
+
+On the host specify the u2f-passthru device with a suitable hidraw:
+
+ qemu -usb -device u2f-passthru,hidraw=/dev/hidraw0
+
+Alternately, the u2f-passthru device can autoscan to take the first
+U2F device it finds on the host (this requires a working libudev):
+
+ qemu -usb -device u2f-passthru
+
+
+5. Libu2f-emu
+
+The u2f-emulated device uses libu2f-emu for the U2F key emulation. Libu2f-emu
+implements completely the U2F protocol device part for all specified
+transport given by the FIDO Alliance.
+
+For more information about libu2f-emu see this page:
+https://github.com/MattGorko/libu2f-emu.
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 */
diff --git a/include/hw/usb/hid.h b/include/hw/usb/hid.h
new file mode 100644
index 0000000..1c14258
--- /dev/null
+++ b/include/hw/usb/hid.h
@@ -0,0 +1,17 @@
+#ifndef HW_USB_HID_H
+#define HW_USB_HID_H
+
+/* HID interface requests */
+#define HID_GET_REPORT 0xa101
+#define HID_GET_IDLE 0xa102
+#define HID_GET_PROTOCOL 0xa103
+#define HID_SET_REPORT 0x2109
+#define HID_SET_IDLE 0x210a
+#define HID_SET_PROTOCOL 0x210b
+
+/* HID descriptor types */
+#define USB_DT_HID 0x21
+#define USB_DT_REPORT 0x22
+#define USB_DT_PHY 0x23
+
+#endif
diff --git a/meson.build b/meson.build
index 74f8ea0..1e7aee8 100644
--- a/meson.build
+++ b/meson.build
@@ -377,6 +377,12 @@ if 'CONFIG_SMARTCARD' in config_host
cacard = declare_dependency(compile_args: config_host['SMARTCARD_CFLAGS'].split(),
link_args: config_host['SMARTCARD_LIBS'].split())
endif
+u2f = not_found
+if have_system
+ u2f = dependency('u2f-emu', required: get_option('u2f'),
+ method: 'pkg-config',
+ static: enable_static)
+endif
usbredir = not_found
if 'CONFIG_USB_REDIR' in config_host
usbredir = declare_dependency(compile_args: config_host['USB_REDIR_CFLAGS'].split(),
@@ -1375,6 +1381,7 @@ summary_info += {'spice support': config_host.has_key('CONFIG_SPICE')}
summary_info += {'rbd support': config_host.has_key('CONFIG_RBD')}
summary_info += {'xfsctl support': config_host.has_key('CONFIG_XFS')}
summary_info += {'smartcard support': config_host.has_key('CONFIG_SMARTCARD')}
+summary_info += {'U2F support': u2f.found()}
summary_info += {'libusb': config_host.has_key('CONFIG_USB_LIBUSB')}
summary_info += {'usb net redir': config_host.has_key('CONFIG_USB_REDIR')}
summary_info += {'OpenGL support': config_host.has_key('CONFIG_OPENGL')}
diff --git a/meson_options.txt b/meson_options.txt
index c55f9cd..aef2de6 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,6 +1,7 @@
option('gettext', type : 'boolean', value : true)
option('sdl', type : 'feature', value : 'auto')
option('sdl_image', type : 'feature', value : 'auto')
+option('u2f', type : 'feature', value : 'auto')
option('vnc', type : 'feature', value : 'enabled')
option('vnc_jpeg', type : 'feature', value : 'auto')
option('vnc_png', type : 'feature', value : 'auto')
diff --git a/scripts/u2f-setup-gen.py b/scripts/u2f-setup-gen.py
new file mode 100755
index 0000000..2122598
--- /dev/null
+++ b/scripts/u2f-setup-gen.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+#
+# Libu2f-emu setup directory generator for USB U2F key emulation.
+#
+# Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr>
+# Written by César Belley <cesar.belley@lse.epita.fr>
+#
+# This work is licensed under the terms of the GNU GPL, version 2
+# or, at your option, any later version. See the COPYING file in
+# the top-level directory.
+
+import sys
+import os
+from random import randint
+from typing import Tuple
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric import ec
+from cryptography.hazmat.primitives.serialization import Encoding, \
+ NoEncryption, PrivateFormat, PublicFormat
+from OpenSSL import crypto
+
+
+def write_setup_dir(dirpath: str, privkey_pem: bytes, cert_pem: bytes,
+ entropy: bytes, counter: int) -> None:
+ """
+ Write the setup directory.
+
+ Args:
+ dirpath: The directory path.
+ key_pem: The private key PEM.
+ cert_pem: The certificate PEM.
+ entropy: The 48 bytes of entropy.
+ counter: The counter value.
+ """
+ # Directory
+ os.mkdir(dirpath)
+
+ # Private key
+ with open(f'{dirpath}/private-key.pem', 'bw') as f:
+ f.write(privkey_pem)
+
+ # Certificate
+ with open(f'{dirpath}/certificate.pem', 'bw') as f:
+ f.write(cert_pem)
+
+ # Entropy
+ with open(f'{dirpath}/entropy', 'wb') as f:
+ f.write(entropy)
+
+ # Counter
+ with open(f'{dirpath}/counter', 'w') as f:
+ f.write(f'{str(counter)}\n')
+
+
+def generate_ec_key_pair() -> Tuple[str, str]:
+ """
+ Generate an ec key pair.
+
+ Returns:
+ The private and public key PEM.
+ """
+ # Key generation
+ privkey = ec.generate_private_key(ec.SECP256R1, default_backend())
+ pubkey = privkey.public_key()
+
+ # PEM serialization
+ privkey_pem = privkey.private_bytes(encoding=Encoding.PEM,
+ format=PrivateFormat.TraditionalOpenSSL,
+ encryption_algorithm=NoEncryption())
+ pubkey_pem = pubkey.public_bytes(encoding=Encoding.PEM,
+ format=PublicFormat.SubjectPublicKeyInfo)
+ return privkey_pem, pubkey_pem
+
+
+def generate_certificate(privkey_pem: str, pubkey_pem: str) -> str:
+ """
+ Generate a x509 certificate from a key pair.
+
+ Args:
+ privkey_pem: The private key PEM.
+ pubkey_pem: The public key PEM.
+
+ Returns:
+ The certificate PEM.
+ """
+ # Convert key pair
+ privkey = crypto.load_privatekey(crypto.FILETYPE_PEM, privkey_pem)
+ pubkey = crypto.load_publickey(crypto.FILETYPE_PEM, pubkey_pem)
+
+ # New x509v3 certificate
+ cert = crypto.X509()
+ cert.set_version(0x2)
+
+ # Serial number
+ cert.set_serial_number(randint(1, 2 ** 64))
+
+ # Before / After
+ cert.gmtime_adj_notBefore(0)
+ cert.gmtime_adj_notAfter(4 * (365 * 24 * 60 * 60))
+
+ # Public key
+ cert.set_pubkey(pubkey)
+
+ # Subject name and issueer
+ cert.get_subject().CN = "U2F emulated"
+ cert.set_issuer(cert.get_subject())
+
+ # Extensions
+ cert.add_extensions([
+ crypto.X509Extension(b"subjectKeyIdentifier",
+ False, b"hash", subject=cert),
+ ])
+ cert.add_extensions([
+ crypto.X509Extension(b"authorityKeyIdentifier",
+ False, b"keyid:always", issuer=cert),
+ ])
+ cert.add_extensions([
+ crypto.X509Extension(b"basicConstraints", True, b"CA:TRUE")
+ ])
+
+ # Signature
+ cert.sign(privkey, 'sha256')
+
+ return crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
+
+
+def generate_setup_dir(dirpath: str) -> None:
+ """
+ Generates the setup directory.
+
+ Args:
+ dirpath: The directory path.
+ """
+ # Key pair
+ privkey_pem, pubkey_pem = generate_ec_key_pair()
+
+ # Certificate
+ certificate_pem = generate_certificate(privkey_pem, pubkey_pem)
+
+ # Entropy
+ entropy = os.urandom(48)
+
+ # Counter
+ counter = 0
+
+ # Write
+ write_setup_dir(dirpath, privkey_pem, certificate_pem, entropy, counter)
+
+
+def main() -> None:
+ """
+ Main function
+ """
+ # Dir path
+ if len(sys.argv) != 2:
+ sys.stderr.write(f'Usage: {sys.argv[0]} <setup_dir>\n')
+ exit(2)
+ dirpath = sys.argv[1]
+
+ # Dir non existence
+ if os.path.exists(dirpath):
+ sys.stderr.write(f'Directory: {dirpath} already exists.\n')
+ exit(1)
+
+ generate_setup_dir(dirpath)
+
+
+if __name__ == '__main__':
+ main()