This patch adds the U2F key emulated mode. The emulated mode consists of completely emulating the behavior of a U2F device through software part. Libu2f-emu is used for that. The emulated mode is associated with a device inheriting from u2f-key base. To work, an emulated U2F device must have differents elements which can be given in different ways. This is detailed in docs/u2f.txt. The Ephemeral one 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 For more information about libu2f-emu see this page: https://github.com/MattGorko/libu2f-emu. Signed-off-by: César Belley <cesar.belley@lse.epita.fr> Message-id: 20200826114209.28821-7-cesar.belley@lse.epita.fr Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
406 lines
11 KiB
C
406 lines
11 KiB
C
/*
|
|
* 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)
|