From: Jeremy White <jwhite@xxxxxxxxxxxxxxx> This uses libpcsclite to provide direct communication with a smartcard. Signed-off-by: Jeremy White <jwhite@xxxxxxxxxxxxxxx> Reviewed-by: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> --- Makefile.am | 9 +- src/capcsc.c | 501 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/capcsc.h | 18 ++ src/card_7816.c | 2 +- src/card_7816.h | 4 +- src/libcacard.syms | 1 + src/vcard.c | 2 +- src/vcard.h | 2 +- src/vcard_emul_nss.c | 11 +- src/vcard_emul_type.c | 1 - 10 files changed, 542 insertions(+), 9 deletions(-) create mode 100644 src/capcsc.c create mode 100644 src/capcsc.h diff --git a/Makefile.am b/Makefile.am index da6dd8c..7951ec4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,6 +5,7 @@ lib_LTLIBRARIES = libcacard.la libcacard_la_SOURCES = \ src/cac.c \ + src/capcsc.h \ src/card_7816.c \ src/event.c \ src/glib-compat.h \ @@ -15,6 +16,10 @@ libcacard_la_SOURCES = \ src/vreader.c \ $(NULL) +if ENABLE_PCSC +libcacard_la_SOURCES += src/capcsc.c +endif + libcacard_includedir = $(includedir)/cacard libcacard_include_HEADERS = \ src/cac.h \ @@ -32,7 +37,7 @@ libcacard_include_HEADERS = \ src/vscard_common.h \ $(NULL) -libcacard_la_LIBADD = $(CACARD_LIBS) +libcacard_la_LIBADD = $(CACARD_LIBS) $(PCSC_LIBS) libcacard_la_LDFLAGS = \ -export-symbols $(srcdir)/src/libcacard.syms \ -no-undefined \ @@ -56,7 +61,7 @@ if OS_WIN32 vscclient_CFLAGS += -D__USE_MINGW_ANSI_STDIO=1 endif -AM_CPPFLAGS = $(CACARD_CFLAGS) $(WARN_CFLAGS) +AM_CPPFLAGS = $(CACARD_CFLAGS) $(PCSC_CFLAGS) $(WARN_CFLAGS) EXTRA_DIST = \ NEWS \ README.md \ diff --git a/src/capcsc.c b/src/capcsc.c new file mode 100644 index 0000000..937be06 --- /dev/null +++ b/src/capcsc.c @@ -0,0 +1,501 @@ +/* + * Supply a vreader using the PC/SC interface. + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include "glib-compat.h" +#include <string.h> +#include <stdio.h> + +#include "vcard.h" +#include "card_7816.h" +#include "capcsc.h" +#include "vreader.h" +#include "vevent.h" + +#include <PCSC/wintypes.h> +#include <PCSC/winscard.h> + + +typedef struct _PCSCContext PCSCContext; + +typedef struct { + PCSCContext *context; + int index; + char *name; + DWORD protocol; + DWORD state; + SCARDHANDLE card; + BYTE atr[MAX_ATR_SIZE]; + DWORD atrlen; + int card_connected; + unsigned long request_count; +} SCardReader; + +typedef struct _PCSCContext { + SCARDCONTEXT context; + SCardReader readers[CAPCSC_MAX_READERS]; + int reader_count; + int readers_changed; + GThread *thread; + CompatGMutex lock; +} PCSCContext; + + +static void delete_reader(PCSCContext *pc, int i) +{ + SCardReader *r = &pc->readers[i]; + g_free(r->name); + r->name = NULL; + + if (i < (pc->reader_count - 1)) { + int rem = pc->reader_count - i - 1; + memmove(&pc->readers[i], &pc->readers[i + 1], + sizeof(SCardReader) * rem); + } + + pc->reader_count--; +} + +static void delete_reader_cb(VReaderEmul *ve) +{ + SCardReader *r = (SCardReader *) ve; + + g_mutex_lock(&r->context->lock); + delete_reader(r->context, r->index); + g_mutex_unlock(&r->context->lock); +} + +static int new_reader(PCSCContext *pc, const char *name, DWORD state) +{ + SCardReader *r; + VReader *vreader; + + if (pc->reader_count >= CAPCSC_MAX_READERS - 1) { + return 1; + } + + r = &pc->readers[pc->reader_count]; + memset(r, 0, sizeof(*r)); + r->index = pc->reader_count++; + r->context = pc; + r->name = g_strdup(name); + + vreader = vreader_new(name, (VReaderEmul *) r, delete_reader_cb); + vreader_add_reader(vreader); + vreader_free(vreader); + + return 0; +} + +static int find_reader(PCSCContext *pc, const char *name) +{ + int i; + for (i = 0; i < pc->reader_count; i++) + if (strcmp(pc->readers[i].name, name) == 0) { + return i; + } + + return -1; +} + + +static int scan_for_readers(PCSCContext *pc) +{ + LONG rc; + + int i; + char buf[8192]; + DWORD buflen = sizeof(buf); + + char *p; + int matches[CAPCSC_MAX_READERS] = { 0, }; + + g_mutex_lock(&pc->lock); + + pc->readers_changed = 1; + memset(buf, 0, sizeof(buf)); + rc = SCardListReaders(pc->context, NULL, buf, &buflen); + if (rc == SCARD_E_NO_READERS_AVAILABLE) { + rc = 0; + goto exit; + } + + if (rc != SCARD_S_SUCCESS) { + fprintf(stderr, "SCardListReaders failed: %s (0x%lX)\n", + pcsc_stringify_error(rc), rc); + goto exit; + } + + for (p = buf; p && p < buf + sizeof(buf); p += (strlen(p) + 1)) { + if (strlen(p) > 0) { + i = find_reader(pc, p); + if (i >= 0) { + matches[i]++; + } else { + if (!new_reader(pc, p, SCARD_STATE_UNAWARE)) { + matches[pc->reader_count - 1]++; + } + } + } + } + + rc = 0; + +exit: + i = pc->reader_count - 1; + g_mutex_unlock(&pc->lock); + + for (; i >= 0; i--) { + if (!matches[i]) { + VReader *reader = vreader_get_reader_by_name(pc->readers[i].name); + if (reader) { + vreader_free(reader); + vreader_remove_reader(reader); + } + } + } + + + return rc; +} + +static int init_pcsc(PCSCContext *pc) +{ + LONG rc; + + memset(pc, 0, sizeof(*pc)); + + rc = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &pc->context); + if (rc != SCARD_S_SUCCESS) { + fprintf(stderr, "SCardEstablishContext: " + "Cannot Connect to Resource Manager %lX\n", rc); + return rc; + } + + return 0; +} + + +static void prepare_reader_states(PCSCContext *pc, SCARD_READERSTATE **states, + DWORD *reader_count) +{ + SCARD_READERSTATE *state; + int i; + + if (*states) { + g_free(*states); + } + + *reader_count = pc->reader_count; + + (*reader_count)++; + *states = g_malloc((*reader_count) * sizeof(**states)); + memset(*states, 0, sizeof((*reader_count) * sizeof(**states))); + + for (i = 0, state = *states; i < pc->reader_count; i++, state++) { + state->szReader = pc->readers[i].name; + state->dwCurrentState = pc->readers[i].state; + } + + /* Leave a space to be notified of new readers */ + state->szReader = "\\\\?PnP?\\Notification"; + state->dwCurrentState = SCARD_STATE_UNAWARE; +} + +static int connect_card(SCardReader *r) +{ + LONG rc; + + r->protocol = -1; + rc = SCardConnect(r->context->context, r->name, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, + &r->card, &r->protocol); + if (rc != SCARD_S_SUCCESS) { + fprintf(stderr, "Failed to connect to a card reader: %s (0x%lX)\n", + pcsc_stringify_error(rc), rc); + return rc; + } + + r->card_connected = 1; + r->request_count = 0; + + return 0; +} + +static LONG send_receive(SCardReader *r, BYTE *transmit, DWORD transmit_len, + BYTE *receive, DWORD *receive_len) +{ + const SCARD_IO_REQUEST *send_header; + SCARD_IO_REQUEST receive_header; + LONG rc; + + if (!r->card_connected) { + rc = connect_card(r); + if (rc) { + return rc; + } + } + + if (r->protocol == SCARD_PROTOCOL_T0) { + send_header = SCARD_PCI_T0; + } else if (r->protocol == SCARD_PROTOCOL_T1) { + send_header = SCARD_PCI_T1; + } else { + fprintf(stderr, "Unknown protocol %lX\n", r->protocol); + return 1; + } + + rc = SCardTransmit(r->card, send_header, transmit, transmit_len, + &receive_header, receive, receive_len); + if (rc != SCARD_S_SUCCESS) { + fprintf(stderr, "Failed to transmit %ld bytes: %s (0x%lX)\n", + transmit_len, pcsc_stringify_error(rc), rc); + return rc; + } + + return 0; +} + + +static VCardStatus apdu_cb(VCard *card, VCardAPDU *apdu, + VCardResponse **response) +{ + VCardStatus ret = VCARD_DONE; + SCardReader *r = (SCardReader *) vcard_get_private(card); + BYTE outbuf[4096]; + DWORD outlen = sizeof(outbuf); + LONG rc; + + rc = send_receive(r, apdu->a_data, apdu->a_len, outbuf, &outlen); + if (rc || outlen < 2) { + ret = VCARD_FAIL; + } else { + *response = vcard_response_new_data(outbuf, outlen - 2); + if (*response == NULL) { + return VCARD_FAIL; + } + vcard_response_set_status_bytes(*response, outbuf[outlen - 2], + outbuf[outlen - 1]); + } + + return ret; +} + +static VCardStatus reset_cb(VCard *card, int channel) +{ + SCardReader *r = (SCardReader *) vcard_get_private(card); + LONG rc; + + /* vreader_power_on is a bit too free with it's resets. + And a reconnect is expensive; as much as 10-20 seconds. + Hence, we discard any initial reconnect request. */ + if (r->request_count++ == 0) { + return VCARD_DONE; + } + + rc = SCardReconnect(r->card, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, + SCARD_RESET_CARD, &r->protocol); + if (rc != SCARD_S_SUCCESS) { + fprintf(stderr, "Failed to reconnect to a card reader: %s (0x%lX)\n", + pcsc_stringify_error(rc), rc); + return VCARD_FAIL; + } + return VCARD_DONE; +} + +static void get_atr_cb(VCard *card, unsigned char *atr, int *atr_len) +{ + SCardReader *r = (SCardReader *) vcard_get_private(card); + *atr_len = r->atrlen; + if (atr) { + memcpy(atr, r->atr, r->atrlen); + } +} + +static void delete_card_cb(VCardEmul *ve) +{ + fprintf(stderr, "TODO, got a delete_card_cb\n"); +} + +static void insert_card(SCardReader *r, SCARD_READERSTATE *s) +{ + VReader *reader; + VCardApplet *applet; + VCard *card; + + memcpy(r->atr, s->rgbAtr, MIN(sizeof(r->atr), sizeof(s->rgbAtr))); + r->atrlen = s->cbAtr; + + reader = vreader_get_reader_by_name(r->name); + if (!reader) { + return; + } + + if (connect_card(r)) { + return; + } + + applet = + vcard_new_applet(apdu_cb, + reset_cb, + (const unsigned char *)CAPCSC_APPLET, + strlen(CAPCSC_APPLET)); + if (!applet) { + return; + } + + card = vcard_new((VCardEmul *) r, delete_card_cb); + if (!card) { + vcard_delete_applet(applet); + vreader_free(reader); + return; + } + + vcard_set_type(card, VCARD_DIRECT); + vcard_set_atr_func(card, get_atr_cb); + vcard_add_applet(card, applet); + + vreader_insert_card(reader, card); + vreader_free(reader); +} + +static void remove_card(SCardReader *r) +{ + LONG rc; + VReader *reader; + + memset(r->atr, 0, sizeof(r->atr)); + r->atrlen = 0; + + rc = SCardDisconnect(r->card, SCARD_LEAVE_CARD); + if (rc != SCARD_S_SUCCESS) { + fprintf(stderr, "Non fatal info:" + "failed to disconnect card reader: %s (0x%lX)\n", + pcsc_stringify_error(rc), rc); + } + r->card_connected = 0; + + reader = vreader_get_reader_by_name(r->name); + if (!reader) { + return; + } + + vreader_insert_card(reader, NULL); + vreader_free(reader); +} + +static void process_reader_change(SCardReader *r, SCARD_READERSTATE *s) +{ + if (s->dwEventState & SCARD_STATE_PRESENT) { + insert_card(r, s); + } else if (s->dwEventState & SCARD_STATE_EMPTY) { + remove_card(r); + } else { + fprintf(stderr, "Unexpected card state change from %lx to %lx:\n", + r->state, s->dwEventState); + } + + r->state = s->dwEventState & ~SCARD_STATE_CHANGED; +} + +/* + * This thread looks for card and reader insertions and puts events on the + * event queue. + */ +static gpointer event_thread(gpointer arg) +{ + PCSCContext *pc = (PCSCContext *) arg; + DWORD reader_count = 0; + SCARD_READERSTATE *reader_states = NULL; + LONG rc; + + scan_for_readers(pc); + + do { + DWORD i; + DWORD timeout = INFINITE; + + g_mutex_lock(&pc->lock); + if (pc->readers_changed) { + prepare_reader_states(pc, &reader_states, &reader_count); + timeout = 0; + } else if (reader_count > 1) { + timeout = 0; + } + + pc->readers_changed = 0; + g_mutex_unlock(&pc->lock); + + rc = SCardGetStatusChange(pc->context, timeout, reader_states, + reader_count); + + /* If we have a new reader, or an unknown reader, + rescan and go back and do it again */ + if ((rc == SCARD_S_SUCCESS && (reader_states[reader_count - 1].dwEventState & SCARD_STATE_CHANGED)) + || + rc == SCARD_E_UNKNOWN_READER) { + scan_for_readers(pc); + continue; + } + + if (rc != SCARD_S_SUCCESS && rc != SCARD_E_TIMEOUT) { + fprintf(stderr, "Unexpected SCardGetStatusChange ret %lx(%s)\n", + rc, pcsc_stringify_error(rc)); + continue; + } + + g_mutex_lock(&pc->lock); + + for (i = 0; i < reader_count; i++) { + if (reader_states[i].dwEventState & SCARD_STATE_CHANGED) { + process_reader_change(&pc->readers[i], &reader_states[i]); + pc->readers_changed++; + } + + } + g_mutex_unlock(&pc->lock); + + /* libpcsclite is only thread safe at a high level. If we constantly + hold long calls into SCardGetStatusChange, we'll starve any running + clients. So, if we have an active session, and nothing has changed + on our front, we just idle. */ + if (!pc->readers_changed && reader_count > 1) { + g_usleep(CAPCSC_POLL_TIME * 1000); + } + + + } while (1); + + return NULL; +} + +/* + * We poll the PC/SC interface, looking for device changes + */ +static int new_event_thread(PCSCContext *pc) +{ + pc->thread = g_thread_new("capcsc_event_thread", event_thread, pc); + return pc->thread == NULL; +} + + +static PCSCContext context; + +int capcsc_init(void) +{ + g_mutex_init(&context.lock); + + if (init_pcsc(&context)) { + return -1; + } + + if (new_event_thread(&context)) { + return -1; + } + + return 0; +} diff --git a/src/capcsc.h b/src/capcsc.h new file mode 100644 index 0000000..bb59a4e --- /dev/null +++ b/src/capcsc.h @@ -0,0 +1,18 @@ +/* + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ +#ifndef CAPCSC_H +#define CAPCSC_H 1 + +#define CAPCSC_POLL_TIME 50 /* ms - Time we will poll for */ + /* card change when a */ + /* reader is connected */ +#define CAPCSC_MAX_READERS 16 + +#define CAPCSC_APPLET "CAPCSC APPLET" + +int capcsc_init(void); + + +#endif diff --git a/src/card_7816.c b/src/card_7816.c index 106e41f..d168e06 100644 --- a/src/card_7816.c +++ b/src/card_7816.c @@ -33,7 +33,7 @@ vcard_response_set_status(VCardResponse *response, vcard_7816_status_t status) /* * set the status bytes in a response buffer */ -static void +void vcard_response_set_status_bytes(VCardResponse *response, unsigned char sw1, unsigned char sw2) { diff --git a/src/card_7816.h b/src/card_7816.h index d191762..30b6d09 100644 --- a/src/card_7816.h +++ b/src/card_7816.h @@ -31,8 +31,8 @@ VCardResponse *vcard_make_response(vcard_7816_status_t status); /* create a raw response (status has already been encoded */ VCardResponse *vcard_response_new_data(unsigned char *buf, int len); - - +void vcard_response_set_status_bytes(VCardResponse *response, + unsigned char sw1, unsigned char sw2); /* * destructor for VCardResponse. diff --git a/src/libcacard.syms b/src/libcacard.syms index 2f9d423..84dd2a5 100644 --- a/src/libcacard.syms +++ b/src/libcacard.syms @@ -40,6 +40,7 @@ vcard_response_new vcard_response_new_bytes vcard_response_new_data vcard_response_new_status_bytes +vcard_response_set_status_bytes vcard_select_applet vcard_set_applet_private vcard_set_atr_func diff --git a/src/vcard.c b/src/vcard.c index 667f30a..2edf1d0 100644 --- a/src/vcard.c +++ b/src/vcard.c @@ -97,7 +97,7 @@ vcard_reset(VCard *card, VCardPower power) VCardApplet * vcard_new_applet(VCardProcessAPDU applet_process_function, VCardResetApplet applet_reset_function, - unsigned char *aid, int aid_len) + const unsigned char *aid, int aid_len) { VCardApplet *applet; diff --git a/src/vcard.h b/src/vcard.h index 16a23b5..1364dfb 100644 --- a/src/vcard.h +++ b/src/vcard.h @@ -30,7 +30,7 @@ void vcard_reset(VCard *card, VCardPower power); */ VCardApplet *vcard_new_applet(VCardProcessAPDU applet_process_function, VCardResetApplet applet_reset_function, - unsigned char *aid, int aid_len); + const unsigned char *aid, int aid_len); /* * destructor for a VCardApplet diff --git a/src/vcard_emul_nss.c b/src/vcard_emul_nss.c index 7ffa0b4..d046ea9 100644 --- a/src/vcard_emul_nss.c +++ b/src/vcard_emul_nss.c @@ -9,6 +9,7 @@ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. * See the COPYING file in the top-level directory. */ +#include "config.h" /* * NSS headers @@ -1253,7 +1254,12 @@ vcard_emul_options(const char *args) opts->hw_card_type = VCARD_EMUL_CAC; opts->use_hw = PR_TRUE; args = find_blank(args + 7); - /* nssemul */ +#if defined(ENABLE_PCSC) + } else if (strncmp(args, "passthru", 8) == 0) { + opts->hw_card_type = VCARD_EMUL_PASSTHRU; + opts->use_hw = PR_TRUE; + args = find_blank(args + 8); +#endif } else { fprintf(stderr, "Error: Unknown smartcard specification.\n"); return NULL; @@ -1273,6 +1279,9 @@ vcard_emul_usage(void) " hw_type={card_type_to_emulate} (default CAC)\n" " hw_param={param_for_card} (default \"\")\n" " nssemul (alias for use_hw=yes, hw_type=CAC)\n" +#if defined(ENABLE_PCSC) +" passthru (alias for use_hw=yes, hw_type=PASSTHRU)\n" +#endif " soft=({slot_name},{vreader_name},{card_type_to_emulate},{params_for_card},\n" " {cert1},{cert2},{cert3} (default none)\n" "\n" diff --git a/src/vcard_emul_type.c b/src/vcard_emul_type.c index 04e8d99..0c2fbb7 100644 --- a/src/vcard_emul_type.c +++ b/src/vcard_emul_type.c @@ -7,7 +7,6 @@ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. * See the COPYING file in the top-level directory. */ - #include <strings.h> #include "vcardt.h" #include "vcard_emul_type.h" -- 2.4.3 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel