From a2afcde061a488713119e03774915ea752757824 Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Wed, 15 Sep 2010 15:54:43 +0200 Subject: smartcard: client side (not enabled yet) --- client/application.cpp | 45 +++++ client/application.h | 12 ++ client/smartcard_channel.cpp | 449 +++++++++++++++++++++++++++++++++++++++++++ client/smartcard_channel.h | 133 +++++++++++++ 4 files changed, 639 insertions(+) create mode 100644 client/smartcard_channel.cpp create mode 100644 client/smartcard_channel.h diff --git a/client/application.cpp b/client/application.cpp index c380373f..ae197857 100644 --- a/client/application.cpp +++ b/client/application.cpp @@ -47,6 +47,10 @@ #include #include +#ifdef USE_SMARTCARD +#include +#endif + #define STICKY_KEY_PIXMAP ALT_IMAGE_RES_ID #define STICKY_KEY_TIMEOUT 750 @@ -364,6 +368,9 @@ Application::Application() #endif // USE_GUI , _during_host_switch(false) , _state (DISCONNECTED) +#ifdef USE_SMARTCARD + , _smartcard_options(new SmartcardOptions()) +#endif { DBG(0, ""); Platform::set_process_loop(*this); @@ -447,6 +454,9 @@ Application::~Application() _main_screen->unref(); destroy_monitors(); +#ifdef USE_SMARTCARD + delete _smartcard_options; +#endif } void Application::init_menu() @@ -2148,6 +2158,12 @@ void Application::register_channels() _client.register_channel_factory(TunnelChannel::Factory()); } #endif +#ifdef USE_SMARTCARD + if (_enabled_channels[SPICE_CHANNEL_SMARTCARD] && _smartcard_options->enable) { + smartcard_init(_smartcard_options); // throws Exception + _client.register_channel_factory(SmartCardChannel::Factory()); + } +#endif } bool Application::process_cmd_line(int argc, char** argv) @@ -2177,6 +2193,12 @@ bool Application::process_cmd_line(int argc, char** argv) SPICE_OPT_DISPLAY_COLOR_DEPTH, SPICE_OPT_DISABLE_DISPLAY_EFFECTS, SPICE_OPT_CONTROLLER, +#ifdef USE_SMARTCARD + SPICE_OPT_SMARTCARD, + SPICE_OPT_NOSMARTCARD, + SPICE_OPT_SMARTCARD_CERT, + SPICE_OPT_SMARTCARD_DB, +#endif }; #ifdef USE_GUI @@ -2235,6 +2257,15 @@ bool Application::process_cmd_line(int argc, char** argv) parser.add(SPICE_OPT_CONTROLLER, "controller", "enable external controller"); +#ifdef USE_SMARTCARD + parser.add(SPICE_OPT_SMARTCARD, "smartcard", "enable smartcard channel"); + parser.add(SPICE_OPT_NOSMARTCARD, "nosmartcard", "disable smartcard channel"); + parser.add(SPICE_OPT_SMARTCARD_CERT, "smartcard-cert", "Use virtual reader+card with given cert(s)", + "smartcard-cert", true); + parser.set_multi(SPICE_OPT_SMARTCARD_CERT, ','); + parser.add(SPICE_OPT_SMARTCARD_DB, "smartcard-db", "Use given db for smartcard certs"); +#endif + for (int i = SPICE_CHANNEL_MAIN; i < SPICE_END_CHANNEL; i++) { _peer_con_opt[i] = RedPeer::ConnectionOptions::CON_OP_INVALID; } @@ -2340,6 +2371,20 @@ bool Application::process_cmd_line(int argc, char** argv) } _enable_controller = true; return true; +#ifdef USE_SMARTCARD + case SPICE_OPT_SMARTCARD: + _smartcard_options->enable= true; + break; + case SPICE_OPT_NOSMARTCARD: + _smartcard_options->enable= false; // default + break; + case SPICE_OPT_SMARTCARD_CERT: + do { + _smartcard_options->certs.insert( + _smartcard_options->certs.end(), std::string(val)); + } while ((val=parser.next_argument())); + break; +#endif case CmdLineParser::OPTION_HELP: parser.show_help(); return false; diff --git a/client/application.h b/client/application.h index 5a5a488f..f9bbd53b 100644 --- a/client/application.h +++ b/client/application.h @@ -29,6 +29,9 @@ #include "foreign_menu.h" #include "controller.h" +#ifdef USE_SMARTCARD +struct SmartcardOptions; +#endif class RedScreen; class Application; class ScreenLayer; @@ -268,6 +271,12 @@ public: void message_box_test(); #endif +#ifdef USE_SMARTCARD + const SmartcardOptions* get_smartcard_options() const { + return _smartcard_options; + } +#endif + static int main(int argc, char** argv, const char* version_str); private: @@ -386,6 +395,9 @@ private: bool _during_host_switch; State _state; +#ifdef USE_SMARTCARD + SmartcardOptions* _smartcard_options; +#endif }; #endif 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 + +#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 { +public: + SmartCardHandler(SmartCardChannel& channel) + : MessageHandlerImp(channel) {} +}; + + +SmartCardChannel::SmartCardChannel(RedClient& client, uint32_t id) + : RedChannel(client, SPICE_CHANNEL_SMARTCARD, id, new SmartCardHandler(*this)) +{ + SmartCardHandler* handler = static_cast(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, 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(data->vreader, data)); + assert(_readers_by_vreader.count(data->vreader) == 1); + _readers_by_id.insert(std::pair(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(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 event(new ReaderAddEvent(this, vevent)); + get_client().push_event(*event); + } + break; + case VEVENT_READER_REMOVE: + LOG_INFO("VEVENT_READER_REMOVE"); + { + AutoRef event(new ReaderRemoveEvent(this, vevent)); + get_client().push_event(*event); + } + break; + case VEVENT_CARD_INSERT: + LOG_INFO("VEVENT_CARD_INSERT"); + { + AutoRef event(new CardInsertEvent(this, vevent)); + get_client().push_event(*event); + } + break; + case VEVENT_CARD_REMOVE: + LOG_INFO("VEVENT_CARD_REMOVE"); + { + AutoRef 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 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; +} + diff --git a/client/smartcard_channel.h b/client/smartcard_channel.h new file mode 100644 index 00000000..ee0d0d08 --- /dev/null +++ b/client/smartcard_channel.h @@ -0,0 +1,133 @@ +#ifndef __SMART_CARD_H__ +#define __SMART_CARD_H__ + +#include + +#include +#include +#include + +#include "red_channel.h" +#include "red_peer.h" + +class Application; + +struct SmartcardOptions { + std::vector certs; + std::string dbname; + bool enable; + SmartcardOptions(); +}; + +void smartcard_init(const SmartcardOptions* options); + +struct ReaderData { + ReaderData() : + vreader(NULL), + reader_id(VSCARD_UNDEFINED_READER_ID), + name(NULL), + card_insert_pending(false) + {} + VReader *vreader; + reader_id_t reader_id; + char* name; + bool card_insert_pending; +}; + +void virtual_card_remove(); +void virtual_card_insert(); + +class SmartCardChannel; + +class VEventEvent : public Event { +public: + VEventEvent(SmartCardChannel* smartcard_channel, VEvent* vevent); + ~VEventEvent(); + SmartCardChannel* _smartcard_channel; + VReader* _vreader; + VEvent* _vevent; +}; + +class ReaderAddEvent: public VEventEvent { +public: + ReaderAddEvent(SmartCardChannel* smartcard_channel, VEvent* vevent) + : VEventEvent(smartcard_channel, vevent) {} + virtual void response(AbstractProcessLoop& events_loop); +}; + +class ReaderRemoveEvent: public VEventEvent { +public: + ReaderRemoveEvent(SmartCardChannel* smartcard_channel, VEvent* vevent) + : VEventEvent(smartcard_channel, vevent) {} + virtual void response(AbstractProcessLoop& events_loop); +}; + +class CardInsertEvent: public VEventEvent { +public: + CardInsertEvent(SmartCardChannel* smartcard_channel, VEvent* vevent) + : VEventEvent(smartcard_channel, vevent) {} + virtual void response(AbstractProcessLoop& events_loop); +}; + +class CardRemoveEvent: public VEventEvent { +public: + CardRemoveEvent(SmartCardChannel* smartcard_channel, VEvent* vevent) + : VEventEvent(smartcard_channel, vevent) {} + virtual void response(AbstractProcessLoop& events_loop); +}; + +class VSCMessageEvent: public Event { +public: + VSCMessageEvent(SmartCardChannel* smartcard_channel, + VSCMsgHeader* vheader); + ~VSCMessageEvent(); + SmartCardChannel* _smartcard_channel; + VSCMsgHeader* _vheader; + virtual void response(AbstractProcessLoop& events_loop); +}; + +class SmartCardChannel : public RedChannel { + +public: + SmartCardChannel(RedClient& client, uint32_t id); + void handle_smartcard_data(RedPeer::InMessage* message); + + static ChannelFactory& Factory(); +protected: + virtual void on_connect(); + virtual void on_disconnect(); + +private: + static void* cac_card_events_thread_entry(void* data); + void cac_card_events_thread_main(); + void send_message(reader_id_t reader_id, VSCMsgType type, uint8_t* data, uint32_t len); + + Thread* _event_thread; + + Application* _app; + + VReaderList *_reader_list; + typedef std::map readers_by_id_t; + readers_by_id_t _readers_by_id; + typedef std::map readers_by_vreader_t; + readers_by_vreader_t _readers_by_vreader; + readers_by_vreader_t _unallocated_readers_by_vreader; + + ReaderData* reader_data_from_vreader(VReader* vreader); + ReaderData* reader_data_from_reader_id(reader_id_t reader_id); + void add_unallocated_reader(VReader* vreader, const char* name); + ReaderData* add_reader(reader_id_t reader_id); + void remove_reader(ReaderData* data); + void send_reader_added(const char* reader_name); + void send_reader_removed(reader_id_t reader_id); + void send_atr(VReader* vreader); + + friend class ReaderAddEvent; + friend class ReaderRemoveEvent; + friend class CardInsertEvent; + friend class CardRemoveEvent; + friend class VSCMessageEvent; +}; + +#endif // __SMART_CARD_H__ + -- cgit