| /* |
| * 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 <eventt.h> |
| #include <vevent.h> |
| #include <vreader.h> |
| #include <vcard_emul.h> |
| |
| #include "qemu/thread.h" |
| #include "sysemu/char.h" |
| #include "monitor/monitor.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 EMULATED_DEV_NAME "ccid-card-emulated" |
| |
| #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 = DO_UPCAST(EmulatedState, base, 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 = DO_UPCAST(EmulatedState, base, 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 = (EmulEvent *)g_malloc(sizeof(EmulEvent)); |
| |
| assert(event); |
| event->p.gen.type = type; |
| emulated_push_event(card, event); |
| } |
| |
| static void emulated_push_error(EmulatedState *card, uint64_t code) |
| { |
| EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent)); |
| |
| 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 = DO_UPCAST(EmulatedState, base, 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", EMULATED_DEV_NAME); |
| return -1; |
| } |
| } else { |
| if (card->backend != BACKEND_NSS_EMULATED) { |
| printf("%s: bad backend specified. The options are:\n%s (default)," |
| " %s.\n", EMULATED_DEV_NAME, 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", |
| EMULATED_DEV_NAME); |
| 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", EMULATED_DEV_NAME); |
| 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 int emulated_exitfn(CCIDCardState *base) |
| { |
| EmulatedState *card = DO_UPCAST(EmulatedState, base, 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); |
| return 0; |
| } |
| |
| 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 = EMULATED_DEV_NAME, |
| .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) |