[Spice-devel] [PATCH v2 05/13] Add a VCARD_DIRECT implemention to the libcacard smartcard support

marcandre.lureau at redhat.com marcandre.lureau at redhat.com
Fri Oct 9 09:15:58 PDT 2015


From: Jeremy White <jwhite at codeweavers.com>

This uses libpcsclite to provide direct communication with a smartcard.

Signed-off-by: Jeremy White <jwhite at codeweavers.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau at redhat.com>
---
 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



More information about the Spice-devel mailing list