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