xemu/hw/usb/ccid-card-emulated.c
Paolo Bonzini b58deb344d qemu/queue.h: leave head structs anonymous unless necessary
Most list head structs need not be given a name.  In most cases the
name is given just in case one is going to use QTAILQ_LAST, QTAILQ_PREV
or reverse iteration, but this does not apply to lists of other kinds,
and even for QTAILQ in practice this is only rarely needed.  In addition,
we will soon reimplement those macros completely so that they do not
need a name for the head struct.  So clean up everything, not giving a
name except in the rare case where it is necessary.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
2019-01-11 15:46:55 +01:00

620 lines
19 KiB
C

/*
* CCID Card Device. Emulated card.
*
* Copyright (c) 2011 Red Hat.
* Written by Alon Levy.
*
* This code is licensed under the GNU LGPL, version 2 or later.
*/
/*
* It can be used to provide access to the local hardware in a non exclusive
* way, or it can use certificates. It requires the usb-ccid bus.
*
* Usage 1: standard, mirror hardware reader+card:
* qemu .. -usb -device usb-ccid -device ccid-card-emulated
*
* Usage 2: use certificates, no hardware required
* one time: create the certificates:
* for i in 1 2 3; do
* certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=user$i" -n user$i
* done
* qemu .. -usb -device usb-ccid \
* -device ccid-card-emulated,cert1=user1,cert2=user2,cert3=user3
*
* If you use a non default db for the certificates you can specify it using
* the db parameter.
*/
#include "qemu/osdep.h"
#include <libcacard.h>
#include "qemu/thread.h"
#include "qemu/main-loop.h"
#include "ccid.h"
#include "qapi/error.h"
#define DPRINTF(card, lvl, fmt, ...) \
do {\
if (lvl <= card->debug) {\
printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__);\
} \
} while (0)
#define TYPE_EMULATED_CCID "ccid-card-emulated"
#define EMULATED_CCID_CARD(obj) \
OBJECT_CHECK(EmulatedState, (obj), TYPE_EMULATED_CCID)
#define BACKEND_NSS_EMULATED_NAME "nss-emulated"
#define BACKEND_CERTIFICATES_NAME "certificates"
enum {
BACKEND_NSS_EMULATED = 1,
BACKEND_CERTIFICATES
};
#define DEFAULT_BACKEND BACKEND_NSS_EMULATED
typedef struct EmulatedState EmulatedState;
enum {
EMUL_READER_INSERT = 0,
EMUL_READER_REMOVE,
EMUL_CARD_INSERT,
EMUL_CARD_REMOVE,
EMUL_GUEST_APDU,
EMUL_RESPONSE_APDU,
EMUL_ERROR,
};
static const char *emul_event_to_string(uint32_t emul_event)
{
switch (emul_event) {
case EMUL_READER_INSERT:
return "EMUL_READER_INSERT";
case EMUL_READER_REMOVE:
return "EMUL_READER_REMOVE";
case EMUL_CARD_INSERT:
return "EMUL_CARD_INSERT";
case EMUL_CARD_REMOVE:
return "EMUL_CARD_REMOVE";
case EMUL_GUEST_APDU:
return "EMUL_GUEST_APDU";
case EMUL_RESPONSE_APDU:
return "EMUL_RESPONSE_APDU";
case EMUL_ERROR:
return "EMUL_ERROR";
}
return "UNKNOWN";
}
typedef struct EmulEvent {
QSIMPLEQ_ENTRY(EmulEvent) entry;
union {
struct {
uint32_t type;
} gen;
struct {
uint32_t type;
uint64_t code;
} error;
struct {
uint32_t type;
uint32_t len;
uint8_t data[];
} data;
} p;
} EmulEvent;
#define MAX_ATR_SIZE 40
struct EmulatedState {
CCIDCardState base;
uint8_t debug;
char *backend_str;
uint32_t backend;
char *cert1;
char *cert2;
char *cert3;
char *db;
uint8_t atr[MAX_ATR_SIZE];
uint8_t atr_length;
QSIMPLEQ_HEAD(, EmulEvent) event_list;
QemuMutex event_list_mutex;
QemuThread event_thread_id;
VReader *reader;
QSIMPLEQ_HEAD(, EmulEvent) guest_apdu_list;
QemuMutex vreader_mutex; /* and guest_apdu_list mutex */
QemuMutex handle_apdu_mutex;
QemuCond handle_apdu_cond;
EventNotifier notifier;
int quit_apdu_thread;
QemuThread apdu_thread_id;
};
static void emulated_apdu_from_guest(CCIDCardState *base,
const uint8_t *apdu, uint32_t len)
{
EmulatedState *card = EMULATED_CCID_CARD(base);
EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent) + len);
assert(event);
event->p.data.type = EMUL_GUEST_APDU;
event->p.data.len = len;
memcpy(event->p.data.data, apdu, len);
qemu_mutex_lock(&card->vreader_mutex);
QSIMPLEQ_INSERT_TAIL(&card->guest_apdu_list, event, entry);
qemu_mutex_unlock(&card->vreader_mutex);
qemu_mutex_lock(&card->handle_apdu_mutex);
qemu_cond_signal(&card->handle_apdu_cond);
qemu_mutex_unlock(&card->handle_apdu_mutex);
}
static const uint8_t *emulated_get_atr(CCIDCardState *base, uint32_t *len)
{
EmulatedState *card = EMULATED_CCID_CARD(base);
*len = card->atr_length;
return card->atr;
}
static void emulated_push_event(EmulatedState *card, EmulEvent *event)
{
qemu_mutex_lock(&card->event_list_mutex);
QSIMPLEQ_INSERT_TAIL(&(card->event_list), event, entry);
qemu_mutex_unlock(&card->event_list_mutex);
event_notifier_set(&card->notifier);
}
static void emulated_push_type(EmulatedState *card, uint32_t type)
{
EmulEvent *event = g_new(EmulEvent, 1);
assert(event);
event->p.gen.type = type;
emulated_push_event(card, event);
}
static void emulated_push_error(EmulatedState *card, uint64_t code)
{
EmulEvent *event = g_new(EmulEvent, 1);
assert(event);
event->p.error.type = EMUL_ERROR;
event->p.error.code = code;
emulated_push_event(card, event);
}
static void emulated_push_data_type(EmulatedState *card, uint32_t type,
const uint8_t *data, uint32_t len)
{
EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent) + len);
assert(event);
event->p.data.type = type;
event->p.data.len = len;
memcpy(event->p.data.data, data, len);
emulated_push_event(card, event);
}
static void emulated_push_reader_insert(EmulatedState *card)
{
emulated_push_type(card, EMUL_READER_INSERT);
}
static void emulated_push_reader_remove(EmulatedState *card)
{
emulated_push_type(card, EMUL_READER_REMOVE);
}
static void emulated_push_card_insert(EmulatedState *card,
const uint8_t *atr, uint32_t len)
{
emulated_push_data_type(card, EMUL_CARD_INSERT, atr, len);
}
static void emulated_push_card_remove(EmulatedState *card)
{
emulated_push_type(card, EMUL_CARD_REMOVE);
}
static void emulated_push_response_apdu(EmulatedState *card,
const uint8_t *apdu, uint32_t len)
{
emulated_push_data_type(card, EMUL_RESPONSE_APDU, apdu, len);
}
#define APDU_BUF_SIZE 270
static void *handle_apdu_thread(void* arg)
{
EmulatedState *card = arg;
uint8_t recv_data[APDU_BUF_SIZE];
int recv_len;
VReaderStatus reader_status;
EmulEvent *event;
while (1) {
qemu_mutex_lock(&card->handle_apdu_mutex);
qemu_cond_wait(&card->handle_apdu_cond, &card->handle_apdu_mutex);
qemu_mutex_unlock(&card->handle_apdu_mutex);
if (card->quit_apdu_thread) {
card->quit_apdu_thread = 0; /* debugging */
break;
}
qemu_mutex_lock(&card->vreader_mutex);
while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) {
event = QSIMPLEQ_FIRST(&card->guest_apdu_list);
assert((unsigned long)event > 1000);
QSIMPLEQ_REMOVE_HEAD(&card->guest_apdu_list, entry);
if (event->p.data.type != EMUL_GUEST_APDU) {
DPRINTF(card, 1, "unexpected message in handle_apdu_thread\n");
g_free(event);
continue;
}
if (card->reader == NULL) {
DPRINTF(card, 1, "reader is NULL\n");
g_free(event);
continue;
}
recv_len = sizeof(recv_data);
reader_status = vreader_xfr_bytes(card->reader,
event->p.data.data, event->p.data.len,
recv_data, &recv_len);
DPRINTF(card, 2, "got back apdu of length %d\n", recv_len);
if (reader_status == VREADER_OK) {
emulated_push_response_apdu(card, recv_data, recv_len);
} else {
emulated_push_error(card, reader_status);
}
g_free(event);
}
qemu_mutex_unlock(&card->vreader_mutex);
}
return NULL;
}
static void *event_thread(void *arg)
{
int atr_len = MAX_ATR_SIZE;
uint8_t atr[MAX_ATR_SIZE];
VEvent *event = NULL;
EmulatedState *card = arg;
while (1) {
const char *reader_name;
event = vevent_wait_next_vevent();
if (event == NULL || event->type == VEVENT_LAST) {
break;
}
if (event->type != VEVENT_READER_INSERT) {
if (card->reader == NULL && event->reader != NULL) {
/* Happens after device_add followed by card remove or insert.
* XXX: create synthetic add_reader events if vcard_emul_init
* already called, which happens if device_del and device_add
* are called */
card->reader = vreader_reference(event->reader);
} else {
if (event->reader != card->reader) {
fprintf(stderr,
"ERROR: wrong reader: quiting event_thread\n");
break;
}
}
}
switch (event->type) {
case VEVENT_READER_INSERT:
/* TODO: take a specific reader. i.e. track which reader
* we are seeing here, check it is the one we want (the first,
* or by a particular name), and ignore if we don't want it.
*/
reader_name = vreader_get_name(event->reader);
if (card->reader != NULL) {
DPRINTF(card, 2, "READER INSERT - replacing %s with %s\n",
vreader_get_name(card->reader), reader_name);
qemu_mutex_lock(&card->vreader_mutex);
vreader_free(card->reader);
qemu_mutex_unlock(&card->vreader_mutex);
emulated_push_reader_remove(card);
}
qemu_mutex_lock(&card->vreader_mutex);
DPRINTF(card, 2, "READER INSERT %s\n", reader_name);
card->reader = vreader_reference(event->reader);
qemu_mutex_unlock(&card->vreader_mutex);
emulated_push_reader_insert(card);
break;
case VEVENT_READER_REMOVE:
DPRINTF(card, 2, " READER REMOVE: %s\n",
vreader_get_name(event->reader));
qemu_mutex_lock(&card->vreader_mutex);
vreader_free(card->reader);
card->reader = NULL;
qemu_mutex_unlock(&card->vreader_mutex);
emulated_push_reader_remove(card);
break;
case VEVENT_CARD_INSERT:
/* get the ATR (intended as a response to a power on from the
* reader */
atr_len = MAX_ATR_SIZE;
vreader_power_on(event->reader, atr, &atr_len);
card->atr_length = (uint8_t)atr_len;
DPRINTF(card, 2, " CARD INSERT\n");
emulated_push_card_insert(card, atr, atr_len);
break;
case VEVENT_CARD_REMOVE:
DPRINTF(card, 2, " CARD REMOVE\n");
emulated_push_card_remove(card);
break;
case VEVENT_LAST: /* quit */
vevent_delete(event);
return NULL;
break;
default:
break;
}
vevent_delete(event);
}
return NULL;
}
static void card_event_handler(EventNotifier *notifier)
{
EmulatedState *card = container_of(notifier, EmulatedState, notifier);
EmulEvent *event, *next;
event_notifier_test_and_clear(&card->notifier);
qemu_mutex_lock(&card->event_list_mutex);
QSIMPLEQ_FOREACH_SAFE(event, &card->event_list, entry, next) {
DPRINTF(card, 2, "event %s\n", emul_event_to_string(event->p.gen.type));
switch (event->p.gen.type) {
case EMUL_RESPONSE_APDU:
ccid_card_send_apdu_to_guest(&card->base, event->p.data.data,
event->p.data.len);
break;
case EMUL_READER_INSERT:
ccid_card_ccid_attach(&card->base);
break;
case EMUL_READER_REMOVE:
ccid_card_ccid_detach(&card->base);
break;
case EMUL_CARD_INSERT:
assert(event->p.data.len <= MAX_ATR_SIZE);
card->atr_length = event->p.data.len;
memcpy(card->atr, event->p.data.data, card->atr_length);
ccid_card_card_inserted(&card->base);
break;
case EMUL_CARD_REMOVE:
ccid_card_card_removed(&card->base);
break;
case EMUL_ERROR:
ccid_card_card_error(&card->base, event->p.error.code);
break;
default:
DPRINTF(card, 2, "unexpected event\n");
break;
}
g_free(event);
}
QSIMPLEQ_INIT(&card->event_list);
qemu_mutex_unlock(&card->event_list_mutex);
}
static int init_event_notifier(EmulatedState *card, Error **errp)
{
if (event_notifier_init(&card->notifier, false) < 0) {
error_setg(errp, "ccid-card-emul: event notifier creation failed");
return -1;
}
event_notifier_set_handler(&card->notifier, card_event_handler);
return 0;
}
static void clean_event_notifier(EmulatedState *card)
{
event_notifier_set_handler(&card->notifier, NULL);
event_notifier_cleanup(&card->notifier);
}
#define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb"
#define CERTIFICATES_ARGS_TEMPLATE\
"db=\"%s\" use_hw=no soft=(,Virtual Reader,CAC,,%s,%s,%s)"
static int wrap_vcard_emul_init(VCardEmulOptions *options)
{
static int called;
static int options_was_null;
if (called) {
if ((options == NULL) != options_was_null) {
printf("%s: warning: running emulated with certificates"
" and emulated side by side is not supported\n",
__func__);
return VCARD_EMUL_FAIL;
}
vcard_emul_replay_insertion_events();
return VCARD_EMUL_OK;
}
options_was_null = (options == NULL);
called = 1;
return vcard_emul_init(options);
}
static int emulated_initialize_vcard_from_certificates(EmulatedState *card)
{
char emul_args[200];
VCardEmulOptions *options = NULL;
snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE,
card->db ? card->db : CERTIFICATES_DEFAULT_DB,
card->cert1, card->cert2, card->cert3);
options = vcard_emul_options(emul_args);
if (options == NULL) {
printf("%s: warning: not using certificates due to"
" initialization error\n", __func__);
}
return wrap_vcard_emul_init(options);
}
typedef struct EnumTable {
const char *name;
uint32_t value;
} EnumTable;
static const EnumTable backend_enum_table[] = {
{BACKEND_NSS_EMULATED_NAME, BACKEND_NSS_EMULATED},
{BACKEND_CERTIFICATES_NAME, BACKEND_CERTIFICATES},
{NULL, 0},
};
static uint32_t parse_enumeration(char *str,
const EnumTable *table, uint32_t not_found_value)
{
uint32_t ret = not_found_value;
if (str == NULL)
return 0;
while (table->name != NULL) {
if (strcmp(table->name, str) == 0) {
ret = table->value;
break;
}
table++;
}
return ret;
}
static void emulated_realize(CCIDCardState *base, Error **errp)
{
EmulatedState *card = EMULATED_CCID_CARD(base);
VCardEmulError ret;
const EnumTable *ptable;
QSIMPLEQ_INIT(&card->event_list);
QSIMPLEQ_INIT(&card->guest_apdu_list);
qemu_mutex_init(&card->event_list_mutex);
qemu_mutex_init(&card->vreader_mutex);
qemu_mutex_init(&card->handle_apdu_mutex);
qemu_cond_init(&card->handle_apdu_cond);
card->reader = NULL;
card->quit_apdu_thread = 0;
if (init_event_notifier(card, errp) < 0) {
goto out1;
}
card->backend = 0;
if (card->backend_str) {
card->backend = parse_enumeration(card->backend_str,
backend_enum_table, 0);
}
if (card->backend == 0) {
error_setg(errp, "backend must be one of:");
for (ptable = backend_enum_table; ptable->name != NULL; ++ptable) {
error_append_hint(errp, "%s\n", ptable->name);
}
goto out2;
}
/* TODO: a passthru backened that works on local machine. third card type?*/
if (card->backend == BACKEND_CERTIFICATES) {
if (card->cert1 != NULL && card->cert2 != NULL && card->cert3 != NULL) {
ret = emulated_initialize_vcard_from_certificates(card);
} else {
error_setg(errp, "%s: you must provide all three certs for"
" certificates backend", TYPE_EMULATED_CCID);
goto out2;
}
} else {
if (card->backend != BACKEND_NSS_EMULATED) {
error_setg(errp, "%s: bad backend specified. The options are:%s"
" (default), %s.", TYPE_EMULATED_CCID,
BACKEND_NSS_EMULATED_NAME, BACKEND_CERTIFICATES_NAME);
goto out2;
}
if (card->cert1 != NULL || card->cert2 != NULL || card->cert3 != NULL) {
error_setg(errp, "%s: unexpected cert parameters to nss emulated "
"backend", TYPE_EMULATED_CCID);
goto out2;
}
/* default to mirroring the local hardware readers */
ret = wrap_vcard_emul_init(NULL);
}
if (ret != VCARD_EMUL_OK) {
error_setg(errp, "%s: failed to initialize vcard", TYPE_EMULATED_CCID);
goto out2;
}
qemu_thread_create(&card->event_thread_id, "ccid/event", event_thread,
card, QEMU_THREAD_JOINABLE);
qemu_thread_create(&card->apdu_thread_id, "ccid/apdu", handle_apdu_thread,
card, QEMU_THREAD_JOINABLE);
return;
out2:
clean_event_notifier(card);
out1:
qemu_cond_destroy(&card->handle_apdu_cond);
qemu_mutex_destroy(&card->handle_apdu_mutex);
qemu_mutex_destroy(&card->vreader_mutex);
qemu_mutex_destroy(&card->event_list_mutex);
}
static void emulated_unrealize(CCIDCardState *base, Error **errp)
{
EmulatedState *card = EMULATED_CCID_CARD(base);
VEvent *vevent = vevent_new(VEVENT_LAST, NULL, NULL);
vevent_queue_vevent(vevent); /* stop vevent thread */
qemu_thread_join(&card->event_thread_id);
card->quit_apdu_thread = 1; /* stop handle_apdu thread */
qemu_cond_signal(&card->handle_apdu_cond);
qemu_thread_join(&card->apdu_thread_id);
clean_event_notifier(card);
/* threads exited, can destroy all condvars/mutexes */
qemu_cond_destroy(&card->handle_apdu_cond);
qemu_mutex_destroy(&card->handle_apdu_mutex);
qemu_mutex_destroy(&card->vreader_mutex);
qemu_mutex_destroy(&card->event_list_mutex);
}
static Property emulated_card_properties[] = {
DEFINE_PROP_STRING("backend", EmulatedState, backend_str),
DEFINE_PROP_STRING("cert1", EmulatedState, cert1),
DEFINE_PROP_STRING("cert2", EmulatedState, cert2),
DEFINE_PROP_STRING("cert3", EmulatedState, cert3),
DEFINE_PROP_STRING("db", EmulatedState, db),
DEFINE_PROP_UINT8("debug", EmulatedState, debug, 0),
DEFINE_PROP_END_OF_LIST(),
};
static void emulated_class_initfn(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
CCIDCardClass *cc = CCID_CARD_CLASS(klass);
cc->realize = emulated_realize;
cc->unrealize = emulated_unrealize;
cc->get_atr = emulated_get_atr;
cc->apdu_from_guest = emulated_apdu_from_guest;
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
dc->desc = "emulated smartcard";
dc->props = emulated_card_properties;
}
static const TypeInfo emulated_card_info = {
.name = TYPE_EMULATED_CCID,
.parent = TYPE_CCID_CARD,
.instance_size = sizeof(EmulatedState),
.class_init = emulated_class_initfn,
};
static void ccid_card_emulated_register_types(void)
{
type_register_static(&emulated_card_info);
}
type_init(ccid_card_emulated_register_types)