📜 ⬆️ ⬇️

We work with smart cards using Python (part 1)

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:


  1. Familiarity with the library and writing / parsing the code of a special command processor that uses it.
  2. Using the command processor from part 1 to read the contents of the file from the sim card, which I once picked up on the street (no personal data will not be disclosed). We learn how to disaccustom Windows to interfere with our interaction with the card, and also, perhaps, we will touch on the topic of choosing (activating) the system application on the card (if my experimental card turns out to be UICC).

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 experience of using on platforms, if interested ...

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:


  1. When placing each card into the reader, this card is considered a Plug & Play device, for which the system “looks for drivers”.
  2. If we wait for the termination of item 1, then the system starts looking for certificates on the card, while communicating with it with its APDUs, these APDUs mix with ours and there are collisions leading to failures in our programs.

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).


My experience with Python versions ...

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:



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:


  1. Let's look at the typical structure / pattern of the program;
  2. Let's take a look at, in my opinion, the most important objects of the library, which should convince to use not a low-level smartcard.scard , but a smartcard ;
  3. We illustrate the use of the library with a real example - we will write a command processor (shell) in Python 3.6, where the commands will be straightforward “APDU in hex” and the response from the card will be output to the console. Text exit and atr commands will also be supported.

Typical program template


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:


  1. The choice of the reader with whom we will interact
  2. Determining the moment when the card will be in this reader
  3. Setting the communication channel with the map
  4. Checking the card for compliance with our criteria (we may not want to work with every card that the user will give us)
  5. Data exchange with the card via APDU
  6. Closing the communication channel with the card
  7. Determining the moment when the card will be removed from the reader

I note that the listed problems are solved, for example, by flashing a mobile phone.


Key objects of the smartcard package


In this section, all names are relative to the smartcard package.


Subclasses of 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 subclasses


Allow 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:



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() function


Allows 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:



Descendants of 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.


Command processor with APDU (CLI)


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.


Function select_reader()


Returns the first reader connected to the computer or None if there are no connected readers.


Code
 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.


Class APDUShell


This class, in addition to inheriting cmd.Cmd , implements the interface has an observer behavior smartcard.CardMonitoring.CardObserver


Instance data of our shell


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.


In the constructor


Constructor code
 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.


update ()


Code
 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.


default ()


Code
 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 the ConsoleCardConnectionObserver 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’s update() method code is in this commit .

_set_up_connection ()


Code
 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).


_clear_connection ()


Code
 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.


Conclusion


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.


Screenshot of our command processor

Screenshot


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