From 331bfc4d476e9ce215e2100b43c75d7be408ae6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henryk=20Pl=C3=B6tz?= Date: Thu, 25 Feb 2010 16:43:54 +0100 Subject: [PATCH] Move all PC/SC specific stuff to a separate class structure for readers (thus preparing the way for non-PC/SC-readers) This commit should be known as "Holy excessive layering, batman!" --- cards/generic_card.py | 39 ++++--------- cyberflex-shell.py | 13 +++-- readers.py | 130 ++++++++++++++++++++++++++++++++++++++++++ utils.py | 68 ++-------------------- 4 files changed, 151 insertions(+), 99 deletions(-) create mode 100644 readers.py diff --git a/cards/generic_card.py b/cards/generic_card.py index bcc33cd..604cca3 100644 --- a/cards/generic_card.py +++ b/cards/generic_card.py @@ -130,12 +130,8 @@ class Card: } TLV_OBJECTS[TLV_utils.context_FCI] = TLV_OBJECTS[TLV_utils.context_FCP] - def __init__(self, card): - if not hasattr(card, "connection"): - self.card = card - else: - self.card = card.connection - self.cardservice = card + def __init__(self, reader): + self.reader = reader self._i = 0 self.last_apdu = None @@ -198,19 +194,13 @@ class Card: "show_applications": cmd_show_applications, } - PROTOMAP = { - 0: smartcard.scard.SCARD_PCI_T0, - 1: smartcard.scard.SCARD_PCI_T1, - } def _real_send(self, apdu): apdu_binary = apdu.render() if DEBUG: print ">> " + utils.hexdump(apdu_binary, indent = 3) - apdu_bytes = map(lambda x: ord(x), apdu_binary) - data, sw1, sw2 = self.card.transmit(apdu_bytes, protocol=self.PROTOMAP[self.get_protocol()]) - result_binary = map(lambda x: chr(x), data + [sw1,sw2]) + result_binary = self.reader.transceive(apdu_binary) result = R_APDU(result_binary) self.last_apdu = apdu @@ -258,20 +248,16 @@ class Card: if purpose is None: purpose = Card.PURPOSE_SUCCESS return self.match_statusword(self.STATUS_MAP[purpose], sw) - def _get_atr(card): - if hasattr(card, "connection"): - ATR = smartcard.util.toASCIIString(card.connection.getATR()) - else: - ATR = smartcard.util.toASCIIString(card.getATR()) - return ATR + def _get_atr(reader): + return reader.get_ATR() _get_atr = staticmethod(_get_atr) def get_atr(self): - return self._get_atr(self.card) + return self._get_atr(self.reader) - def can_handle(cls, card): + def can_handle(cls, reader): """Determine whether this class can handle a given card/connection object.""" - ATR = cls._get_atr(card) + ATR = cls._get_atr(reader) def match_list(atr, list): for (knownatr, mask) in list: if mask is None: @@ -330,9 +316,6 @@ class Card: else: return "%s (SW %s)" % (retval, binascii.b2a_hex(self.last_sw)) - def get_protocol(self): - hresult, reader, state, protocol, atr = smartcard.scard.SCardStatus( self.card.component.hcard ) - return ((protocol == smartcard.scard.SCARD_PROTOCOL_T0) and (0,) or (1,))[0] def get_driver_name(self): if len(self.DRIVER_NAME) > 1: @@ -343,8 +326,6 @@ class Card: def close_card(self): "Disconnect from a card" - self.card.disconnect() - if hasattr(self, "cardservice"): - del self.cardservice - del self.card + self.reader.disconnect() + del self.reader diff --git a/cyberflex-shell.py b/cyberflex-shell.py index 7d88ed9..35521da 100755 --- a/cyberflex-shell.py +++ b/cyberflex-shell.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: iso-8859-1 -*- -import crypto_utils, utils, cards, os, re, binascii, sys, exceptions, traceback, getopt, datetime +import crypto_utils, utils, cards, readers, os, re, binascii, sys, exceptions, traceback, getopt, datetime from shell import Shell class Logger(object): @@ -94,7 +94,8 @@ class Cyberflex_Shell(Shell): def cmd_listreaders(self): "List the available readers" - list_readers() + for i, (name, obj) in enumerate(readers.list_readers()): + print "%i: %s" % (i,name) def cmd_enc(self, *args): "Encrypt or decrypt with openssl-like interface" @@ -156,7 +157,7 @@ class Cyberflex_Shell(Shell): def cmd_atr(self, *args): """Print the ATR of the currently inserted card.""" - print "ATR: %s" % utils.hexdump(self.card.card.status()['ATR'], short=True) + print "ATR: %s" % utils.hexdump(self.card.reader.get_ATR(), short=True) def cmd_save_response(self, file_name, start = None, end = None): "Save the data in the last response to a file. start and end are optional" @@ -268,7 +269,7 @@ class Cyberflex_Shell(Shell): sys.stdout = self.logger print "Logging to %s" % filename try: - self.logger.println( "# ATR of currently inserted card is: %s" % utils.hexdump(self.card.get_atr(), short=True) ) + self.logger.println( "# ATR of currently inserted card is: %s" % utils.hexdump(self.card.reader.get_ATR(), short=True) ) except (KeyboardInterrupt, SystemExit): raise except: @@ -341,8 +342,8 @@ class Cyberflex_Shell(Shell): if reader is None: reader = self.reader - card_object = utils.CommandLineArgumentHelper.connect_to(reader) - self.card = cards.new_card_object(card_object) + reader_object = readers.connect_to(reader) + self.card = cards.new_card_object(reader_object) self.unregister_commands(self, self.NOCARD_COMMANDS) self.register_commands(self, self.CARD_COMMANDS) diff --git a/readers.py b/readers.py new file mode 100644 index 0000000..5086afa --- /dev/null +++ b/readers.py @@ -0,0 +1,130 @@ +try: + import smartcard, smartcard.CardRequest +except ImportError: + print >>sys.stderr, """Could not import smartcard module. Please install pyscard +from http://pyscard.sourceforge.net/ +If you can't install pyscard and want to continue using +pycsc you'll need to downgrade to SVN revision 246. +""" + raise +class Smartcard_Reader(object): + def list_readers(): + "Return a list of tuples: (reader name, implementing object)" + return [] + + def connect(self): + "Create a connection to this reader" + raise NotImplementedError, "Please implement in a sub-class" + + def get_ATR(self): + "Get the ATR of the inserted card as a binary string" + raise NotImplementedError, "Please implement in a sub-class" + + def transceive(self, data): + "Send a binary blob, receive a binary blob" + raise NotImplementedError, "Please implement in a sub-class" + + def disconnect(self): + "Disconnect from the card and release all resources" + raise NotImplementedError, "Please implement in a sub-class" + +class PCSC_Reader(Smartcard_Reader): + def __init__(self, reader): + self._reader = reader + self._name = str(reader) + self._cardservice = None + + name = property(lambda self: self._name, None, None, "The human readable name of the reader") + + def list_readers(cls): + return [ (str(r), cls(r)) for r in smartcard.System.readers() ] + list_readers = classmethod(list_readers) + + def connect(self): + unpatched = False + printed = False + while True: + try: + if not unpatched: + cardrequest = smartcard.CardRequest.CardRequest( readers=[self._reader], timeout=0.1 ) + else: + cardrequest = smartcard.CardRequest.CardRequest( readers=[self._reader], timeout=1 ) + + self._cardservice = cardrequest.waitforcard() + self._cardservice.connection.connect() + break + except TypeError: + unpatched = True + except (KeyboardInterrupt, SystemExit): + raise + except smartcard.Exceptions.CardRequestException: + if sys.exc_info()[1].message.endswith("Command timeout."): + if not printed: + print "Please insert card ..." + printed = True + else: + raise + except smartcard.Exceptions.CardRequestTimeoutException: + if not printed: + print "Please insert card ..." + printed = True + except smartcard.Exceptions.NoCardException: + print "Card is mute or absent. Please retry." + + def get_ATR(self): + return smartcard.util.toASCIIString(self._cardservice.connection.getATR()) + + def get_protocol(self): + hresult, reader, state, protocol, atr = smartcard.scard.SCardStatus( self._cardservice.connection.component.hcard ) + return ((protocol == smartcard.scard.SCARD_PROTOCOL_T0) and (0,) or (1,))[0] + + PROTOMAP = { + 0: smartcard.scard.SCARD_PCI_T0, + 1: smartcard.scard.SCARD_PCI_T1, + } + + def transceive(self, data): + data_bytes = map(lambda x: ord(x), data) + data, sw1, sw2 = self._cardservice.connection.transmit(data_bytes, protocol=self.PROTOMAP[self.get_protocol()]) + result_binary = map(lambda x: chr(x), data + [sw1,sw2]) + return result_binary + + def disconnect(self): + self._cardservice.connection.disconnect() + del self._cardservice + self._cardservice = None + +def list_readers(): + "Collect readers from all known drivers" + readers = PCSC_Reader.list_readers() + return readers + +def connect_to(reader): + "Open the connection to a reader" + + readerObject = None + readers = list_readers() + + if isinstance(reader, int) or reader.isdigit(): + reader = int(reader) + readerObject = readers[reader][1] + else: + for i, name, obj in readers: + if str(name).startswith(reader): + readerObject = obj + + if readerObject is None: + readerObject = readers[0][1] + + print "Using reader: %s" % readerObject.name + + readerObject.connect() + + from utils import hexdump + + print "ATR: %s" % hexdump(readerObject.get_ATR(), short = True) + return readerObject + +if __name__ == "__main__": + list_readers() + diff --git a/utils.py b/utils.py index 0162c46..df93196 100644 --- a/utils.py +++ b/utils.py @@ -1,13 +1,4 @@ -import string, binascii, sys, re, getopt -try: - import smartcard, smartcard.CardRequest -except ImportError: - print >>sys.stderr, """Could not import smartcard module. Please install pyscard -from http://pyscard.sourceforge.net/ -If you can't install pyscard and want to continue using -pycsc you'll need to downgrade to SVN revision 246. -""" - raise +import string, binascii, sys, re, getopt, readers class CommandLineArgumentHelper: OPTIONS = "r:l" @@ -15,65 +6,13 @@ class CommandLineArgumentHelper: exit_now = False reader = None - def list_readers(): - for index, name in enumerate(smartcard.System.readers()): - print "%i: %s" % (index, name) - list_readers = staticmethod(list_readers) - def connect(self): "Open the connection to a card" if self.reader is None: self.reader = 0 - return self.connect_to(self.reader) - - def connect_to(reader): - "Open the connection to a reader" - - readerObject = None - - if isinstance(reader, int) or reader.isdigit(): - reader = int(reader) - readerObject = smartcard.System.readers()[reader] - else: - for r in smartcard.System.readers(): - if str(r).startswith(reader): - readerObject = r - - if readerObject is None: - readerObject = smartcard.System.readers()[0] - - print "Using reader: %s" % readerObject - unpatched = False - printed = False - while True: - try: - if not unpatched: - cardrequest = smartcard.CardRequest.CardRequest( readers=[readerObject], timeout=0.1 ) - else: - cardrequest = smartcard.CardRequest.CardRequest( readers=[readerObject], timeout=1 ) - - cardservice = cardrequest.waitforcard() - cardservice.connection.connect() - break - except TypeError: - unpatched = True - except (KeyboardInterrupt, SystemExit): - raise - except smartcard.Exceptions.CardRequestException: - if sys.exc_info()[1].message.endswith("Command timeout."): - if not printed: - print "Please insert card ..." - printed = True - else: - raise - except smartcard.Exceptions.NoCardException: - print "Card is mute or absent. Please retry." - - print "ATR: %s" % hexdump(smartcard.util.toASCIIString(cardservice.connection.getATR()), short = True) - return cardservice - connect_to = staticmethod(connect_to) + return readers.connect_to(self.reader) def getopt(self, argv, opts="", long_opts=[]): "Wrapper around getopt.gnu_getopt. Handles common arguments, returns everything else." @@ -85,7 +24,8 @@ class CommandLineArgumentHelper: if option in ("-r","--reader"): self.reader = value elif option in ("-l","--list-readers"): - self.list_readers() + for i, (name, obj) in enumerate(readers.list_readers()): + print "%i: %s" % (i,name) self.exit_now = True else: unrecognized.append( (option, value) )