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.
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2.0.22 (GNU/Linux)
 
 iQIcBAABCgAGBQJfTLWxAAoJEEy22O7T6HE4Y1gP/RkZim1jU2FEGTGBqgWXxjeo
 +0tMAVDLU03T08CewkqHR+gWdU/MHepIRGx6ddXwl47gdMZnA2YJxxeWjXQhIKS2
 FPO2fnKPOFMjk0V3+WwDU+LLy9o/6TiiqoaHYROPpJOxRGJ3pbQbstHRa0pwiWus
 5L7PQAinOfpjM2+nzGoTpe1mH9A8vw6GheZv/xuYhaxMJ7prveJoU0zYaCfdw3Is
 dpUjkWbVv4sOwEfLtzZEpAzU2Yp9XS7/3/KDDbpFKRrqsbpvxpsbD7u8pkllz/H5
 tjiyzVVHL9tXTHCLb7TZGvDRSYKuKANJ6i/xHQ12D2YTxzK5qmKC5clN9DyqIHYl
 sS/eppzCLjO8kUdgFOKvF0zr/XOwLXrWHQlJdSkd520XuHWh4zVh72t6M7A+tsbV
 VB9Auew/37z+TREnUYJyuGjsRCLSNsuL7rV8wujwa2H2VI+aqLqIko661jJWwXGr
 iapSXrZzEHir9rxd7daj/tffvBzmFjMLEZDhQ71mZckImQJ3ct0bZ279cKDKQKr0
 xweNbRG9zXeYwyyhSxEOSwh+h0hSoBfzjrs2v3bk05PgoyN00Ro+76fthE28rwnF
 xQY9OXh4Yc9JabsuPW3Bc8aengZyQGyxI8HLybBEzHY3IZWZcERO1gnCrcUhvQz3
 SNnJG7sb+bZh5/BNEJbr
 =uVNh
 -----END PGP SIGNATURE-----

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>
master
Peter Maydell 2020-08-31 19:39:12 +01:00
commit 2f4c51c0f3
20 changed files with 1797 additions and 40 deletions

8
configure vendored
View File

@ -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"

View File

@ -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 ===

View File

@ -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

110
docs/u2f.txt Normal file
View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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) {

View File

@ -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 */
},
},

View File

@ -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 {

View File

@ -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;

View File

@ -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) {

View File

@ -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()

405
hw/usb/u2f-emulated.c Normal file
View File

@ -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)

551
hw/usb/u2f-passthru.c Normal file
View File

@ -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)

352
hw/usb/u2f.c Normal file
View File

@ -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)

92
hw/usb/u2f.h Normal file
View File

@ -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 */

17
include/hw/usb/hid.h Normal file
View File

@ -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

View File

@ -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')}

View File

@ -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')

170
scripts/u2f-setup-gen.py Executable file
View File

@ -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()