summaryrefslogtreecommitdiffstats
path: root/client/smartcard_channel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'client/smartcard_channel.cpp')
-rw-r--r--client/smartcard_channel.cpp449
1 files changed, 449 insertions, 0 deletions
diff --git a/client/smartcard_channel.cpp b/client/smartcard_channel.cpp
new file mode 100644
index 00000000..d585c9a5
--- /dev/null
+++ b/client/smartcard_channel.cpp
@@ -0,0 +1,449 @@
+#include <spice/enums.h>
+
+#include "client/red_client.h"
+#include "mutex.h"
+
+extern "C" {
+#include "vscard_common.h"
+#include "vreader.h"
+#include "vcard_emul.h"
+#include "vevent.h"
+}
+
+#include "smartcard_channel.h"
+
+#define APDUBufSize 270
+
+#define MAX_ATR_LEN 40
+
+//#define DEBUG_SMARTCARD
+
+#ifdef DEBUG_SMARTCARD
+void PrintByteArray(uint8_t *arrBytes, unsigned int nSize)
+{
+ int i;
+
+ for (i=0; i < nSize; i++) {
+ DBG(0, "%X ", arrBytes[i]);
+ }
+ DBG(0, "\n");
+}
+#define DEBUG_PRINT_BYTE_ARRAY(arrBytes, nSize) PrintByteArray(arrBytes, nSize)
+#else
+#define DEBUG_PRINT_BYTE_ARRAY(arrBytes, nSize)
+#endif
+
+SmartCardChannel* g_smartcard_channel = NULL; // used for insert/remove of virtual card. Can be
+ // changed if we let Application store the SmartCard instance.
+
+class SmartCardHandler: public MessageHandlerImp<SmartCardChannel, SPICE_CHANNEL_SMARTCARD> {
+public:
+ SmartCardHandler(SmartCardChannel& channel)
+ : MessageHandlerImp<SmartCardChannel, SPICE_CHANNEL_SMARTCARD>(channel) {}
+};
+
+
+SmartCardChannel::SmartCardChannel(RedClient& client, uint32_t id)
+ : RedChannel(client, SPICE_CHANNEL_SMARTCARD, id, new SmartCardHandler(*this))
+{
+ SmartCardHandler* handler = static_cast<SmartCardHandler*>(get_message_handler());
+
+ g_smartcard_channel = this;
+ handler->set_handler(SPICE_MSG_SMARTCARD_DATA,
+ &SmartCardChannel::handle_smartcard_data);
+}
+
+ReaderData* SmartCardChannel::reader_data_from_vreader(VReader* vreader)
+{
+ if (vreader == NULL) {
+ assert(_readers_by_vreader.size() > 0);
+ return _readers_by_vreader.begin()->second;
+ }
+ if (_readers_by_vreader.count(vreader) > 0) {
+ return _readers_by_vreader.find(vreader)->second;
+ }
+ assert(_unallocated_readers_by_vreader.count(vreader) > 0);
+ return _unallocated_readers_by_vreader.find(vreader)->second;
+}
+
+ReaderData* SmartCardChannel::reader_data_from_reader_id(reader_id_t reader_id)
+{
+ if (_readers_by_id.count(reader_id) > 0) {
+ return _readers_by_id.find(reader_id)->second;
+ }
+ return NULL;
+}
+
+/** On VEVENT_READER_INSERT we call this, send a VSC_ReaderAdd, and wait for a VSC_ReaderAddResponse
+ */
+void SmartCardChannel::add_unallocated_reader(VReader* vreader, const char* name)
+{
+ ReaderData* data = new ReaderData();
+
+ data->vreader = vreader;
+ data->reader_id = VSCARD_UNDEFINED_READER_ID;
+ data->name = spice_strdup(name);
+ _unallocated_readers_by_vreader.insert(std::pair<VReader*, ReaderData*>(vreader, data));
+}
+
+/** called upon the VSC_ReaderAddResponse
+ */
+ReaderData* SmartCardChannel::add_reader(reader_id_t reader_id)
+{
+ ReaderData* data;
+ size_t unallocated = _unallocated_readers_by_vreader.size();
+
+ assert(unallocated > 0);
+ data = _unallocated_readers_by_vreader.begin()->second;
+ data->reader_id = reader_id;
+ _readers_by_vreader.insert(
+ std::pair<VReader*, ReaderData*>(data->vreader, data));
+ assert(_readers_by_vreader.count(data->vreader) == 1);
+ _readers_by_id.insert(std::pair<reader_id_t, ReaderData*>(reader_id, data));
+ assert(_readers_by_id.count(reader_id) == 1);
+ _unallocated_readers_by_vreader.erase(_unallocated_readers_by_vreader.begin());
+ assert(_unallocated_readers_by_vreader.size() == unallocated - 1);
+ assert(_unallocated_readers_by_vreader.count(data->vreader) == 0);
+ return data;
+}
+
+void* SmartCardChannel::cac_card_events_thread_entry(void* data)
+{
+ static_cast<SmartCardChannel*>(data)->cac_card_events_thread_main();
+ return NULL;
+}
+
+VEventEvent::VEventEvent(SmartCardChannel* smartcard_channel, VEvent* vevent)
+ : _smartcard_channel(smartcard_channel)
+ , _vreader(vevent->reader)
+ , _vevent(vevent)
+{
+}
+
+VEventEvent::~VEventEvent()
+{
+ vevent_delete(_vevent);
+}
+
+void ReaderAddEvent::response(AbstractProcessLoop& events_loop)
+{
+ static int num = 0;
+ char name[20];
+
+ sprintf(name, "test%4d", num++);
+ _smartcard_channel->add_unallocated_reader(_vreader, name);
+ _smartcard_channel->send_reader_added(name);
+}
+
+void ReaderRemoveEvent::response(AbstractProcessLoop& events_loop)
+{
+ ReaderData* data;
+
+ data = _smartcard_channel->reader_data_from_vreader(_vreader);
+ _smartcard_channel->send_reader_removed(data->reader_id);
+ _smartcard_channel->remove_reader(data);
+}
+
+void CardInsertEvent::response(AbstractProcessLoop& events_loop)
+{
+ ReaderData* data = _smartcard_channel->reader_data_from_vreader(
+ _vreader);
+
+ if (data->reader_id == VSCARD_UNDEFINED_READER_ID) {
+ data->card_insert_pending = true;
+ } else {
+ _smartcard_channel->send_atr(_vreader);
+ }
+}
+
+void CardRemoveEvent::response(AbstractProcessLoop& events_loop)
+{
+ ReaderData* data = _smartcard_channel->reader_data_from_vreader(
+ _vreader);
+
+ ASSERT(data->reader_id != VSCARD_UNDEFINED_READER_ID);
+ _smartcard_channel->send_message(data->reader_id, VSC_CardRemove, NULL, 0);
+}
+
+void SmartCardChannel::remove_reader(ReaderData* data)
+{
+ // TODO - untested code (caccard doesn't produce these events yet)
+ if (_readers_by_vreader.count(data->vreader) > 0) {
+ _readers_by_vreader.erase(_readers_by_vreader.find(data->vreader));
+ _readers_by_id.erase(_readers_by_id.find(data->reader_id));
+ } else {
+ _unallocated_readers_by_vreader.erase(
+ _unallocated_readers_by_vreader.find(data->vreader));
+ }
+ free(data->name);
+ delete data;
+}
+
+void SmartCardChannel::cac_card_events_thread_main()
+{
+ VEvent *vevent = NULL;
+ bool cont = true;
+
+ while (cont) {
+ vevent = vevent_wait_next_vevent();
+ if (vevent == NULL) {
+ break;
+ }
+ switch (vevent->type) {
+ case VEVENT_READER_INSERT:
+ LOG_INFO("VEVENT_READER_INSERT");
+ {
+ AutoRef<ReaderAddEvent> event(new ReaderAddEvent(this, vevent));
+ get_client().push_event(*event);
+ }
+ break;
+ case VEVENT_READER_REMOVE:
+ LOG_INFO("VEVENT_READER_REMOVE");
+ {
+ AutoRef<ReaderRemoveEvent> event(new ReaderRemoveEvent(this, vevent));
+ get_client().push_event(*event);
+ }
+ break;
+ case VEVENT_CARD_INSERT:
+ LOG_INFO("VEVENT_CARD_INSERT");
+ {
+ AutoRef<CardInsertEvent> event(new CardInsertEvent(this, vevent));
+ get_client().push_event(*event);
+ }
+ break;
+ case VEVENT_CARD_REMOVE:
+ LOG_INFO("VEVENT_CARD_REMOVE");
+ {
+ AutoRef<CardRemoveEvent> event(new CardRemoveEvent(this, vevent));
+ get_client().push_event(*event);
+ }
+ break;
+ case VEVENT_LAST:
+ cont = false;
+ default:
+ /* anything except VEVENT_LAST and default
+ * gets to VEventEvent which does vevent_delete in VEventEvent~ */
+ vevent_delete(vevent);
+ }
+ }
+}
+
+#define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb"
+#define CERTIFICATES_ARGS_TEMPLATE "db=\"%s\" use_hw=no soft=(,Virtual Card,CAC,,%s,%s,%s)"
+
+SmartcardOptions::SmartcardOptions() :
+dbname(CERTIFICATES_DEFAULT_DB),
+enable(false)
+{
+}
+
+static VCardEmulError init_vcard_local_certs(const char* dbname, const char* cert1,
+ const char* cert2, const char* cert3)
+{
+ char emul_args[200];
+ VCardEmulOptions *options = NULL;
+
+ snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE,
+ dbname, cert1, cert2, cert3);
+ options = vcard_emul_options(emul_args);
+ if (options == NULL) {
+ LOG_WARN("not using certificates due to initialization error");
+ }
+ return vcard_emul_init(options);
+}
+
+static bool g_vcard_inited = false;
+
+void smartcard_init(const SmartcardOptions* options)
+{
+ if (g_vcard_inited) {
+ return;
+ }
+ if (options->certs.size() == 3) {
+ const char* dbname = options->dbname.c_str();
+ if (init_vcard_local_certs(dbname, options->certs[0].c_str(),
+ options->certs[1].c_str(), options->certs[2].c_str()) != VCARD_EMUL_OK) {
+ throw Exception("smartcard: emulated card initialization failed (check certs/db)");
+ }
+ } else {
+ if (options->certs.size() > 0) {
+ LOG_WARN("Ignoring smartcard certificates - must be exactly three for virtual card emulation");
+ }
+ if (vcard_emul_init(NULL) != VCARD_EMUL_OK) {
+ throw Exception("smartcard: vcard initialization failed (check hardware/drivers)");
+ }
+ }
+ g_vcard_inited = true;
+}
+
+void SmartCardChannel::on_connect()
+{
+ _event_thread = new Thread(SmartCardChannel::cac_card_events_thread_entry, this);
+}
+
+void SmartCardChannel::on_disconnect()
+{
+ VEvent *stop_event;
+
+ if (_event_thread == NULL) {
+ return;
+ }
+ stop_event = vevent_new(VEVENT_LAST, NULL, NULL);
+ vevent_queue_vevent(stop_event);
+ _event_thread->join();
+ delete _event_thread;
+ _event_thread = NULL;
+}
+
+
+void SmartCardChannel::send_reader_removed(reader_id_t reader_id)
+{
+ send_message(reader_id, VSC_ReaderRemove, NULL, 0);
+}
+
+void SmartCardChannel::send_reader_added(const char* reader_name)
+{
+ send_message(VSCARD_UNDEFINED_READER_ID,
+ VSC_ReaderAdd, (uint8_t*)reader_name, strlen(reader_name));
+}
+
+void SmartCardChannel::send_atr(VReader* vreader)
+{
+ unsigned char atr[ MAX_ATR_LEN];
+ int atr_len = MAX_ATR_LEN;
+ reader_id_t reader_id = reader_data_from_vreader(vreader)->reader_id;
+
+ assert(reader_id != VSCARD_UNDEFINED_READER_ID);
+ vreader_power_on(vreader, atr, &atr_len);
+ DBG(0, "ATR: ");
+ DEBUG_PRINT_BYTE_ARRAY(atr, atr_len);
+ send_message(reader_id, VSC_ATR, (uint8_t*)atr, atr_len);
+}
+
+void SmartCardChannel::send_message(uint32_t reader_id, VSCMsgType type, uint8_t* data, uint32_t len)
+{
+ VSCMsgHeader mhHeader;
+ Message* msg = new Message(SPICE_MSGC_SMARTCARD_DATA);
+ SpiceMarshaller* m = msg->marshaller();
+
+ mhHeader.type = type;
+ mhHeader.length = len;
+ mhHeader.reader_id = reader_id;
+ spice_marshaller_add(m, (uint8_t*)&mhHeader, sizeof(mhHeader));
+ spice_marshaller_add(m, data, len);
+ post_message(msg);
+}
+
+VSCMessageEvent::VSCMessageEvent(SmartCardChannel* smartcard_channel,
+ VSCMsgHeader* vheader)
+ : _smartcard_channel(smartcard_channel)
+ , _vheader(NULL)
+{
+ _vheader = (VSCMsgHeader*)spice_memdup(vheader,
+ sizeof(VSCMsgHeader) + vheader->length);
+ ASSERT(_vheader);
+}
+
+VSCMessageEvent::~VSCMessageEvent()
+{
+ free(_vheader);
+}
+
+void VSCMessageEvent::response(AbstractProcessLoop& loop)
+{
+ static int recv_count = 0;
+ int dwSendLength;
+ int dwRecvLength;
+ uint8_t* pbSendBuffer = _vheader->data;
+ uint8_t pbRecvBuffer[APDUBufSize+sizeof(uint32_t)];
+ VReaderStatus reader_status;
+ uint32_t rv;
+ ReaderData* data;
+
+ switch (_vheader->type) {
+ case (VSC_ReaderAddResponse):
+ data = _smartcard_channel->add_reader(_vheader->reader_id);
+ if (data->card_insert_pending) {
+ data->card_insert_pending = false;
+ _smartcard_channel->send_atr(data->vreader);
+ }
+ return;
+ break;
+ case VSC_APDU:
+ break;
+ case VSC_Error:
+ {
+ VSCMsgError *error = (VSCMsgError*)_vheader->data;
+ LOG_WARN("VSC Error: reader %d, code %d",
+ _vheader->reader_id, error->code);
+ }
+ return;
+ default:
+ LOG_WARN("unhandled VSC %d of length %d, reader %d",
+ _vheader->type, _vheader->length, _vheader->reader_id);
+ return;
+ }
+
+ /* Transmit recieved APDU */
+ dwSendLength = _vheader->length;
+ dwRecvLength = sizeof(pbRecvBuffer);
+
+ DBG(0, " %3d: recv APDU: ", recv_count++);
+ DEBUG_PRINT_BYTE_ARRAY(pbSendBuffer, _vheader->length);
+
+ ReaderData* reader_data = _smartcard_channel->reader_data_from_reader_id(
+ _vheader->reader_id);
+ if (reader_data == NULL) {
+ LOG_WARN("got message for non existant reader");
+ return;
+ }
+
+ VReader* vreader = reader_data->vreader;
+
+ reader_status = vreader_xfr_bytes(vreader,
+ pbSendBuffer, dwSendLength,
+ pbRecvBuffer, &dwRecvLength);
+ if (reader_status == VREADER_OK) {
+ DBG(0, " sent APDU: ");
+ DEBUG_PRINT_BYTE_ARRAY(pbRecvBuffer, dwRecvLength);
+ _smartcard_channel->send_message (
+ _vheader->reader_id,
+ VSC_APDU,
+ pbRecvBuffer,
+ dwRecvLength
+ );
+ } else {
+ rv = reader_status; /* warning: not meaningful */
+ _smartcard_channel->send_message (
+ _vheader->reader_id,
+ VSC_Error,
+ (uint8_t*)&rv,
+ sizeof (uint32_t)
+ );
+ }
+}
+
+void SmartCardChannel::handle_smartcard_data(RedPeer::InMessage* message)
+{
+ VSCMsgHeader* mhHeader = (VSCMsgHeader*)(message->data());
+
+ AutoRef<VSCMessageEvent> event(new VSCMessageEvent(this, mhHeader));
+ get_client().push_event(*event);
+}
+
+class SmartCardFactory: public ChannelFactory {
+public:
+ SmartCardFactory() : ChannelFactory(SPICE_CHANNEL_SMARTCARD) {}
+ virtual RedChannel* construct(RedClient& client, uint32_t id)
+ {
+ return new SmartCardChannel(client, id);
+ }
+};
+
+static SmartCardFactory factory;
+
+ChannelFactory& SmartCardChannel::Factory()
+{
+ return factory;
+}
+