Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 1 | /* |
| 2 | * CCID Card Device. Emulated card. |
| 3 | * |
| 4 | * Copyright (c) 2011 Red Hat. |
| 5 | * Written by Alon Levy. |
| 6 | * |
Matthew Fernandez | 8e31bf3 | 2011-06-26 12:21:35 +1000 | [diff] [blame] | 7 | * This code is licensed under the GNU LGPL, version 2 or later. |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 8 | */ |
| 9 | |
| 10 | /* |
| 11 | * It can be used to provide access to the local hardware in a non exclusive |
| 12 | * way, or it can use certificates. It requires the usb-ccid bus. |
| 13 | * |
| 14 | * Usage 1: standard, mirror hardware reader+card: |
| 15 | * qemu .. -usb -device usb-ccid -device ccid-card-emulated |
| 16 | * |
| 17 | * Usage 2: use certificates, no hardware required |
| 18 | * one time: create the certificates: |
| 19 | * for i in 1 2 3; do |
| 20 | * certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=user$i" -n user$i |
| 21 | * done |
| 22 | * qemu .. -usb -device usb-ccid \ |
| 23 | * -device ccid-card-emulated,cert1=user1,cert2=user2,cert3=user3 |
| 24 | * |
| 25 | * If you use a non default db for the certificates you can specify it using |
| 26 | * the db parameter. |
| 27 | */ |
| 28 | |
| 29 | #include <eventt.h> |
| 30 | #include <vevent.h> |
| 31 | #include <vreader.h> |
| 32 | #include <vcard_emul.h> |
| 33 | |
Paolo Bonzini | 1de7afc | 2012-12-17 18:20:00 +0100 | [diff] [blame] | 34 | #include "qemu/thread.h" |
Paolo Bonzini | dccfcd0 | 2013-04-08 16:55:25 +0200 | [diff] [blame] | 35 | #include "sysemu/char.h" |
Paolo Bonzini | 47b43a1 | 2013-03-18 17:36:02 +0100 | [diff] [blame] | 36 | #include "ccid.h" |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 37 | |
| 38 | #define DPRINTF(card, lvl, fmt, ...) \ |
| 39 | do {\ |
| 40 | if (lvl <= card->debug) {\ |
| 41 | printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__);\ |
| 42 | } \ |
| 43 | } while (0) |
| 44 | |
| 45 | #define EMULATED_DEV_NAME "ccid-card-emulated" |
| 46 | |
| 47 | #define BACKEND_NSS_EMULATED_NAME "nss-emulated" |
| 48 | #define BACKEND_CERTIFICATES_NAME "certificates" |
| 49 | |
| 50 | enum { |
| 51 | BACKEND_NSS_EMULATED = 1, |
| 52 | BACKEND_CERTIFICATES |
| 53 | }; |
| 54 | |
| 55 | #define DEFAULT_BACKEND BACKEND_NSS_EMULATED |
| 56 | |
| 57 | typedef struct EmulatedState EmulatedState; |
| 58 | |
| 59 | enum { |
| 60 | EMUL_READER_INSERT = 0, |
| 61 | EMUL_READER_REMOVE, |
| 62 | EMUL_CARD_INSERT, |
| 63 | EMUL_CARD_REMOVE, |
| 64 | EMUL_GUEST_APDU, |
| 65 | EMUL_RESPONSE_APDU, |
| 66 | EMUL_ERROR, |
| 67 | }; |
| 68 | |
| 69 | static const char *emul_event_to_string(uint32_t emul_event) |
| 70 | { |
| 71 | switch (emul_event) { |
| 72 | case EMUL_READER_INSERT: |
| 73 | return "EMUL_READER_INSERT"; |
| 74 | case EMUL_READER_REMOVE: |
| 75 | return "EMUL_READER_REMOVE"; |
| 76 | case EMUL_CARD_INSERT: |
| 77 | return "EMUL_CARD_INSERT"; |
| 78 | case EMUL_CARD_REMOVE: |
| 79 | return "EMUL_CARD_REMOVE"; |
| 80 | case EMUL_GUEST_APDU: |
| 81 | return "EMUL_GUEST_APDU"; |
| 82 | case EMUL_RESPONSE_APDU: |
| 83 | return "EMUL_RESPONSE_APDU"; |
| 84 | case EMUL_ERROR: |
| 85 | return "EMUL_ERROR"; |
| 86 | } |
| 87 | return "UNKNOWN"; |
| 88 | } |
| 89 | |
| 90 | typedef struct EmulEvent { |
| 91 | QSIMPLEQ_ENTRY(EmulEvent) entry; |
| 92 | union { |
| 93 | struct { |
| 94 | uint32_t type; |
| 95 | } gen; |
| 96 | struct { |
| 97 | uint32_t type; |
| 98 | uint64_t code; |
| 99 | } error; |
| 100 | struct { |
| 101 | uint32_t type; |
| 102 | uint32_t len; |
| 103 | uint8_t data[]; |
| 104 | } data; |
| 105 | } p; |
| 106 | } EmulEvent; |
| 107 | |
| 108 | #define MAX_ATR_SIZE 40 |
| 109 | struct EmulatedState { |
| 110 | CCIDCardState base; |
| 111 | uint8_t debug; |
| 112 | char *backend_str; |
| 113 | uint32_t backend; |
| 114 | char *cert1; |
| 115 | char *cert2; |
| 116 | char *cert3; |
| 117 | char *db; |
| 118 | uint8_t atr[MAX_ATR_SIZE]; |
| 119 | uint8_t atr_length; |
| 120 | QSIMPLEQ_HEAD(event_list, EmulEvent) event_list; |
| 121 | QemuMutex event_list_mutex; |
Paolo Bonzini | da5361c | 2011-12-12 17:21:34 +0100 | [diff] [blame] | 122 | QemuThread event_thread_id; |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 123 | VReader *reader; |
| 124 | QSIMPLEQ_HEAD(guest_apdu_list, EmulEvent) guest_apdu_list; |
| 125 | QemuMutex vreader_mutex; /* and guest_apdu_list mutex */ |
| 126 | QemuMutex handle_apdu_mutex; |
| 127 | QemuCond handle_apdu_cond; |
Paolo Bonzini | c1129f6b | 2014-06-23 12:30:36 +0200 | [diff] [blame] | 128 | EventNotifier notifier; |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 129 | int quit_apdu_thread; |
Paolo Bonzini | da5361c | 2011-12-12 17:21:34 +0100 | [diff] [blame] | 130 | QemuThread apdu_thread_id; |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 131 | }; |
| 132 | |
| 133 | static void emulated_apdu_from_guest(CCIDCardState *base, |
| 134 | const uint8_t *apdu, uint32_t len) |
| 135 | { |
| 136 | EmulatedState *card = DO_UPCAST(EmulatedState, base, base); |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 137 | EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent) + len); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 138 | |
| 139 | assert(event); |
| 140 | event->p.data.type = EMUL_GUEST_APDU; |
| 141 | event->p.data.len = len; |
| 142 | memcpy(event->p.data.data, apdu, len); |
| 143 | qemu_mutex_lock(&card->vreader_mutex); |
| 144 | QSIMPLEQ_INSERT_TAIL(&card->guest_apdu_list, event, entry); |
| 145 | qemu_mutex_unlock(&card->vreader_mutex); |
| 146 | qemu_mutex_lock(&card->handle_apdu_mutex); |
| 147 | qemu_cond_signal(&card->handle_apdu_cond); |
| 148 | qemu_mutex_unlock(&card->handle_apdu_mutex); |
| 149 | } |
| 150 | |
| 151 | static const uint8_t *emulated_get_atr(CCIDCardState *base, uint32_t *len) |
| 152 | { |
| 153 | EmulatedState *card = DO_UPCAST(EmulatedState, base, base); |
| 154 | |
| 155 | *len = card->atr_length; |
| 156 | return card->atr; |
| 157 | } |
| 158 | |
| 159 | static void emulated_push_event(EmulatedState *card, EmulEvent *event) |
| 160 | { |
| 161 | qemu_mutex_lock(&card->event_list_mutex); |
| 162 | QSIMPLEQ_INSERT_TAIL(&(card->event_list), event, entry); |
| 163 | qemu_mutex_unlock(&card->event_list_mutex); |
Paolo Bonzini | c1129f6b | 2014-06-23 12:30:36 +0200 | [diff] [blame] | 164 | event_notifier_set(&card->notifier); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 165 | } |
| 166 | |
| 167 | static void emulated_push_type(EmulatedState *card, uint32_t type) |
| 168 | { |
Markus Armbruster | 98f3433 | 2015-10-29 16:55:22 +0100 | [diff] [blame] | 169 | EmulEvent *event = g_new(EmulEvent, 1); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 170 | |
| 171 | assert(event); |
| 172 | event->p.gen.type = type; |
| 173 | emulated_push_event(card, event); |
| 174 | } |
| 175 | |
| 176 | static void emulated_push_error(EmulatedState *card, uint64_t code) |
| 177 | { |
Markus Armbruster | 98f3433 | 2015-10-29 16:55:22 +0100 | [diff] [blame] | 178 | EmulEvent *event = g_new(EmulEvent, 1); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 179 | |
| 180 | assert(event); |
| 181 | event->p.error.type = EMUL_ERROR; |
| 182 | event->p.error.code = code; |
| 183 | emulated_push_event(card, event); |
| 184 | } |
| 185 | |
| 186 | static void emulated_push_data_type(EmulatedState *card, uint32_t type, |
| 187 | const uint8_t *data, uint32_t len) |
| 188 | { |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 189 | EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent) + len); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 190 | |
| 191 | assert(event); |
| 192 | event->p.data.type = type; |
| 193 | event->p.data.len = len; |
| 194 | memcpy(event->p.data.data, data, len); |
| 195 | emulated_push_event(card, event); |
| 196 | } |
| 197 | |
| 198 | static void emulated_push_reader_insert(EmulatedState *card) |
| 199 | { |
| 200 | emulated_push_type(card, EMUL_READER_INSERT); |
| 201 | } |
| 202 | |
| 203 | static void emulated_push_reader_remove(EmulatedState *card) |
| 204 | { |
| 205 | emulated_push_type(card, EMUL_READER_REMOVE); |
| 206 | } |
| 207 | |
| 208 | static void emulated_push_card_insert(EmulatedState *card, |
| 209 | const uint8_t *atr, uint32_t len) |
| 210 | { |
| 211 | emulated_push_data_type(card, EMUL_CARD_INSERT, atr, len); |
| 212 | } |
| 213 | |
| 214 | static void emulated_push_card_remove(EmulatedState *card) |
| 215 | { |
| 216 | emulated_push_type(card, EMUL_CARD_REMOVE); |
| 217 | } |
| 218 | |
| 219 | static void emulated_push_response_apdu(EmulatedState *card, |
| 220 | const uint8_t *apdu, uint32_t len) |
| 221 | { |
| 222 | emulated_push_data_type(card, EMUL_RESPONSE_APDU, apdu, len); |
| 223 | } |
| 224 | |
| 225 | #define APDU_BUF_SIZE 270 |
| 226 | static void *handle_apdu_thread(void* arg) |
| 227 | { |
| 228 | EmulatedState *card = arg; |
| 229 | uint8_t recv_data[APDU_BUF_SIZE]; |
| 230 | int recv_len; |
| 231 | VReaderStatus reader_status; |
| 232 | EmulEvent *event; |
| 233 | |
| 234 | while (1) { |
| 235 | qemu_mutex_lock(&card->handle_apdu_mutex); |
| 236 | qemu_cond_wait(&card->handle_apdu_cond, &card->handle_apdu_mutex); |
| 237 | qemu_mutex_unlock(&card->handle_apdu_mutex); |
| 238 | if (card->quit_apdu_thread) { |
| 239 | card->quit_apdu_thread = 0; /* debugging */ |
| 240 | break; |
| 241 | } |
| 242 | qemu_mutex_lock(&card->vreader_mutex); |
| 243 | while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) { |
| 244 | event = QSIMPLEQ_FIRST(&card->guest_apdu_list); |
| 245 | assert((unsigned long)event > 1000); |
| 246 | QSIMPLEQ_REMOVE_HEAD(&card->guest_apdu_list, entry); |
| 247 | if (event->p.data.type != EMUL_GUEST_APDU) { |
| 248 | DPRINTF(card, 1, "unexpected message in handle_apdu_thread\n"); |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 249 | g_free(event); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 250 | continue; |
| 251 | } |
| 252 | if (card->reader == NULL) { |
| 253 | DPRINTF(card, 1, "reader is NULL\n"); |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 254 | g_free(event); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 255 | continue; |
| 256 | } |
| 257 | recv_len = sizeof(recv_data); |
| 258 | reader_status = vreader_xfr_bytes(card->reader, |
| 259 | event->p.data.data, event->p.data.len, |
| 260 | recv_data, &recv_len); |
| 261 | DPRINTF(card, 2, "got back apdu of length %d\n", recv_len); |
| 262 | if (reader_status == VREADER_OK) { |
| 263 | emulated_push_response_apdu(card, recv_data, recv_len); |
| 264 | } else { |
| 265 | emulated_push_error(card, reader_status); |
| 266 | } |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 267 | g_free(event); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 268 | } |
| 269 | qemu_mutex_unlock(&card->vreader_mutex); |
| 270 | } |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 271 | return NULL; |
| 272 | } |
| 273 | |
| 274 | static void *event_thread(void *arg) |
| 275 | { |
| 276 | int atr_len = MAX_ATR_SIZE; |
| 277 | uint8_t atr[MAX_ATR_SIZE]; |
| 278 | VEvent *event = NULL; |
| 279 | EmulatedState *card = arg; |
| 280 | |
| 281 | while (1) { |
| 282 | const char *reader_name; |
| 283 | |
| 284 | event = vevent_wait_next_vevent(); |
| 285 | if (event == NULL || event->type == VEVENT_LAST) { |
| 286 | break; |
| 287 | } |
| 288 | if (event->type != VEVENT_READER_INSERT) { |
| 289 | if (card->reader == NULL && event->reader != NULL) { |
| 290 | /* Happens after device_add followed by card remove or insert. |
| 291 | * XXX: create synthetic add_reader events if vcard_emul_init |
| 292 | * already called, which happens if device_del and device_add |
| 293 | * are called */ |
| 294 | card->reader = vreader_reference(event->reader); |
| 295 | } else { |
| 296 | if (event->reader != card->reader) { |
| 297 | fprintf(stderr, |
| 298 | "ERROR: wrong reader: quiting event_thread\n"); |
| 299 | break; |
| 300 | } |
| 301 | } |
| 302 | } |
| 303 | switch (event->type) { |
| 304 | case VEVENT_READER_INSERT: |
| 305 | /* TODO: take a specific reader. i.e. track which reader |
| 306 | * we are seeing here, check it is the one we want (the first, |
| 307 | * or by a particular name), and ignore if we don't want it. |
| 308 | */ |
| 309 | reader_name = vreader_get_name(event->reader); |
| 310 | if (card->reader != NULL) { |
| 311 | DPRINTF(card, 2, "READER INSERT - replacing %s with %s\n", |
| 312 | vreader_get_name(card->reader), reader_name); |
| 313 | qemu_mutex_lock(&card->vreader_mutex); |
| 314 | vreader_free(card->reader); |
| 315 | qemu_mutex_unlock(&card->vreader_mutex); |
| 316 | emulated_push_reader_remove(card); |
| 317 | } |
| 318 | qemu_mutex_lock(&card->vreader_mutex); |
| 319 | DPRINTF(card, 2, "READER INSERT %s\n", reader_name); |
| 320 | card->reader = vreader_reference(event->reader); |
| 321 | qemu_mutex_unlock(&card->vreader_mutex); |
| 322 | emulated_push_reader_insert(card); |
| 323 | break; |
| 324 | case VEVENT_READER_REMOVE: |
| 325 | DPRINTF(card, 2, " READER REMOVE: %s\n", |
| 326 | vreader_get_name(event->reader)); |
| 327 | qemu_mutex_lock(&card->vreader_mutex); |
| 328 | vreader_free(card->reader); |
| 329 | card->reader = NULL; |
| 330 | qemu_mutex_unlock(&card->vreader_mutex); |
| 331 | emulated_push_reader_remove(card); |
| 332 | break; |
| 333 | case VEVENT_CARD_INSERT: |
| 334 | /* get the ATR (intended as a response to a power on from the |
| 335 | * reader */ |
| 336 | atr_len = MAX_ATR_SIZE; |
| 337 | vreader_power_on(event->reader, atr, &atr_len); |
| 338 | card->atr_length = (uint8_t)atr_len; |
| 339 | DPRINTF(card, 2, " CARD INSERT\n"); |
| 340 | emulated_push_card_insert(card, atr, atr_len); |
| 341 | break; |
| 342 | case VEVENT_CARD_REMOVE: |
| 343 | DPRINTF(card, 2, " CARD REMOVE\n"); |
| 344 | emulated_push_card_remove(card); |
| 345 | break; |
| 346 | case VEVENT_LAST: /* quit */ |
| 347 | vevent_delete(event); |
| 348 | return NULL; |
| 349 | break; |
| 350 | default: |
| 351 | break; |
| 352 | } |
| 353 | vevent_delete(event); |
| 354 | } |
| 355 | return NULL; |
| 356 | } |
| 357 | |
Paolo Bonzini | c1129f6b | 2014-06-23 12:30:36 +0200 | [diff] [blame] | 358 | static void card_event_handler(EventNotifier *notifier) |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 359 | { |
Paolo Bonzini | c1129f6b | 2014-06-23 12:30:36 +0200 | [diff] [blame] | 360 | EmulatedState *card = container_of(notifier, EmulatedState, notifier); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 361 | EmulEvent *event, *next; |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 362 | |
Paolo Bonzini | c1129f6b | 2014-06-23 12:30:36 +0200 | [diff] [blame] | 363 | event_notifier_test_and_clear(&card->notifier); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 364 | qemu_mutex_lock(&card->event_list_mutex); |
| 365 | QSIMPLEQ_FOREACH_SAFE(event, &card->event_list, entry, next) { |
| 366 | DPRINTF(card, 2, "event %s\n", emul_event_to_string(event->p.gen.type)); |
| 367 | switch (event->p.gen.type) { |
| 368 | case EMUL_RESPONSE_APDU: |
| 369 | ccid_card_send_apdu_to_guest(&card->base, event->p.data.data, |
| 370 | event->p.data.len); |
| 371 | break; |
| 372 | case EMUL_READER_INSERT: |
| 373 | ccid_card_ccid_attach(&card->base); |
| 374 | break; |
| 375 | case EMUL_READER_REMOVE: |
| 376 | ccid_card_ccid_detach(&card->base); |
| 377 | break; |
| 378 | case EMUL_CARD_INSERT: |
| 379 | assert(event->p.data.len <= MAX_ATR_SIZE); |
| 380 | card->atr_length = event->p.data.len; |
| 381 | memcpy(card->atr, event->p.data.data, card->atr_length); |
| 382 | ccid_card_card_inserted(&card->base); |
| 383 | break; |
| 384 | case EMUL_CARD_REMOVE: |
| 385 | ccid_card_card_removed(&card->base); |
| 386 | break; |
| 387 | case EMUL_ERROR: |
| 388 | ccid_card_card_error(&card->base, event->p.error.code); |
| 389 | break; |
| 390 | default: |
| 391 | DPRINTF(card, 2, "unexpected event\n"); |
| 392 | break; |
| 393 | } |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 394 | g_free(event); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 395 | } |
| 396 | QSIMPLEQ_INIT(&card->event_list); |
| 397 | qemu_mutex_unlock(&card->event_list_mutex); |
| 398 | } |
| 399 | |
Paolo Bonzini | c1129f6b | 2014-06-23 12:30:36 +0200 | [diff] [blame] | 400 | static int init_event_notifier(EmulatedState *card) |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 401 | { |
Paolo Bonzini | c1129f6b | 2014-06-23 12:30:36 +0200 | [diff] [blame] | 402 | if (event_notifier_init(&card->notifier, false) < 0) { |
| 403 | DPRINTF(card, 2, "event notifier creation failed\n"); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 404 | return -1; |
| 405 | } |
Paolo Bonzini | c1129f6b | 2014-06-23 12:30:36 +0200 | [diff] [blame] | 406 | event_notifier_set_handler(&card->notifier, card_event_handler); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 407 | return 0; |
| 408 | } |
| 409 | |
| 410 | #define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb" |
| 411 | #define CERTIFICATES_ARGS_TEMPLATE\ |
| 412 | "db=\"%s\" use_hw=no soft=(,Virtual Reader,CAC,,%s,%s,%s)" |
| 413 | |
| 414 | static int wrap_vcard_emul_init(VCardEmulOptions *options) |
| 415 | { |
| 416 | static int called; |
| 417 | static int options_was_null; |
| 418 | |
| 419 | if (called) { |
| 420 | if ((options == NULL) != options_was_null) { |
| 421 | printf("%s: warning: running emulated with certificates" |
| 422 | " and emulated side by side is not supported\n", |
| 423 | __func__); |
| 424 | return VCARD_EMUL_FAIL; |
| 425 | } |
| 426 | vcard_emul_replay_insertion_events(); |
| 427 | return VCARD_EMUL_OK; |
| 428 | } |
| 429 | options_was_null = (options == NULL); |
| 430 | called = 1; |
| 431 | return vcard_emul_init(options); |
| 432 | } |
| 433 | |
| 434 | static int emulated_initialize_vcard_from_certificates(EmulatedState *card) |
| 435 | { |
| 436 | char emul_args[200]; |
| 437 | VCardEmulOptions *options = NULL; |
| 438 | |
| 439 | snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE, |
| 440 | card->db ? card->db : CERTIFICATES_DEFAULT_DB, |
| 441 | card->cert1, card->cert2, card->cert3); |
| 442 | options = vcard_emul_options(emul_args); |
| 443 | if (options == NULL) { |
| 444 | printf("%s: warning: not using certificates due to" |
| 445 | " initialization error\n", __func__); |
| 446 | } |
| 447 | return wrap_vcard_emul_init(options); |
| 448 | } |
| 449 | |
| 450 | typedef struct EnumTable { |
| 451 | const char *name; |
| 452 | uint32_t value; |
| 453 | } EnumTable; |
| 454 | |
Jim Meyering | d18c711 | 2012-05-21 21:56:20 +0200 | [diff] [blame] | 455 | static const EnumTable backend_enum_table[] = { |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 456 | {BACKEND_NSS_EMULATED_NAME, BACKEND_NSS_EMULATED}, |
| 457 | {BACKEND_CERTIFICATES_NAME, BACKEND_CERTIFICATES}, |
| 458 | {NULL, 0}, |
| 459 | }; |
| 460 | |
| 461 | static uint32_t parse_enumeration(char *str, |
Jim Meyering | d18c711 | 2012-05-21 21:56:20 +0200 | [diff] [blame] | 462 | const EnumTable *table, uint32_t not_found_value) |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 463 | { |
| 464 | uint32_t ret = not_found_value; |
| 465 | |
Marc-André Lureau | d0ebd78 | 2012-11-21 14:16:08 +0100 | [diff] [blame] | 466 | if (str == NULL) |
| 467 | return 0; |
| 468 | |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 469 | while (table->name != NULL) { |
| 470 | if (strcmp(table->name, str) == 0) { |
| 471 | ret = table->value; |
| 472 | break; |
| 473 | } |
| 474 | table++; |
| 475 | } |
| 476 | return ret; |
| 477 | } |
| 478 | |
| 479 | static int emulated_initfn(CCIDCardState *base) |
| 480 | { |
| 481 | EmulatedState *card = DO_UPCAST(EmulatedState, base, base); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 482 | VCardEmulError ret; |
Jim Meyering | d18c711 | 2012-05-21 21:56:20 +0200 | [diff] [blame] | 483 | const EnumTable *ptable; |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 484 | |
| 485 | QSIMPLEQ_INIT(&card->event_list); |
| 486 | QSIMPLEQ_INIT(&card->guest_apdu_list); |
| 487 | qemu_mutex_init(&card->event_list_mutex); |
| 488 | qemu_mutex_init(&card->vreader_mutex); |
| 489 | qemu_mutex_init(&card->handle_apdu_mutex); |
| 490 | qemu_cond_init(&card->handle_apdu_cond); |
| 491 | card->reader = NULL; |
| 492 | card->quit_apdu_thread = 0; |
Paolo Bonzini | c1129f6b | 2014-06-23 12:30:36 +0200 | [diff] [blame] | 493 | if (init_event_notifier(card) < 0) { |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 494 | return -1; |
| 495 | } |
Cole Robinson | ae12e3a | 2013-04-14 16:06:59 -0400 | [diff] [blame] | 496 | |
| 497 | card->backend = 0; |
| 498 | if (card->backend_str) { |
| 499 | card->backend = parse_enumeration(card->backend_str, |
| 500 | backend_enum_table, 0); |
| 501 | } |
| 502 | |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 503 | if (card->backend == 0) { |
Cole Robinson | ae12e3a | 2013-04-14 16:06:59 -0400 | [diff] [blame] | 504 | printf("backend must be one of:\n"); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 505 | for (ptable = backend_enum_table; ptable->name != NULL; ++ptable) { |
| 506 | printf("%s\n", ptable->name); |
| 507 | } |
| 508 | return -1; |
| 509 | } |
| 510 | |
| 511 | /* TODO: a passthru backened that works on local machine. third card type?*/ |
| 512 | if (card->backend == BACKEND_CERTIFICATES) { |
| 513 | if (card->cert1 != NULL && card->cert2 != NULL && card->cert3 != NULL) { |
| 514 | ret = emulated_initialize_vcard_from_certificates(card); |
| 515 | } else { |
| 516 | printf("%s: you must provide all three certs for" |
| 517 | " certificates backend\n", EMULATED_DEV_NAME); |
| 518 | return -1; |
| 519 | } |
| 520 | } else { |
| 521 | if (card->backend != BACKEND_NSS_EMULATED) { |
| 522 | printf("%s: bad backend specified. The options are:\n%s (default)," |
| 523 | " %s.\n", EMULATED_DEV_NAME, BACKEND_NSS_EMULATED_NAME, |
| 524 | BACKEND_CERTIFICATES_NAME); |
| 525 | return -1; |
| 526 | } |
| 527 | if (card->cert1 != NULL || card->cert2 != NULL || card->cert3 != NULL) { |
| 528 | printf("%s: unexpected cert parameters to nss emulated backend\n", |
| 529 | EMULATED_DEV_NAME); |
| 530 | return -1; |
| 531 | } |
| 532 | /* default to mirroring the local hardware readers */ |
| 533 | ret = wrap_vcard_emul_init(NULL); |
| 534 | } |
| 535 | if (ret != VCARD_EMUL_OK) { |
| 536 | printf("%s: failed to initialize vcard\n", EMULATED_DEV_NAME); |
| 537 | return -1; |
| 538 | } |
Dr. David Alan Gilbert | 4900116 | 2014-01-30 10:20:32 +0000 | [diff] [blame] | 539 | qemu_thread_create(&card->event_thread_id, "ccid/event", event_thread, |
| 540 | card, QEMU_THREAD_JOINABLE); |
| 541 | qemu_thread_create(&card->apdu_thread_id, "ccid/apdu", handle_apdu_thread, |
| 542 | card, QEMU_THREAD_JOINABLE); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 543 | return 0; |
| 544 | } |
| 545 | |
| 546 | static int emulated_exitfn(CCIDCardState *base) |
| 547 | { |
| 548 | EmulatedState *card = DO_UPCAST(EmulatedState, base, base); |
| 549 | VEvent *vevent = vevent_new(VEVENT_LAST, NULL, NULL); |
| 550 | |
| 551 | vevent_queue_vevent(vevent); /* stop vevent thread */ |
Paolo Bonzini | da5361c | 2011-12-12 17:21:34 +0100 | [diff] [blame] | 552 | qemu_thread_join(&card->event_thread_id); |
| 553 | |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 554 | card->quit_apdu_thread = 1; /* stop handle_apdu thread */ |
| 555 | qemu_cond_signal(&card->handle_apdu_cond); |
Paolo Bonzini | da5361c | 2011-12-12 17:21:34 +0100 | [diff] [blame] | 556 | qemu_thread_join(&card->apdu_thread_id); |
| 557 | |
| 558 | /* threads exited, can destroy all condvars/mutexes */ |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 559 | qemu_cond_destroy(&card->handle_apdu_cond); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 560 | qemu_mutex_destroy(&card->handle_apdu_mutex); |
| 561 | qemu_mutex_destroy(&card->vreader_mutex); |
| 562 | qemu_mutex_destroy(&card->event_list_mutex); |
| 563 | return 0; |
| 564 | } |
| 565 | |
Anthony Liguori | 39bffca | 2011-12-07 21:34:16 -0600 | [diff] [blame] | 566 | static Property emulated_card_properties[] = { |
| 567 | DEFINE_PROP_STRING("backend", EmulatedState, backend_str), |
| 568 | DEFINE_PROP_STRING("cert1", EmulatedState, cert1), |
| 569 | DEFINE_PROP_STRING("cert2", EmulatedState, cert2), |
| 570 | DEFINE_PROP_STRING("cert3", EmulatedState, cert3), |
| 571 | DEFINE_PROP_STRING("db", EmulatedState, db), |
| 572 | DEFINE_PROP_UINT8("debug", EmulatedState, debug, 0), |
| 573 | DEFINE_PROP_END_OF_LIST(), |
| 574 | }; |
| 575 | |
Anthony Liguori | ba7c052 | 2011-12-04 12:34:10 -0600 | [diff] [blame] | 576 | static void emulated_class_initfn(ObjectClass *klass, void *data) |
| 577 | { |
Anthony Liguori | 39bffca | 2011-12-07 21:34:16 -0600 | [diff] [blame] | 578 | DeviceClass *dc = DEVICE_CLASS(klass); |
Anthony Liguori | ba7c052 | 2011-12-04 12:34:10 -0600 | [diff] [blame] | 579 | CCIDCardClass *cc = CCID_CARD_CLASS(klass); |
| 580 | |
| 581 | cc->initfn = emulated_initfn; |
| 582 | cc->exitfn = emulated_exitfn; |
| 583 | cc->get_atr = emulated_get_atr; |
| 584 | cc->apdu_from_guest = emulated_apdu_from_guest; |
Marcel Apfelbaum | 125ee0e | 2013-07-29 17:17:45 +0300 | [diff] [blame] | 585 | set_bit(DEVICE_CATEGORY_INPUT, dc->categories); |
Anthony Liguori | 39bffca | 2011-12-07 21:34:16 -0600 | [diff] [blame] | 586 | dc->desc = "emulated smartcard"; |
| 587 | dc->props = emulated_card_properties; |
Anthony Liguori | ba7c052 | 2011-12-04 12:34:10 -0600 | [diff] [blame] | 588 | } |
| 589 | |
Andreas Färber | 8c43a6f | 2013-01-10 16:19:07 +0100 | [diff] [blame] | 590 | static const TypeInfo emulated_card_info = { |
Anthony Liguori | 39bffca | 2011-12-07 21:34:16 -0600 | [diff] [blame] | 591 | .name = EMULATED_DEV_NAME, |
| 592 | .parent = TYPE_CCID_CARD, |
| 593 | .instance_size = sizeof(EmulatedState), |
| 594 | .class_init = emulated_class_initfn, |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 595 | }; |
| 596 | |
Andreas Färber | 83f7d43 | 2012-02-09 15:20:55 +0100 | [diff] [blame] | 597 | static void ccid_card_emulated_register_types(void) |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 598 | { |
Anthony Liguori | 39bffca | 2011-12-07 21:34:16 -0600 | [diff] [blame] | 599 | type_register_static(&emulated_card_info); |
Alon Levy | 585738a | 2010-10-24 12:09:18 +0200 | [diff] [blame] | 600 | } |
| 601 | |
Andreas Färber | 83f7d43 | 2012-02-09 15:20:55 +0100 | [diff] [blame] | 602 | type_init(ccid_card_emulated_register_types) |