mirror of
https://github.com/xemu-project/xemu.git
synced 2024-11-24 12:09:58 +00:00
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>
This commit is contained in:
commit
2f4c51c0f3
8
configure
vendored
8
configure
vendored
@ -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"
|
||||
|
||||
|
@ -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 ===
|
||||
|
@ -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
110
docs/u2f.txt
Normal 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.
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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 */
|
||||
},
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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
405
hw/usb/u2f-emulated.c
Normal 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
551
hw/usb/u2f-passthru.c
Normal 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
352
hw/usb/u2f.c
Normal 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
92
hw/usb/u2f.h
Normal 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
17
include/hw/usb/hid.h
Normal 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
|
@ -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')}
|
||||
|
@ -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
170
scripts/u2f-setup-gen.py
Executable 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()
|
Loading…
Reference in New Issue
Block a user