All of lore.kernel.org
 help / color / mirror / Atom feed
From: Gerd Hoffmann <kraxel@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Daniel P. Berrangé" <berrange@redhat.com>,
	"César Belley" <cesar.belley@lse.epita.fr>,
	"Gerd Hoffmann" <kraxel@redhat.com>,
	"Cleber Rosa" <crosa@redhat.com>,
	"Paolo Bonzini" <pbonzini@redhat.com>,
	"Eduardo Habkost" <ehabkost@redhat.com>
Subject: [PULL 10/18] hw/usb: Add U2F key emulated mode
Date: Wed, 26 Aug 2020 16:52:31 +0200	[thread overview]
Message-ID: <20200826145239.6077-11-kraxel@redhat.com> (raw)
In-Reply-To: <20200826145239.6077-1-kraxel@redhat.com>

From: César Belley <cesar.belley@lse.epita.fr>

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>
---
 hw/usb/u2f-emulated.c | 405 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 405 insertions(+)
 create mode 100644 hw/usb/u2f-emulated.c

diff --git a/hw/usb/u2f-emulated.c b/hw/usb/u2f-emulated.c
new file mode 100644
index 000000000000..9e1b829f3d32
--- /dev/null
+++ b/hw/usb/u2f-emulated.c
@@ -0,0 +1,405 @@
+/*
+ * U2F USB Emulated device.
+ *
+ * Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr>
+ * Written by César Belley <cesar.belley@lse.epita.fr>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/thread.h"
+#include "qemu/main-loop.h"
+#include "qapi/error.h"
+#include "hw/usb.h"
+#include "hw/qdev-properties.h"
+
+#include <u2f-emu/u2f-emu.h>
+
+#include "u2f.h"
+
+/* Counter which sync with a file */
+struct synced_counter {
+    /* Emulated device counter */
+    struct u2f_emu_vdev_counter vdev_counter;
+
+    /* Private attributes */
+    uint32_t value;
+    FILE *fp;
+};
+
+static void counter_increment(struct u2f_emu_vdev_counter *vdev_counter)
+{
+    struct synced_counter *counter = (struct synced_counter *)vdev_counter;
+    ++counter->value;
+
+    /* Write back */
+    if (fseek(counter->fp, 0, SEEK_SET) == -1) {
+        return;
+    }
+    fprintf(counter->fp, "%u\n", counter->value);
+}
+
+static uint32_t counter_read(struct u2f_emu_vdev_counter *vdev_counter)
+{
+    struct synced_counter *counter = (struct synced_counter *)vdev_counter;
+    return counter->value;
+}
+
+typedef struct U2FEmulatedState U2FEmulatedState;
+
+#define PENDING_OUT_NUM 32
+
+struct U2FEmulatedState {
+    U2FKeyState base;
+
+    /* U2F virtual emulated device */
+    u2f_emu_vdev *vdev;
+    QemuMutex vdev_mutex;
+
+    /* Properties */
+    char *dir;
+    char *cert;
+    char *privkey;
+    char *entropy;
+    char *counter;
+    struct synced_counter synced_counter;
+
+    /* Pending packets received from the guest */
+    uint8_t pending_out[PENDING_OUT_NUM][U2FHID_PACKET_SIZE];
+    uint8_t pending_out_start;
+    uint8_t pending_out_end;
+    uint8_t pending_out_num;
+    QemuMutex pending_out_mutex;
+
+    /* Emulation thread and sync */
+    QemuCond key_cond;
+    QemuMutex key_mutex;
+    QemuThread key_thread;
+    bool stop_thread;
+    EventNotifier notifier;
+};
+
+#define TYPE_U2F_EMULATED "u2f-emulated"
+#define EMULATED_U2F_KEY(obj) \
+    OBJECT_CHECK(U2FEmulatedState, (obj), TYPE_U2F_EMULATED)
+
+static void u2f_emulated_reset(U2FEmulatedState *key)
+{
+    key->pending_out_start = 0;
+    key->pending_out_end = 0;
+    key->pending_out_num = 0;
+}
+
+static void u2f_pending_out_add(U2FEmulatedState *key,
+                                const uint8_t packet[U2FHID_PACKET_SIZE])
+{
+    int index;
+
+    if (key->pending_out_num >= PENDING_OUT_NUM) {
+        return;
+    }
+
+    index = key->pending_out_end;
+    key->pending_out_end = (index + 1) % PENDING_OUT_NUM;
+    ++key->pending_out_num;
+
+    memcpy(&key->pending_out[index], packet, U2FHID_PACKET_SIZE);
+}
+
+static uint8_t *u2f_pending_out_get(U2FEmulatedState *key)
+{
+    int index;
+
+    if (key->pending_out_num == 0) {
+        return NULL;
+    }
+
+    index  = key->pending_out_start;
+    key->pending_out_start = (index + 1) % PENDING_OUT_NUM;
+    --key->pending_out_num;
+
+    return key->pending_out[index];
+}
+
+static void u2f_emulated_recv_from_guest(U2FKeyState *base,
+                                    const uint8_t packet[U2FHID_PACKET_SIZE])
+{
+    U2FEmulatedState *key = EMULATED_U2F_KEY(base);
+
+    qemu_mutex_lock(&key->pending_out_mutex);
+    u2f_pending_out_add(key, packet);
+    qemu_mutex_unlock(&key->pending_out_mutex);
+
+    qemu_mutex_lock(&key->key_mutex);
+    qemu_cond_signal(&key->key_cond);
+    qemu_mutex_unlock(&key->key_mutex);
+}
+
+static void *u2f_emulated_thread(void* arg)
+{
+    U2FEmulatedState *key = arg;
+    uint8_t packet[U2FHID_PACKET_SIZE];
+    uint8_t *packet_out = NULL;
+
+
+    while (true) {
+        /* Wait signal */
+        qemu_mutex_lock(&key->key_mutex);
+        qemu_cond_wait(&key->key_cond, &key->key_mutex);
+        qemu_mutex_unlock(&key->key_mutex);
+
+        /* Exit thread check */
+        if (key->stop_thread) {
+            key->stop_thread = false;
+            break;
+        }
+
+        qemu_mutex_lock(&key->pending_out_mutex);
+        packet_out = u2f_pending_out_get(key);
+        if (packet_out == NULL) {
+            qemu_mutex_unlock(&key->pending_out_mutex);
+            continue;
+        }
+        memcpy(packet, packet_out, U2FHID_PACKET_SIZE);
+        qemu_mutex_unlock(&key->pending_out_mutex);
+
+        qemu_mutex_lock(&key->vdev_mutex);
+        u2f_emu_vdev_send(key->vdev, U2F_EMU_USB, packet,
+                          U2FHID_PACKET_SIZE);
+
+        /* Notify response */
+        if (u2f_emu_vdev_has_response(key->vdev, U2F_EMU_USB)) {
+            event_notifier_set(&key->notifier);
+        }
+        qemu_mutex_unlock(&key->vdev_mutex);
+    }
+    return NULL;
+}
+
+static ssize_t u2f_emulated_read(const char *path, char *buffer,
+                                 size_t buffer_len)
+{
+    int fd;
+    ssize_t ret;
+
+    fd = qemu_open(path, O_RDONLY);
+    if (fd < 0) {
+        return -1;
+    }
+
+    ret = read(fd, buffer, buffer_len);
+    close(fd);
+
+    return ret;
+}
+
+static bool u2f_emulated_setup_counter(const char *path,
+                                       struct synced_counter *counter)
+{
+    int fd, ret;
+    FILE *fp;
+
+    fd = qemu_open(path, O_RDWR);
+    if (fd < 0) {
+        return false;
+    }
+    fp = fdopen(fd, "r+");
+    if (fp == NULL) {
+        close(fd);
+        return false;
+    }
+    ret = fscanf(fp, "%u", &counter->value);
+    if (ret == EOF) {
+        fclose(fp);
+        return false;
+    }
+    counter->fp = fp;
+    counter->vdev_counter.counter_increment = counter_increment;
+    counter->vdev_counter.counter_read = counter_read;
+
+    return true;
+}
+
+static u2f_emu_rc u2f_emulated_setup_vdev_manualy(U2FEmulatedState *key)
+{
+    ssize_t ret;
+    char cert_pem[4096], privkey_pem[2048];
+    struct u2f_emu_vdev_setup setup_info;
+
+    /* Certificate */
+    ret = u2f_emulated_read(key->cert, cert_pem, sizeof(cert_pem));
+    if (ret < 0) {
+        return -1;
+    }
+
+    /* Private key */
+    ret = u2f_emulated_read(key->privkey, privkey_pem, sizeof(privkey_pem));
+    if (ret < 0) {
+        return -1;
+    }
+
+    /* Entropy */
+    ret = u2f_emulated_read(key->entropy, (char *)&setup_info.entropy,
+                            sizeof(setup_info.entropy));
+    if (ret < 0) {
+        return -1;
+    }
+
+    /* Counter */
+    if (!u2f_emulated_setup_counter(key->counter, &key->synced_counter)) {
+        return -1;
+    }
+
+    /* Setup */
+    setup_info.certificate = cert_pem;
+    setup_info.private_key = privkey_pem;
+    setup_info.counter = (struct u2f_emu_vdev_counter *)&key->synced_counter;
+
+    return u2f_emu_vdev_new(&key->vdev, &setup_info);
+}
+
+static void u2f_emulated_event_handler(EventNotifier *notifier)
+{
+    U2FEmulatedState *key = container_of(notifier, U2FEmulatedState, notifier);
+    size_t packet_size;
+    uint8_t *packet_in = NULL;
+
+    event_notifier_test_and_clear(&key->notifier);
+    qemu_mutex_lock(&key->vdev_mutex);
+    while (u2f_emu_vdev_has_response(key->vdev, U2F_EMU_USB)) {
+        packet_size = u2f_emu_vdev_get_response(key->vdev, U2F_EMU_USB,
+                                                &packet_in);
+        if (packet_size == U2FHID_PACKET_SIZE) {
+            u2f_send_to_guest(&key->base, packet_in);
+        }
+        u2f_emu_vdev_free_response(packet_in);
+    }
+    qemu_mutex_unlock(&key->vdev_mutex);
+}
+
+static void u2f_emulated_realize(U2FKeyState *base, Error **errp)
+{
+    U2FEmulatedState *key = EMULATED_U2F_KEY(base);
+    u2f_emu_rc rc;
+
+    if (key->cert != NULL || key->privkey != NULL || key->entropy != NULL
+        || key->counter != NULL) {
+        if (key->cert != NULL && key->privkey != NULL
+            && key->entropy != NULL && key->counter != NULL) {
+            rc = u2f_emulated_setup_vdev_manualy(key);
+        } else {
+            error_setg(errp, "%s: cert, priv, entropy and counter "
+                       "parameters must be provided to manualy configure "
+                       "the emulated device", TYPE_U2F_EMULATED);
+            return;
+        }
+    } else if (key->dir != NULL) {
+        rc = u2f_emu_vdev_new_from_dir(&key->vdev, key->dir);
+    } else {
+        rc = u2f_emu_vdev_new_ephemeral(&key->vdev);
+    }
+
+    if (rc != U2F_EMU_OK) {
+        error_setg(errp, "%s: Failed to setup the key", TYPE_U2F_EMULATED);
+        return;
+    }
+
+    if (event_notifier_init(&key->notifier, false) < 0) {
+        error_setg(errp, "%s: Failed to initialize notifier",
+                   TYPE_U2F_EMULATED);
+        return;
+    }
+    /* Notifier */
+    event_notifier_set_handler(&key->notifier, u2f_emulated_event_handler);
+
+    /* Synchronization */
+    qemu_cond_init(&key->key_cond);
+    qemu_mutex_init(&key->vdev_mutex);
+    qemu_mutex_init(&key->pending_out_mutex);
+    qemu_mutex_init(&key->key_mutex);
+    u2f_emulated_reset(key);
+
+    /* Thread */
+    key->stop_thread = false;
+    qemu_thread_create(&key->key_thread, "u2f-key", u2f_emulated_thread,
+                       key, QEMU_THREAD_JOINABLE);
+}
+
+static void u2f_emulated_unrealize(U2FKeyState *base)
+{
+    U2FEmulatedState *key = EMULATED_U2F_KEY(base);
+
+    /* Thread */
+    key->stop_thread = true;
+    qemu_cond_signal(&key->key_cond);
+    qemu_thread_join(&key->key_thread);
+
+    /* Notifier */
+    event_notifier_set_handler(&key->notifier, NULL);
+    event_notifier_cleanup(&key->notifier);
+
+    /* Synchronization */
+    qemu_cond_destroy(&key->key_cond);
+    qemu_mutex_destroy(&key->vdev_mutex);
+    qemu_mutex_destroy(&key->key_mutex);
+    qemu_mutex_destroy(&key->pending_out_mutex);
+
+    /* Vdev */
+    u2f_emu_vdev_free(key->vdev);
+    if (key->synced_counter.fp != NULL) {
+        fclose(key->synced_counter.fp);
+    }
+}
+
+static Property u2f_emulated_properties[] = {
+    DEFINE_PROP_STRING("dir", U2FEmulatedState, dir),
+    DEFINE_PROP_STRING("cert", U2FEmulatedState, cert),
+    DEFINE_PROP_STRING("privkey", U2FEmulatedState, privkey),
+    DEFINE_PROP_STRING("entropy", U2FEmulatedState, entropy),
+    DEFINE_PROP_STRING("counter", U2FEmulatedState, counter),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void u2f_emulated_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    U2FKeyClass *kc = U2F_KEY_CLASS(klass);
+
+    kc->realize = u2f_emulated_realize;
+    kc->unrealize = u2f_emulated_unrealize;
+    kc->recv_from_guest = u2f_emulated_recv_from_guest;
+    dc->desc = "QEMU U2F emulated key";
+    device_class_set_props(dc, u2f_emulated_properties);
+}
+
+static const TypeInfo u2f_key_emulated_info = {
+    .name = TYPE_U2F_EMULATED,
+    .parent = TYPE_U2F_KEY,
+    .instance_size = sizeof(U2FEmulatedState),
+    .class_init = u2f_emulated_class_init
+};
+
+static void u2f_key_emulated_register_types(void)
+{
+    type_register_static(&u2f_key_emulated_info);
+}
+
+type_init(u2f_key_emulated_register_types)
-- 
2.27.0



  parent reply	other threads:[~2020-08-26 15:06 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-08-26 14:52 [PULL 00/18] Usb 20200826 patches Gerd Hoffmann
2020-08-26 14:52 ` [PULL 01/18] hw: xhci: check return value of 'usb_packet_map' Gerd Hoffmann
2020-08-26 14:52 ` [PULL 02/18] hw: ehci: destroy sglist in error path Gerd Hoffmann
2020-08-26 14:52 ` [PULL 03/18] hw: ehci: check return value of 'usb_packet_map' Gerd Hoffmann
2020-08-26 14:52 ` [PULL 04/18] ehci: drop pointless warn_report for guest bugs Gerd Hoffmann
2020-08-26 14:52 ` [PULL 05/18] hw/usb: Regroup USB HID protocol values Gerd Hoffmann
2020-08-26 14:52 ` [PULL 06/18] docs: Add USB U2F key device documentation Gerd Hoffmann
2020-08-26 14:52 ` [PULL 07/18] hw/usb: Add U2F key base class Gerd Hoffmann
2020-08-26 14:52 ` [PULL 08/18] hw/usb: Add U2F key base class implementation Gerd Hoffmann
2020-08-26 14:52 ` [PULL 09/18] hw/usb: Add U2F key passthru mode Gerd Hoffmann
2020-08-26 14:52 ` Gerd Hoffmann [this message]
2020-08-26 14:52 ` [PULL 11/18] meson: Add U2F key to meson Gerd Hoffmann
2020-08-26 14:52 ` [PULL 12/18] docs/system: Add U2F key to the USB devices examples Gerd Hoffmann
2020-08-26 14:52 ` [PULL 13/18] docs/qdev-device-use.txt: Add USB U2F key to the QDEV " Gerd Hoffmann
2020-08-26 14:52 ` [PULL 14/18] scripts: Add u2f-setup-gen script Gerd Hoffmann
2020-08-26 14:52 ` [PULL 15/18] hw/usb: Add U2F device check to passthru mode Gerd Hoffmann
2020-08-26 14:52 ` [PULL 16/18] hw/usb: Add U2F device autoscan " Gerd Hoffmann
2020-08-26 14:52 ` [PULL 17/18] usb-host: workaround libusb bug Gerd Hoffmann
2020-08-26 14:52 ` [PULL 18/18] usb: fix setup_len init (CVE-2020-14364) Gerd Hoffmann
2020-08-26 16:05 ` [PULL 00/18] Usb 20200826 patches Peter Maydell
2020-08-28  8:08 [PULL 00/18] Usb 20200828 patches Gerd Hoffmann
2020-08-28  8:08 ` [PULL 10/18] hw/usb: Add U2F key emulated mode Gerd Hoffmann
2020-08-31  8:32 [PULL 00/18] Usb 20200831 patches Gerd Hoffmann
2020-08-31  8:32 ` [PULL 10/18] hw/usb: Add U2F key emulated mode Gerd Hoffmann

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200826145239.6077-11-kraxel@redhat.com \
    --to=kraxel@redhat.com \
    --cc=berrange@redhat.com \
    --cc=cesar.belley@lse.epita.fr \
    --cc=crosa@redhat.com \
    --cc=ehabkost@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.