123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- /*
- * 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_old(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_old(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 manually 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)
|