/* * 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 <eventt.h> #include <vevent.h> #include <vreader.h> #include <vcard_emul.h> #include "qemu/thread.h" #include "sysemu/char.h" #include "ccid.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(event_list, EmulEvent) event_list; QemuMutex event_list_mutex; QemuThread event_thread_id; VReader *reader; QSIMPLEQ_HEAD(guest_apdu_list, 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) { if (event_notifier_init(&card->notifier, false) < 0) { DPRINTF(card, 2, "event notifier creation failed\n"); return -1; } event_notifier_set_handler(&card->notifier, card_event_handler); return 0; } #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 int emulated_initfn(CCIDCardState *base) { 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) < 0) { return -1; } card->backend = 0; if (card->backend_str) { card->backend = parse_enumeration(card->backend_str, backend_enum_table, 0); } if (card->backend == 0) { printf("backend must be one of:\n"); for (ptable = backend_enum_table; ptable->name != NULL; ++ptable) { printf("%s\n", ptable->name); } return -1; } /* 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 { printf("%s: you must provide all three certs for" " certificates backend\n", TYPE_EMULATED_CCID); return -1; } } else { if (card->backend != BACKEND_NSS_EMULATED) { printf("%s: bad backend specified. The options are:\n%s (default)," " %s.\n", TYPE_EMULATED_CCID, BACKEND_NSS_EMULATED_NAME, BACKEND_CERTIFICATES_NAME); return -1; } if (card->cert1 != NULL || card->cert2 != NULL || card->cert3 != NULL) { printf("%s: unexpected cert parameters to nss emulated backend\n", TYPE_EMULATED_CCID); return -1; } /* default to mirroring the local hardware readers */ ret = wrap_vcard_emul_init(NULL); } if (ret != VCARD_EMUL_OK) { printf("%s: failed to initialize vcard\n", TYPE_EMULATED_CCID); return -1; } 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 0; } static void emulated_exitfn(CCIDCardState *base) { 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); /* 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->initfn = emulated_initfn; cc->exitfn = emulated_exitfn; 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)