At first, at the time of the idea, in 2014, this article was planned as a single publication, but after working through the material (too lazy forced to stretch this process), I realized that it was necessary to divide it into two parts:
I think the first part will be of more interest to the pros-cards, and the second part will be interesting, first of all, for newcomers to this topic (and will have the Tutorial tag).
Among the many Python libraries that have reviews on Habré, I did not find a pyscard , a library for interacting with smart cards.
In this article I will try to give a brief description of the main features of the pyscard , and for sweet we will write a simple command processor that works with the card through the APDU .
Please note that in order to understand how to use this library and the surrounding terminology, familiarity with the ISO 7816-4
or, at least, GSM 11.11
is required. It is easier to get official access to the GSM standard by downloading it from the ETSI website , by the way, and ISO 7816-4 (pdf, an old version) is googling, despite the fact that it is at the office. site want money).
Pyscard has existed since 2007 and is a cross-platform (win / mac / linux) add-on for PC / SC API .
My work environment where I use pyscard - Windows7
I tested the material of this article mainly on mac OS, but I also drove to Windows7, in a virtual machine. I should note that, unlike XP , the "seven" and, probably, the "ten", with the default settings, "puts a stick in the wheels" when working with the card in the reader:
These factors gave me a lot of pain when switching from XP, until I defeated them. How to do this, I will tell in the second part.
Development started under the auspices of one of the leading (and at the time of creation, and now) card market players.
Both Python branches are supported (2 and 3).
In the work environment, I use a bunch of pyscard + Python 2.7, but for the article, it seemed to me correct to use the currently relevant branch of Python (3.6)
In my opinion, the pyscard library is not designed very pythonic and looks more like a port of some Java framework, but this does not diminish its usefulness, at least for me, although the names of the modules look strange, of course.
The entry point to the library is the smartcard
package.
We should also mention the smartcard.scard
package, which is responsible for communicating with the operating system's card API. If you do not need all the abstractions of the library, but only bare PC/SC
, then you are here. We will not dwell on it in detail.
Installation of pyscard is possible in the following ways:
pip install pyscard
) - suitable for systems configured to build artifacts from sources, swig is used (ok for mac and, perhaps, linux)Pyscard has an informative user guide , which is available on the official website, pydoc and examples, so I see no reason to duplicate it all here. Instead, we:
smartcard.scard
, but a smartcard
;It’s time to throw in the portions of the code, otherwise all the boring introductory “bububu” ...
from smartcard.CardRequest import CardRequest cardrequest = CardRequest() # waitforcard(), cardservice = cardrequest.waitforcard() # APDU = [0xA0, 0xA4, 0, 0, 2] # SELECT GSM 11.11 # smartcard.CardConnection.CardConnection - with cardservice.connection as connection: connection.connect() # - data, sw1, sw2 = connection.transmit(APDU)
What tasks solves (almost any) program that works with smart cards in the reader? These are:
I note that the listed problems are solved, for example, by flashing a mobile phone.
smartcard
packageIn this section, all names are relative to the smartcard
package.
CardType
Allow us to specify the exact type of cards with which our application is going to work. You can make sure that our application does not even respond to placing a card in the reader that does not suit us.
Examples:
CardType.ATRCardType
(exists in the library) - filtering cards by ATR value. Our application will only respond to cards with a specific ATR value.USIMCardType
(I imagined, it can be implemented) - only USIM are valid cards, inside we check the possibility of choosing a USIM application.
CardRequest
and its subclassesAllow you to bring together all the requirements of our application relating to the establishment of communication with the card:
By default, no restrictions are set in CardRequest
.
CardConnection
The communication channel of our application with the card allows you to send APDUs to the card and receive an answer, the key method here is transmit()
. It is with its help that the direct interaction of our application with the map takes place. It should be noted that the transmit()
method always returns a triplet (tuple) consisting of:
None
, depending on the type of APDU, not all APDUs return data)SW1
SW2
CardConnection
is a context manager that adds convenience when using it.
CardConnectionDecorator
The word "decorator" is used here in the same context as in Java, and not in the one to which Python developers are accustomed.
Allows you to CardConnection
specific properties to the CardConnection
object. The library provides working decorators with speaking names: ExclusiveConnectCardConnection
and ExclusiveTransmitCardConnection
. Personally, I did not feel the effect of using these decorators - if the system (Windows) decided to interpose with its APDUs in our session, then none of these decorators will save, but maybe I did something wrong.
System.readers()
functionAllows you to get a list of card readers connected to the system and establish a connection with the card in a specific reader.
sw.ErrorChecker
, sw.ErrorCheckingChain
By default, during data exchange between the card and our application, no erroneous values ​​of the StatusWord (SW1, SW2) raise exceptions. This can be changed by ErrorChecker
descendants of ErrorChecker
, which are:
sw.ErrorCheckingChain
sequenceCardConnection
and check for errors the result of each call to the transmit()
method.SW1
, SW2
.CardConnectionObserver
Join an instance of CardConnection
and receive information about all command APDUs and card responses that pass through the observed connection. Example of application - maintaining a log of commands and answers from the card.
Armed with this knowledge , we may well aim at writing a command processor using the described library.
I will not dwell on the cmd module, which the standard library kindly provides us with, it has already been written about here , I will move on to the implementation.
The entire source code of the processor is on github .
Let's go through the main points, not exchanging trivia.
select_reader()
Returns the first reader connected to the computer or None
if there are no connected readers.
def select_reader(): """Select the first of available readers. Return smartcard.reader.Reader or None if no readers attached. """ readers_list = readers() if readers_list: return readers_list[0]
There is a variant of this function (depends on the msvcrt module, ie, only for Windows), which allows you to select a reader if there are several of them in the computer.
This class, in addition to inheriting cmd.Cmd
, implements the interface has an observer behavior smartcard.CardMonitoring.CardObserver
reader - the reader to work with.
card - the card object, we will need to determine the moment of changing the card in the reader.
connection — APDU transmission channel to the card and receiving the processing result.
sel_obj is a string containing the ID of the current object (file or folder) selected by the SELECT
command. This line is changed whenever the SELECT command is executed.
atr - here we remember the ATR of the current card so that it can be displayed on the screen without requesting a card every time (such a request resets the state of file selection in the card).
The card_connection_observer is an observer that binds to each connection , details below.
def __init__(self): super(APDUShell, self).__init__(completekey=None) self.reader = select_reader() self._clear_context() self.connection = None self.card_connection_observer = ConsoleCardConnectionObserver() CardMonitor().addObserver(self)
We, in addition to initializing data, add ourselves to the observers.smartcard.CardMonitoring.CardMonitor
- an object that reacts to reader and card interaction events (the card is placed in the reader, the card is removed from the reader) and notifies about these events a smartcard.CardMonitoring.CardObserver
, i.e. us. This type of alert is configured only once during the life of our shell. CardMonitor
is a singleton , so we do not care about the lifetime of its copy.
I also draw attention to the smartcard.CardConnectionObserver.ConsoleCardConnectionObserver
instance - this is a ready-made library observer object that monitors the status of the communication channel with the card and prints this state to the console. We will hang it on every new connection with the map.
def update(self, observable, handlers): """CardObserver interface implementation""" addedcards, removedcards = handlers if self.card and self.card in removedcards: self._clear_connection() self._clear_context() for card in addedcards: if str(card.reader) == str(self.reader): self.card = card self._set_up_connection() break
This is, in fact, the behavior of smartcard.CardMonitoring.CardObserver
. If our current card is in the removedcards
list, then we clear the state of the shell for the current card.
If a new card has appeared in our selected reader (and in the addedcards
list at the same time), then we initialize a new shell state for this card.
def default(self, line): """Process all APDU""" if not line or self.card is None: return try: apdu = toBytes(line) data, sw1, sw2 = self.connection.transmit(apdu) # if INS is A4 (SELECT) then catch and save FID if select is successful if apdu[1] != APDUShell.SELECT_COMMAND_INSTRUCTION or sw1 not in APDUShell.SELECT_SUCCESSFUL_SW1: return self.sel_obj = toHexString(apdu[5:], PACK) except (TypeError, CardConnectionException) as e: try: print(e.message.decode(locale.getpreferredencoding())) except AttributeError: print(e.__class__.__name__ + ' (no message given)')
Here all hexadecimal APDUs entered by the user are converted into byte lists and sent to the map. I note that the only thing we do with the result here is to determine if the command sent is not a successful SELECT
. If yes, then we update the ID of the last selected object to print in the invitation to the user.
Our ConsoleCardConnectionObserver
does the rest of the routine work of interpreting and displaying the result of the command for the user.
Small passing retreat
I personally don’t really like how theConsoleCardConnectionObserver
displays the result of the APDU execution - it doesn’t separate the SW from the resulting data as I would like it to. I used it only in order not to litter the example code with unimportant details. However, if anyone is interested, my observer’supdate()
method code is in this commit .
def _set_up_connection(self): """Create & configure a new card connection""" self.connection = self.card.createConnection() self.connection.addObserver(self.card_connection_observer) self.connection.connect() self.atr = toHexString(self.connection.getATR(), PACK)
A hard worker who helps us every time the card in the reader changes. He creates a connection with the card, ConsoleCardConnectionObserver
on it, and remembers the ATR card (so that the atr command can display it on the screen).
def _clear_connection(self): if not self.connection: return self.connection.deleteObserver(self.card_connection_observer) self.connection.disconnect() self.connection = None
The _set_up_connection()
, “cleans” when the card is removed from the reader.
At this stage, we can run our command shell and, depending on the availability of a card reader, get a simple error message (no reader) or see the shell in work (the lucky ones with the reader). If you have a reader, nothing prevents you from inserting any smart card into it and executing the atr command - it should work, but please do not forget that you are experimenting on your own working SIM and bank cards at your own peril and risk.
See you in the second part, I assume that Python will not be there (almost or completely), but there will be APDU and SW.
When preparing the article, I came across a couple of projects that use this library:
https://bitbucket.org/benallard/webscard/src
https://github.com/mitshell/card
May be real code examples will be useful.
Source: https://habr.com/ru/post/346270/
All Articles