pyscard user’s guide¶
Copyright¶
This file is part of pyscard.
pyscard is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
pyscard is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with pyscard; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Introduction¶
The pyscard smartcard library is a framework for building smart card aware applications in Python. The smartcard module is built on top of the PCSC API Python wrapper module.
pyscard supports Windows operating systems via the Microsoft Smart Card SDK components, and Linux and Mac OS X by using PCSC-lite.
Smart Cards¶
Smart cards are plastic cards having generally the size of a credit card and embedding a microprocessor. Smart cards communicate with the outside world thru a serial port interface and an half-duplex protocol. Smartcards usually interface with a dedicated terminal, such as a point-of-sale terminal or a mobile phone. Sometime, smart cards have to be interfaced with personal computers. This is the case for some applications such as secure login, mail cyphering or digital signature, but also for some PC based smart card tools used to personalize or edit the content of smart cards. Smart cards are interfaced with a personal computer using a smart card reader. The smart card reader connects on one side to the serial port of the smart card, and on the other side to the PC, often nowadays thru a USB port.
The PCSC workgroup has defined a standard API to interface smart card and smart card readers to a PC. The resulting reference implementation on Linux and Mac OS X operating systems is PC/SC-lite. All Windows operating systems also include out-of-the-box smart card support.
The PCSC API is implemented in C language, and several bridges are provided to access the PCSC API from different languages such as java or visual basic. pyscard is a Python framework to develop smart card PC applications on Linux, Mac OS X, and Windows. pyscard lower layers interface to the PCSC API to access the smart cards and smart card readers.
Quick-start¶
We will see in this section some variations on how to send APDU commands to a smart card.
The reader-centric approach¶
A PC application interacts with a card by sending list of bytes, known as Application Protocol Data Units (APDU). The format of these APDUs is defined in the ISO7816-4 standard. To send APDUs to a card, the application needs first to connect to a card thru a smart card reader. Smart card aware applications that first select a smart card reader, then connect to the card inserted in the smart card reader use the reader-centric approach.
In the reader-centric approach, we open a connection with a card thru a smart card reader, and send APDU commands to the card using the connection:
>>> from smartcard.System import readers
>>>
>>> r = readers()
>>> print(r)
['SchlumbergerSema Reflex USB v.2 0', 'Utimaco CardManUSB 0']
>>> connection = r[0].createConnection()
>>> connection.connect()
>>> SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
>>> DF_TELECOM = [0x7F, 0x10]
>>> data, sw1, sw2 = connection.transmit(SELECT + DF_TELECOM)
>>> print("%x %x" % (sw1, sw2))
9f 1a
>>>
The list of available readers is retrieved with the readers() function. We create a connection with the first reader (index 0 for reader 1, 1 for reader 2, …) with the r[0].createConnection() call and connect to the card with the connect() method of the connection. We can then send APDU commands to the card with the transmit() method.
Scripts written with the reader centric approach however have the following drawbacks:
the reader index or reader name is hard coded in the scripts; the scripts must be edited to match each user configuration; for example in the previous script, we would have to edit the script and change r[0] to r[1] for using the second reader
there is no a-priori knowledge that the card is in the reader; to detect card insertion, we would have to execute the script and eventually catch a CardConnectionException that would indicate that there is no card in the reader.
there is no built-in check that the card in the reader is of the card type we expect; in the previous example, we might try to select the DF_TELECOM of an EMV card.
Most of these issues are solved with the card-centric approach, based on card type detection techniques, such as using the Answer To Reset (ATR) of the card.
The Answer To Reset (ATR)¶
The first answer of a smart card inserted in a smart card reader is call the ATR. The purpose of the ATR is to describe the supported communication parameters. The smart card reader, smart card reader driver, and operating system will use these parameters to establish a communication with the card. The ATR is described in the ISO7816-3 standard. The first bytes of the ATR describe the voltage convention (direct or inverse), followed by bytes describing the available communication interfaces and their respective parameters. These interface bytes are then followed by Historical Bytes which are not standardized, and are useful for transmitting proprietary information such as the card type, the version of the embedded software, or the card state. Finally these historical bytes are eventually followed by a checksum byte.
The class smartcard.ATR is a pyscard utility class that can interpret the content of an ATR:
#! /usr/bin/env python3
"""
Sample script for the smartcard.ATR utility class.
__author__ = "https://www.gemalto.com/"
Copyright 2001-2009 gemalto
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
This file is part of pyscard.
pyscard is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
pyscard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with pyscard; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from smartcard.ATR import ATR
from smartcard.util import toHexString
atr = ATR(
[
0x3B,
0x9E,
0x95,
0x80,
0x1F,
0xC3,
0x80,
0x31,
0xA0,
0x73,
0xBE,
0x21,
0x13,
0x67,
0x29,
0x02,
0x01,
0x01,
0x81,
0xCD,
0xB9,
]
)
print(atr)
print("historical bytes: ", toHexString(atr.getHistoricalBytes()))
print("checksum: ", "0x%X" % atr.getChecksum())
print("checksum OK: ", atr.checksumOK)
print("T0 supported: ", atr.isT0Supported())
print("T1 supported: ", atr.isT1Supported())
print("T15 supported: ", atr.isT15Supported())
Which results in the following output:
3B 9E 95 80 1F C3 80 31 A0 73 BE 21 13 67 29 02 01 01 81 CD B9
historical bytes: 80 31 A0 73 BE 21 13 67 29 02 01 01 81 CD
checksum: 0xB9
checksum OK: True
T0 supported: True
T1 supported: False
T15 supported: True
In practice, the ATR can be used to detect a particular card, either by trying to match a card with a complete ATR, or by matching a card with some data in the historical bytes. Smart card aware PC applications that detects smart cards based on the content of the ATR use the card-centric approach, independently on the smart card reader in which the card is inserted..
The card-centric approach¶
In the card-centric approach, we create a request for a specific type of card and wait until a card matching the request is inserted. Once a matching card is introduced, a connection to the card is automatically created and we can send APDU commands to the card using this connection.
Requesting a card by ATR¶
The following scripts requests a card with a known ATR:
>>> from smartcard.CardType import ATRCardType
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.util import toHexString, toBytes
>>>
>>> cardtype = ATRCardType(toBytes("3B 16 94 20 02 01 00 00 0D"))
>>> cardrequest = CardRequest(timeout=1, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> cardservice.connection.connect()
>>> print(toHexString(cardservice.connection.getATR()))
3B 16 94 20 02 01 00 00 0D
>>>
>>> SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
>>> DF_TELECOM = [0x7F, 0x10]
>>> data, sw1, sw2 = cardservice.connection.transmit(SELECT + DF_TELECOM)
>>> print("%x %x" % (sw1, sw2))
9f 1a
>>>
To request a card with a know ATR, you must first create an ATRCardType object with the desired ATR:
>>> cardtype = ATRCardType(toBytes("3B 16 94 20 02 01 00 00 0D"))
And then create a CardRequest for this card type. In the sample, we request a time-out of 1 second.
>>> cardrequest = CardRequest(timeout=1, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
The waitforcard() will either return with a card service or a time-out. The card service connection attribute can be used thereafter to transmit APDU commands to the card, as with the reader centric approach.
>>> cardservice.connection.connect()
>>> print(toHexString(cardservice.connection.getATR()))
If necessary, the reader used for the connection can be accessed thru the CardConnection object:
>>> print(cardservice.connection.getReader())
SchlumbergerSema Reflex USB v.2 0
The ATRCardType also supports masks:
>>> from smartcard.CardType import ATRCardType
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.util import toHexString, toBytes
>>>
>>> cardtype = ATRCardType(toBytes("3B 15 94 20 02 01 00 00 0F"), toBytes("00 00 FF FF FF FF FF FF 00"))
>>> cardrequest = CardRequest(timeout=1, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> cardservice.connection.connect()
>>> print(toHexString(cardservice.connection.getATR()))
3B 16 94 20 02 01 00 00 0D
Other CardTypes are available, and new CardTypes can be created, as described below.
Requesting any card¶
The AnyCardType is useful for requesting any card in any reader:
>>> from smartcard.CardType import AnyCardType
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.util import toHexString
>>>
>>> cardtype = AnyCardType()
>>> cardrequest = CardRequest(timeout=1, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> cardservice.connection.connect()
>>> print(toHexString(cardservice.connection.getATR()))
3B 16 94 20 02 01 00 00 0D
>>> print(cardservice.connection.getReader())
SchlumbergerSema Reflex USB v.2 0
Custom CardTypes¶
Custom CardTypes can be created, e.g. a card type that checks the ATR and the historical bytes of the card. To create a custom CardType, derive your CardType class from the CardType base class (or any other CardType) and override the matches() method. For example to create a DCCardType that will match cards with the direct convention (first byte of ATR to 0x3b):
>>> from smartcard.CardType import CardType
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.util import toHexString
>>>
>>> class DCCardType(CardType):
... def matches(self, atr, reader=None):
... return atr[0] == 0x3B
...
>>> cardtype = DCCardType()
>>> cardrequest = CardRequest(timeout=1, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> cardservice.connection.connect()
>>> print(toHexString(cardservice.connection.getATR()))
3B 16 94 20 02 01 00 00 0D
>>> print(cardservice.connection.getReader())
SchlumbergerSema Reflex USB v.2 0
>>>
Scripts written with the card-centric approach fixes the problems of the reader-centric approach:
there is no assumption concerning the reader index or reader name; the desired card will be located in any reader
the request will block or time-out if the desired card type is not inserted since we request the desired card type, the script is not played on an unknown or incompatible card
Scripts written with the card-centric approach have however the following drawbacks:
the script is limited to a specific card type; we have to modify the script if we want to execute the script on another card type. For example, we have to modify the ATR of the card if we are using the ATRCardType. This can be partially solved by having a custom CardType that matches several ATRs, though.
Selecting the card communication protocol¶
Communication parameters are mostly important for the protocol negotiation between the smart card reader and the card. The main smartcard protocols are the T=0 protocol and the T=1 protocol, for byte or block transmission, respectively. The required protocol can be specified at card connection or card transmission.
By defaults, the connect() method of the CardConnection object.will try to connect using either the T=0 or T=1 protocol. To force a connection protocol, you can pass the required protocol to the connect() method.
>>> from smartcard.CardType import AnyCardType
>>> from smartcard.CardConnection import CardConnection
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.util import toHexString
>>>
>>> cardtype = AnyCardType()
>>> cardrequest = CardRequest(timeout=1, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> cardservice.connection.connect(CardConnection.T1_protocol)
>>> print(toHexString(cardservice.connection.getATR()))
3B 16 94 20 02 01 00 00 0D
>>> print(cardservice.connection.getReader())
SchlumbergerSema Reflex USB v.2 0
Alternatively, you can specify the required protocol in the CardConnection transmit() method:
>>> from smartcard.CardType import AnyCardType
>>> from smartcard.CardConnection import CardConnection
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.util import toHexString
>>>
>>> cardtype = AnyCardType()
>>> cardrequest = CardRequest(timeout=1, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> cardservice.connection.connect()
>>>
>>> SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
>>> DF_TELECOM = [0x7F, 0x10]
>>>
>>> apdu = SELECT + DF_TELECOM
>>> print('sending ' + toHexString(apdu))
sending A0 A4 00 00 02 7F 10
>>> response, sw1, sw2 = cardservice.connection.transmit(apdu, CardConnection.T1_protocol)
>>> print('response:', response, ' status words:', "%x %x" % (sw1, sw2))
response: [] status words: 9f 1a
>>>
>>> if sw1 == 0x9F:
... GET_RESPONSE = [0XA0, 0XC0, 00, 00]
... apdu = GET_RESPONSE + [sw2]
... print('sending ' + toHexString(apdu))
... response, sw1, sw2 = cardservice.connection.transmit(apdu)
... print('response:', toHexString(response), ' status words:', "%x %x" % (sw1, sw2))
...
sending A0 C0 00 00 1A
response: 00 00 00 00 7F 10 02 00 00 00 00 00 0D 13 00 0A 04 00 83 8A 83 8A 00 01 00 00 status words: 90 0
>>>
The object-centric approach¶
In the object-centric approach, we associate a high-level object with a set of smart cards supported by the object. For example we associate a javacard loader class with a set of javacard smart cards. We create a request for the specific object, and wait until a card supported by the object is inserted. Once a card supported by the object is inserted, we perform the required function by calling the object methods.
To be written…
Tracing APDUs¶
The brute force¶
A straightforward way of tracing command and response APDUs is to insert print statements around the transmit() method calls:
>>> from smartcard.CardType import ATRCardType
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.util import toHexString, toBytes
>>>
>>> cardtype = ATRCardType(toBytes("3B 16 94 20 02 01 00 00 0D"))
>>> cardrequest = CardRequest(timeout=1, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> cardservice.connection.connect()
>>>
>>> SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
>>> DF_TELECOM = [0x7F, 0x10]
>>>
>>> apdu = SELECT + DF_TELECOM
>>> print('sending ' + toHexString(apdu))
sending A0 A4 00 00 02 7F 10
>>> response, sw1, sw2 = cardservice.connection.transmit(apdu)
>>> print('response:', response, ' status words:', "%x %x" % (sw1, sw2))
response: [] status words: 9f 1a
>>>
>>> if sw1 == 0x9F:
... GET_RESPONSE = [0XA0, 0XC0, 00, 00]
... apdu = GET_RESPONSE + [sw2]
... print('sending ' + toHexString(apdu))
... response, sw1, sw2 = cardservice.connection.transmit(apdu)
... print('response:', toHexString(response), ' status words:', "%x %x" % (sw1, sw2))
...
sending A0 C0 00 00 1A
response: 00 00 00 00 7F 10 02 00 00 00 00 00 0D 13 00 0A 04 00 83 8A 83 8A 00 01 00 00 status words: 90 0
>>>
Scripts written this way are quite difficult to read, because there are more tracing statements than actual apdu transmits..
A small improvement in visibility would be to replace the print instructions by functions, e.g.:
>>> from smartcard.CardType import ATRCardType
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.util import toHexString, toBytes
>>>
>>> cardtype = ATRCardType(toBytes("3B 16 94 20 02 01 00 00 0D"))
>>> cardrequest = CardRequest(timeout=1, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> cardservice.connection.connect()
>>>
>>> SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
>>> DF_TELECOM = [0x7F, 0x10]
>>>
>>> def trace_command(apdu):
... print('sending ' + toHexString(apdu))
...
>>> def trace_response(response, sw1, sw2):
... if response is None: response = []
... print('response:', toHexString(response), ' status words:', "%x %x" % (sw1, sw2))
...
>>> apdu = SELECT + DF_TELECOM
>>> trace_command(apdu)
sending A0 A4 00 00 02 7F 10
>>> response, sw1, sw2 = cardservice.connection.transmit(apdu)
>>> trace_response(response, sw1, sw2)
response: status words: 9f 1a
>>>
>>> if sw1 == 0x9F:
... GET_RESPONSE = [0XA0, 0XC0, 00, 00]
... apdu = GET_RESPONSE + [sw2]
... trace_command(apdu)
... response, sw1, sw2 = cardservice.connection.transmit(apdu)
... trace_response(response, sw1, sw2)
...
sending A0 C0 00 00 1A
response: 00 00 00 00 7F 10 02 00 00 00 00 00 0D 13 00 0A 04 00 83 8A 83 8A 00 01 00 00 status words: 90 0
>>>
Using card connection observers to trace apdu transmission¶
The preferred solution is to implement a card connection observer, and register the observer with the card connection. The card connection will then notify the observer when card connection events occur (e.g. connection, disconnection, apdu command or apdu response). This is illustrated in the following script:
>>> from smartcard.CardType import AnyCardType
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
>>>
>>> GET_RESPONSE = [0XA0, 0XC0, 00, 00]
>>> SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
>>> DF_TELECOM = [0x7F, 0x10]
>>>
>>>
>>> cardtype = AnyCardType()
>>> cardrequest = CardRequest(timeout=10, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> observer = ConsoleCardConnectionObserver()
>>> cardservice.connection.addObserver(observer)
>>>
>>> cardservice.connection.connect()
connecting to SchlumbergerSema Reflex USB v.2 0
>>>
>>> apdu = SELECT + DF_TELECOM
>>> response, sw1, sw2 = cardservice.connection.transmit(apdu)
> A0 A4 00 00 02 7F 10
< [] 9F 1A
>>> if sw1 == 0x9F:
... apdu = GET_RESPONSE + [sw2]
... response, sw1, sw2 = cardservice.connection.transmit(apdu)
... else:
... print('no DF_TELECOM')
...
> A0 C0 00 00 1A
< 00 00 00 00 7F 10 02 00 00 00 00 00 0D 13 00 0A 04 00 83 8A 83 8A 00 01 00 00 90 0
>>>
In this script, a ConsoleCardConnectionObserver is attached to the card service connection once the watiforcard() call returns.
>>> observer = ConsoleCardConnectionObserver()
>>> cardservice.connection.addObserver(observer)
On card connection events (connect, disconnect, transmit command apdu, receive response apdu), the card connection notifies its observers with a CarConnectionEvent including the event type and the event data. The ConsoleCardConnectionObserver is a simple observer that will print on the console the card connection events. The class definition is the following:
class ConsoleCardConnectionObserver(CardConnectionObserver):
def update(self, cardconnection, ccevent):
if 'connect' == ccevent.type:
print('connecting to', cardconnection.getReader())
elif 'disconnect' == ccevent.type:
print('disconnecting from', cardconnection.getReader())
elif 'command' == ccevent.type:
print('>', toHexString(ccevent.args[0]))
elif 'response' == ccevent.type:
if [] == ccevent.args[0]:
print('< []', "%-2X %-2X" % tuple(ccevent.args[-2:]))
else:
print('<', toHexString(ccevent.args[0]), "%-2X %-2X" % tuple(ccevent.args[-2:]))
The console card connection observer is thus printing the connect, disconnect, command and response apdu events:
>>> cardservice.connection.connect()
connecting to SchlumbergerSema Reflex USB v.2 0
>>>
>>> apdu = SELECT + DF_TELECOM
>>> response, sw1, sw2 = cardservice.connection.transmit(apdu)
> A0 A4 00 00 02 7F 10
< [] 9F 1A
>>> if sw1 == 0x9F:
... apdu = GET_RESPONSE + [sw2]
... response, sw1, sw2 = cardservice.connection.transmit(apdu)
... else:
... print('no DF_TELECOM')
...
> A0 C0 00 00 1A
< 00 00 00 00 7F 10 02 00 00 00 00 00 0D 13 00 0A 04 00 83 8A 83 8A 00 01 00 00 90 0
A card connection observer’s update method is called upon card connection event, with the connection and the connection event as parameters. The CardConnectionEvent class definition is the following:
class CardConnectionEvent:
"""Base class for card connection events.
This event is notified by CardConnection objects.
type: 'connect', 'disconnect', 'command', 'response'
args: None for 'connect' or 'disconnect'
command APDU byte list for 'command'
[response data, sw1, sw2] for 'response'
type: 'connect' args:"""
def __init__(self, type, args=None):
self.type = type
self.args = args
You can write your own card connection observer, for example to perform fancy output in a wxWindows frame, or apdu interpretation. The following scripts defines a small SELECT and GET RESPONSE apdu interpreter:
>>> from smartcard.CardType import AnyCardType
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.CardConnectionObserver import CardConnectionObserver
>>> from smartcard.util import toHexString
>>>
>>> from string import replace
>>>
>>> class TracerAndSELECTInterpreter(CardConnectionObserver):
... def update(self, cardconnection, ccevent):
... if 'connect' == ccevent.type:
... print('connecting to', cardconnection.getReader())
... elif 'disconnect' == ccevent.type:
... print('disconnecting from', cardconnection.getReader())
... elif 'command' == ccevent.type:
... str = toHexString(ccevent.args[0])
... str = replace(str , "A0 A4 00 00 02", "SELECT")
... str = replace(str , "A0 C0 00 00", "GET RESPONSE")
... print('>', str)
... elif 'response' == ccevent.type:
... if [] == ccevent.args[0]:
... print('< []', "%-2X %-2X" % tuple(ccevent.args[-2:]))
... else:
... print('<', toHexString(ccevent.args[0]), "%-2X %-2X" % tuple(ccevent.args[-2:]))
...
>>>
>>> GET_RESPONSE = [0XA0, 0XC0, 00, 00]
>>> SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
>>> DF_TELECOM = [0x7F, 0x10]
>>>
>>>
>>> cardtype = AnyCardType()
>>> cardrequest = CardRequest(timeout=10, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> observer = TracerAndSELECTInterpreter()
>>> cardservice.connection.addObserver(observer)
>>>
>>> cardservice.connection.connect()
connecting to SchlumbergerSema Reflex USB v.2 0
>>>
>>> apdu = SELECT + DF_TELECOM
>>> response, sw1, sw2 = cardservice.connection.transmit(apdu)
> SELECT 7F 10
< [] 9F 1A
>>> if sw1 == 0x9F:
... apdu = GET_RESPONSE + [sw2]
... response, sw1, sw2 = cardservice.connection.transmit(apdu)
... else:
... print('no DF_TELECOM')
...
> GET RESPONSE 1A
< 00 00 00 00 7F 10 02 00 00 00 00 00 0D 13 00 0A 04 00 83 8A 83 8A 00 01 00 00 90 0
>>>
Full sample code¶
#! /usr/bin/env python3
"""
Sample script that defines a custom card connection observer.
__author__ = "https://www.gemalto.com/"
Copyright 2001-2012 gemalto
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
This file is part of pyscard.
pyscard is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
pyscard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with pyscard; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from smartcard.CardConnectionObserver import CardConnectionObserver
from smartcard.CardRequest import CardRequest
from smartcard.CardType import AnyCardType
from smartcard.util import toHexString
class TracerAndSELECTInterpreter(CardConnectionObserver):
"""This observer will interprer SELECT and GET RESPONSE bytes
and replace them with a human readable string."""
def update(self, cardconnection, ccevent):
if "connect" == ccevent.type:
print("connecting to " + cardconnection.getReader())
elif "disconnect" == ccevent.type:
print("disconnecting from " + cardconnection.getReader())
elif "command" == ccevent.type:
str = toHexString(ccevent.args[0])
str = str.replace("A0 A4 00 00 02", "SELECT")
str = str.replace("A0 C0 00 00", "GET RESPONSE")
print(">", str)
elif "response" == ccevent.type:
if [] == ccevent.args[0]:
print("< []", "%-2X %-2X" % tuple(ccevent.args[-2:]))
else:
print(
"<",
toHexString(ccevent.args[0]),
"%-2X %-2X" % tuple(ccevent.args[-2:]),
)
# define the apdus used in this script
GET_RESPONSE = [0xA0, 0xC0, 00, 00]
SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
DF_TELECOM = [0x7F, 0x10]
# we request any type and wait for 10s for card insertion
cardtype = AnyCardType()
cardrequest = CardRequest(timeout=10, cardType=cardtype)
cardservice = cardrequest.waitforcard()
# create an instance of our observer and attach to the connection
observer = TracerAndSELECTInterpreter()
cardservice.connection.addObserver(observer)
# connect and send APDUs
# the observer will trace on the console
cardservice.connection.connect()
apdu = SELECT + DF_TELECOM
response, sw1, sw2 = cardservice.connection.transmit(apdu)
if sw1 == 0x9F:
apdu = GET_RESPONSE + [sw2]
response, sw1, sw2 = cardservice.connection.transmit(apdu)
else:
print("no DF_TELECOM")
import sys
if "win32" == sys.platform:
print("press Enter to continue")
sys.stdin.read(1)
Testing for APDU transmission errors¶
Upon transmission and processing of an APDU, the smart card returns a pair of status words, SW1 and SW2, to report various success or error codes following the required processing. Some of these success or error codes are standardized in ISO7816-4, ISO7816-8 or ISO7816-9, for example. Other status word codes are standardized by standardization bodies such as Open Platform (e.g. javacard), 3GPP (e.g. SIM or USIM cards), or Eurocard-Mastercard-Visa (EMV) (e.g. banking cards). Finally, any smart card application developer can defined application related proprietary codes; for example the MUSCLE applet defines a set of proprietary codes related to the MUSCLE applet features.
Some of these status word codes are unique, but others have a different meaning depending on the card type and its supported standards. For example, ISO7816-4 defines the error code 0x62 0x82 as “File Invalidated”, whereas in Open Platform 2.1 the same error code is defined as “Card life cycle is CARD_LOCKED”. As a result, the list of error codes that can be returned by a smart card and they interpretation depend on the card type. The following discussion outlines possible strategies to check and report smart card status word errors.
The brute force for testing APDU transmission errors¶
As for APDU tracing, a straightforward way of checking for errors in response APDUs during the execution of scripts is to insert test statements after the transmit() method calls:
>>> from smartcard.CardType import AnyCardType
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
>>>
>>> GET_RESPONSE = [0XA0, 0XC0, 00, 00]
>>> SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
>>> DF_TELECOM = [0x7F, 0x10]
>>>
>>> cardtype = AnyCardType()
>>> cardrequest = CardRequest(timeout=10, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> observer = ConsoleCardConnectionObserver()
>>> cardservice.connection.addObserver(observer)
>>>
>>> cardservice.connection.connect()
connecting to Utimaco CardManUSB 0
>>>
>>> apdu = SELECT + DF_TELECOM
>>> response, sw1, sw2 = cardservice.connection.transmit(apdu)
> A0 A4 00 00 02 7F 10
< [] 6E 0
>>>
>>> if sw1 in range(0x61, 0x6f):
... print("Error: sw1: %x sw2: %x" % (sw1, sw2))
...
Error: sw1: 6e sw2: 0
>>> if sw1 == 0x9F:
... apdu = GET_RESPONSE + [sw2]
... response, sw1, sw2 = cardservice.connection.transmit(apdu)
...
>>> cardservice.connection.disconnect()
disconnecting from Utimaco CardManUSB 0
>>>
Scripts written this way are quite difficult to read, because there are more error detection statements than actual apdu transmits.
An improvement in visibility is to wrap the transmit instruction inside a function mytransmit, e.g.:
>>> from smartcard.CardType import AnyCardType
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
>>>
>>> def mytransmit(connection, apdu):
... response, sw1, sw2 = connection.transmit(apdu)
... if sw1 in range(0x61, 0x6f):
... print("Error: sw1: %x sw2: %x" % (sw1, sw2))
... return response, sw1, sw2
...
>>>
>>> GET_RESPONSE = [0XA0, 0XC0, 00, 00]
>>> SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
>>> DF_TELECOM = [0x7F, 0x10]
>>>
>>>
>>> cardtype = AnyCardType()
>>> cardrequest = CardRequest(timeout=10, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> observer = ConsoleCardConnectionObserver()
>>> cardservice.connection.addObserver(observer)
>>>
>>> cardservice.connection.connect()
connecting to Utimaco CardManUSB 0
>>>
>>> apdu = SELECT + DF_TELECOM
>>> response, sw1, sw2 = mytransmit(cardservice.connection, apdu)
> A0 A4 00 00 02 7F 10
< [] 6E 0
Error: sw1: 6e sw2: 0
>>>
>>> if sw1 == 0x9F:
... apdu = GET_RESPONSE + [sw2]
... response, sw1, sw2 = mytransmit(cardservice.connection, apdu)
...
>>> cardservice.connection.disconnect()
disconnecting from Utimaco CardManUSB 0
>>>
The preferred solution is for testing errors is to use smarcard.sw.ErrorChecker, as described in the following section.
Checking APDU transmission errors with error checkers¶
Status word errors can occur from different sources. The ISO7816-4 standards defines status words for sw1 in the range 0x62 to 0x6F and some values of sw2, except for 0x66 which is reserved for security related issues. The ISO7816-8 standards define other status words, e.g. sw1=0x68 and sw2=0x83 or 0x84 for command chaining errors. Other standards, like Open Platform, define additional status words error, e.g. sw1=0x94 and sw2=0x84.
The preferred strategy for status word error checking is based around individual error checkers (smartcard.sw.ErrorChecker) that can be chained into an error checking chain (smartcars.sw.ErrorCheckingChain).
Error checkers¶
An error checker is a class deriving from ErrorChecker that checks for recognized sw1, sw2 error conditions when called, and raises an exception when finding such condition. This is illustrated in the following sample:
>>> from smartcard.sw.ISO7816_4ErrorChecker import ISO7816_4ErrorChecker
>>>
>>> errorchecker = ISO7816_4ErrorChecker()
>>> errorchecker([], 0x90, 0x00)
>>> errorchecker([], 0x6A, 0x80)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "D:\projects\pyscard-install\factory\python\lib\site-packages\smartcard\sw\ISO7816_4ErrorChecker.py", line 137, in __call__
raise exception(data, sw1, sw2, message)
smartcard.sw.SWExceptions.CheckingErrorException: 'Status word exception: checking error - Incorrect parameters in the data field!'
>>>
The first call to error checker does not raise an exception, since 90 00 does not report any error. The second calls however raises a CheckingErrorException.
Error checking chains¶
Error checkers can be chained into error checking chain. Each checker in the chain is called until an error condition is met, in which case an exception is raised. This is illustrated in the following sample:
>>> from smartcard.sw.ISO7816_4ErrorChecker import ISO7816_4ErrorChecker
>>> from smartcard.sw.ISO7816_8ErrorChecker import ISO7816_8ErrorChecker
>>> from smartcard.sw.ISO7816_9ErrorChecker import ISO7816_9ErrorChecker
>>>
>>> from smartcard.sw.ErrorCheckingChain import ErrorCheckingChain
>>>
>>> errorchain = []
>>> errorchain = [ErrorCheckingChain(errorchain, ISO7816_9ErrorChecker()),
... ErrorCheckingChain(errorchain, ISO7816_8ErrorChecker()),
... ErrorCheckingChain(errorchain, ISO7816_4ErrorChecker())]
>>>
>>> errorchain[0]([], 0x90, 0x00)
>>> errorchain[0]([], 0x6A, 0x8a)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "D:\projects\pyscard-install\factory\python\lib\site-packages\smartcard\sw\ErrorCheckingChain.py", line 60,
in __call__
self.strategy(data, sw1, sw2)
File "D:\projects\pyscard-install\factory\python\lib\site-packages\smartcard\sw\ISO7816_9ErrorChecker.py", line 74, in __call__
raise exception(data, sw1, sw2, message)
smartcard.sw.SWExceptions.CheckingErrorException: 'Status word exception: checking error - DF name already exists!'
>>>
In this sample, an error checking chain is created that will check first for iso 7816-9 errors, then iso7816-8 errors, and finally iso7816-4 errors.
The first call to the error chain does not raise an exception, since 90 00 does not report any error. The second calls however raises a CheckingErrorException, caused by the iso7816-9 error checker.
Filtering exceptions¶
You can filter undesired exceptions in a chain by adding a filtered exception to the error checking chain:
>>> from smartcard.sw.ISO7816_4ErrorChecker import ISO7816_4ErrorChecker
>>> from smartcard.sw.ISO7816_8ErrorChecker import ISO7816_8ErrorChecker
>>> from smartcard.sw.ISO7816_9ErrorChecker import ISO7816_9ErrorChecker
>>>
>>> from smartcard.sw.ErrorCheckingChain import ErrorCheckingChain
>>>
>>> errorchain = []
>>> errorchain = [ErrorCheckingChain(errorchain, ISO7816_9ErrorChecker()),
... ErrorCheckingChain(errorchain, ISO7816_8ErrorChecker()),
... ErrorCheckingChain(errorchain, ISO7816_4ErrorChecker())]
>>>
>>>
>>> errorchain[0]([], 0x90, 0x00)
>>> errorchain[0]([], 0x62, 0x00)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "D:\projects\pyscard-install\factory\python\lib\site-packages\smartcard\sw\ErrorCheckingChain.py", line 72, in __call__
return self.next()(data, sw1, sw2)
File "D:\projects\pyscard-install\factory\python\lib\site-packages\smartcard\sw\ErrorCheckingChain.py", line 72, in __call__
return self.next()(data, sw1, sw2)
File "D:\projects\pyscard-install\factory\python\lib\site-packages\smartcard\sw\ErrorCheckingChain.py", line 60, in __call__
self.strategy(data, sw1, sw2)
File "D:\projects\pyscard-install\factory\python\lib\site-packages\smartcard\sw\ISO7816_4ErrorChecker.py", line 137, in __call__
raise exception(data, sw1, sw2, message)
smartcard.sw.SWExceptions.WarningProcessingException: 'Status word exception: warning processing - Response padded/ More APDU commands expected!'
>>>
>>> from smartcard.sw.SWExceptions import WarningProcessingException
>>>
>>> errorchain[0].addFilterException(WarningProcessingException)
>>> errorchain[0]([], 0x62, 0x00)
>>>
The first call to the error chain with sw1 sw2 = 62 00 raises a WarningProcessingException.
...
>>> errorchain[0]([], 0x62, 0x00)
Traceback (most recent call last):
...
After adding a filter for WarningProcessingException, the second call to the error chain with sw1 sw2 = 62 00 does not raise any exception:
>>> from smartcard.sw.SWExceptions import WarningProcessingException
>>>
>>> errorchain[0].addFilterException(WarningProcessingException)
>>> errorchain[0]([], 0x62, 0x00)
>>>
Full sample code¶
#! /usr/bin/env python3
"""Sample script for APDU error checking.
__author__ = "https://www.gemalto.com/"
Copyright 2001-2012 gemalto
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
This file is part of pyscard.
pyscard is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
pyscard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with pyscard; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.CardRequest import CardRequest
from smartcard.CardType import AnyCardType
from smartcard.sw.ErrorCheckingChain import ErrorCheckingChain
from smartcard.sw.ISO7816_4ErrorChecker import ISO7816_4ErrorChecker
from smartcard.sw.ISO7816_8ErrorChecker import ISO7816_8ErrorChecker
from smartcard.sw.ISO7816_9ErrorChecker import ISO7816_9ErrorChecker
from smartcard.sw.SWExceptions import SWException, WarningProcessingException
# define the apdus used in this script
GET_RESPONSE = [0xA0, 0xC0, 00, 00]
SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
DF_TELECOM = [0x7F, 0x10]
if __name__ == "__main__":
print("Insert a card within 10 seconds")
print("Cards without a DF_TELECOM will except")
# request any card type
cardtype = AnyCardType()
cardrequest = CardRequest(timeout=10, cardType=cardtype)
cardservice = cardrequest.waitforcard()
# use ISO7816-4 and ISO7816-8 error checking strategy
# first check iso7816_8 errors, then iso7816_4 errors
errorchain = []
errorchain = [ErrorCheckingChain(errorchain, ISO7816_9ErrorChecker())]
errorchain = [ErrorCheckingChain(errorchain, ISO7816_8ErrorChecker())]
errorchain = [ErrorCheckingChain(errorchain, ISO7816_4ErrorChecker())]
cardservice.connection.setErrorCheckingChain(errorchain)
# filter Warning Processing Exceptions (sw1 = 0x62 or 0x63)
cardservice.connection.addSWExceptionToFilter(WarningProcessingException)
# attach the console tracer
observer = ConsoleCardConnectionObserver()
cardservice.connection.addObserver(observer)
# connect to the card and perform a few transmits
cardservice.connection.connect()
try:
apdu = SELECT + DF_TELECOM
response, sw1, sw2 = cardservice.connection.transmit(apdu)
if sw1 == 0x9F:
apdu = GET_RESPONSE + [sw2]
response, sw1, sw2 = cardservice.connection.transmit(apdu)
except SWException as e:
print(str(e))
cardservice.connection.disconnect()
import sys
if "win32" == sys.platform:
print("press Enter to continue")
sys.stdin.read(1)
Detecting response APDU errors for a card connection¶
To detect APDU response errors during transmission, simply set the error checking chain of the connection used for transmission:
from smartcard.CardType import AnyCardType
from smartcard.CardRequest import CardRequest
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.sw.ErrorCheckingChain import ErrorCheckingChain
from smartcard.sw.ISO7816_4ErrorChecker import ISO7816_4ErrorChecker
from smartcard.sw.ISO7816_8ErrorChecker import ISO7816_8ErrorChecker
from smartcard.sw.SWExceptions import SWException, WarningProcessingException
# request any card
cardtype = AnyCardType()
cardrequest = CardRequest(timeout=10, cardType=cardtype)
cardservice = cardrequest.waitforcard()
# our error checking chain
errorchain = []
errorchain = [ErrorCheckingChain(errorchain, ISO7816_8ErrorChecker()),
ErrorCheckingChain(errorchain, ISO7816_4ErrorChecker())]
cardservice.connection.setErrorCheckingChain(errorchain)
# a console tracer
observer=ConsoleCardConnectionObserver()
cardservice.connection.addObserver(observer)
# send a few apdus; exceptions will occur upon errors
cardservice.connection.connect()
try:
SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
DF_TELECOM = [0x7F, 0x10]
apdu = SELECT + DF_TELECOM
response, sw1, sw2 = cardservice.connection.transmit(apdu)
if sw1 == 0x9F:
GET_RESPONSE = [0XA0, 0XC0, 00, 00]
apdu = GET_RESPONSE + [sw2]
response, sw1, sw2 = cardservice.connection.transmit(apdu)
except SWException, e:
print(str(e))
Executing the previous script on a SIM card will cause an output similar to:
connecting to SchlumbergerSema Reflex USB v.2 0
> A0 A4 00 00 02 7F 10
< [] 9F 1A
> A0 C0 00 00 1A
< 00 00 00 00 7F 10 02 00 00 00 00 00 0D 13 00 0A 04 00 83 8A 83 8A 00 01 00 00 90 0
disconnecting from SchlumbergerSema Reflex USB v.2 0
disconnecting from SchlumbergerSema Reflex USB v.2 0
whereas executing the script on a non-SIM card will result in:
connecting to Utimaco CardManUSB 0
> A0 A4 00 00 02 7F 10
< [] 6E 0
'Status word exception: checking error - Class (CLA) not supported!'
disconnecting from Utimaco CardManUSB 0
disconnecting from Utimaco CardManUSB 0
To implement an error checking chain, create an ErrorCheckingChain object with the desired error checking strategies, and set this chain object as the card connection error checking chain. The card connection will use the chain for error checking upon reception of a response apdu:
Writing a custom error checker¶
Implementing a custom error checker requires implementing a sub-class of op21_ErrorChecker, and overriding the __call__ method. The following error checker raises a SecurityRelatedException exception when sw1=0x66 and sw2=0x00.
Custom checkers can be used standalone, as in the following sample, or chained to other error checkers.
#! /usr/bin/env python3
"""Sample script for APDU error checking with a custom error checker.
__author__ = "https://www.gemalto.com/"
Copyright 2001-2012 gemalto
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
This file is part of pyscard.
pyscard is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
pyscard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with pyscard; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.CardRequest import CardRequest
from smartcard.CardType import AnyCardType
from smartcard.sw.ErrorChecker import ErrorChecker
from smartcard.sw.ErrorCheckingChain import ErrorCheckingChain
from smartcard.sw.SWExceptions import SWException
class MyErrorChecker(ErrorChecker):
"""Our custom error checker that will except if 0x61<sw1<0x70."""
def __call__(self, data, sw1, sw2):
print(sw1, sw2)
if 0x61 < sw1 and 0x70 > sw1:
raise SWException(data, sw1, sw2)
# define the apdus used in this script
GET_RESPONSE = [0xA0, 0xC0, 00, 00]
SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
DF_TELECOM = [0x7F, 0x10]
if __name__ == "__main__":
print("Insert a card within 10 seconds")
print("Cards without a DF_TELECOM will except")
# request any card
cardtype = AnyCardType()
cardrequest = CardRequest(timeout=10, cardType=cardtype)
cardservice = cardrequest.waitforcard()
# our error checking chain
errorchain = []
errorchain = [ErrorCheckingChain([], MyErrorChecker())]
cardservice.connection.setErrorCheckingChain(errorchain)
# attach the console tracer
observer = ConsoleCardConnectionObserver()
cardservice.connection.addObserver(observer)
# send a few apdus; exceptions will occur upon errors
cardservice.connection.connect()
try:
SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
DF_TELECOM = [0x7F, 0x10]
apdu = SELECT + DF_TELECOM
response, sw1, sw2 = cardservice.connection.transmit(apdu)
if sw1 == 0x9F:
GET_RESPONSE = [0xA0, 0xC0, 00, 00]
apdu = GET_RESPONSE + [sw2]
response, sw1, sw2 = cardservice.connection.transmit(apdu)
except SWException as e:
print(e, f"{e.sw1:x} {e.sw2:x}")
cardservice.connection.disconnect()
import sys
if "win32" == sys.platform:
print("press Enter to continue")
sys.stdin.read(1)
Smartcard readers¶
Listing Smartcard Readers¶
The easiest way to retrieve the list of smartcard readers is the smartcard.System.readers() function:
>>> import smartcard.System
>>> print(smartcard.System.readers())
['Schlumberger e-gate 0', 'SchlumbergerSema Reflex USB v.2 0', 'Utimaco CardManUSB 0']
>>>
Organizing Smartcard Readers into reader groups¶
Reader group management is only available on Windows, since PCSC-lite does not currently supports reader groups management.
Readers can be organized in reader groups. To retrieve the smartcard reader groups, use readergroups():
>>> import smartcard.System
>>> print(smartcard.System.readergroups())
['SCard$DefaultReaders']
>>>
The readergroups() object has all the list attributes. To add a reader group, simply use the + operator, e.g.:
>>> from smartcard.System import readergroups
>>> g = readergroups()
>>> print(g)
['SCard$DefaultReaders']
>>> g += 'Biometric$Readers'
>>> print(g)
['SCard$DefaultReaders', 'Biometric$Readers']
>>>
You can also use the append and insert methods, as well as the + operator, e.g.:
>>> from smartcard.System import readergroups
>>> g = readergroups()
>>> print(g)
['SCard$DefaultReaders']
>>> g = g + ['Biometric$Readers','Pinpad$Readers']
>>> print(g)
['SCard$DefaultReaders', 'Biometric$Readers', 'Pinpad$Readers']
>>>
or
>>> from smartcard.System import readergroups
>>> g = readergroups()
>>> print(g)
['SCard$DefaultReaders']
>>> g.append('Biometric$Readers')
>>> g.insert(1,'Pinpad$Readers')
>>> print(g)
['SCard$DefaultReaders', 'Pinpad$Readers', 'Biometric$Readers']
>>>
Smartcard reader groups are not persistent until a reader as been added to the group. To add a reader to a reader group, use addreadertogroups():
>>> from smartcard.System import readergroups, addreadertogroups, readers
>>> g = readergroups()
>>> g += 'USB$Readers'
>>> addreadertogroups('Schlumberger e-gate 0', 'USB$Readers')
>>> readers('USB$Readers')
['Schlumberger e-gate 0']
>>>
To remove a reader group, all list operators are available to manage reader groups, including pop() or remove():
>>> from smartcard.System import readergroups, addreadertogroups, readers
>>> g = readergroups()
>>> g += 'USB$Readers'
>>> print(g)
['SCard$DefaultReaders', 'USB$Readers']
>>> g.pop(1)
'USB$Readers'
>>> g
['SCard$DefaultReaders']
>>>
or
>>> from smartcard.System import readergroups, addreadertogroups, readers
>>> g = readergroups()
>>> g += 'USB$Readers'
>>> print(g)
['SCard$DefaultReaders', 'USB$Readers']
>>> readergroups().remove('USB$Readers')
>>> readergroups()
['SCard$DefaultReaders']
>>>
Monitoring readers¶
You can monitor the insertion or removal of readers using the ReaderObserver interface.
To monitor reader insertion, create a ReaderObserver object that implements an update() method that will be called upon reader/insertion removal. The following sample code implements a ReaderObserver that simply prints the inserted/removed readers on the standard output:
from smartcard.ReaderMonitoring import ReaderObserver
class printobserver(ReaderObserver):
"""A simple reader observer that is notified
when readers are added/removed from the system and
prints the list of readers
"""
def update(self, observable, (addedreaders, removedreaders)):
print("Added readers", addedreaders)
print("Removed readers", removedreaders)
To monitor reader insertion/removal, simply add the observer to the ReaderMonitor:
#! /usr/bin/env python3
"""
Sample script that monitors smartcard readers.
__author__ = "https://www.gemalto.com/"
Copyright 2001-2012 gemalto
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
This file is part of pyscard.
pyscard is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
pyscard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with pyscard; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from time import sleep
from smartcard.ReaderMonitoring import ReaderMonitor, ReaderObserver
class printobserver(ReaderObserver):
"""A simple reader observer that is notified
when readers are added/removed from the system and
prints the list of readers
"""
def update(self, observable, actions):
(addedreaders, removedreaders) = actions
print("Added readers", addedreaders)
print("Removed readers", removedreaders)
if __name__ == "__main__":
print("Add or remove a smartcard reader to the system.")
print("This program will exit in 10 seconds")
print("")
readermonitor = ReaderMonitor()
readerobserver = printobserver()
readermonitor.addObserver(readerobserver)
sleep(10)
# don't forget to remove observer, or the
# monitor will poll forever...
readermonitor.deleteObserver(readerobserver)
import sys
if "win32" == sys.platform:
print("press Enter to continue")
sys.stdin.read(1)
Smart Cards¶
Monitoring Smart Cards¶
You can monitor the insertion or removal of cards using the CardObserver interface.
To monitor card insertion and removal, create a CardObserver object that implements an update() method that will be called upon card insertion/removal. The following sample code implements a CardObserver that simply prints the inserted/removed cards on the standard output, named printobserver. To monitor card insertion/removal, simply add the card observer to the CardMonitor:
#! /usr/bin/env python3
"""
Sample script that monitors smartcard insertion/removal.
__author__ = "https://www.gemalto.com/"
Copyright 2001-2012 gemalto
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
This file is part of pyscard.
pyscard is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
pyscard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with pyscard; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from time import sleep
from smartcard.CardMonitoring import CardMonitor, CardObserver
from smartcard.util import toHexString
# a simple card observer that prints inserted/removed cards
class PrintObserver(CardObserver):
"""A simple card observer that is notified
when cards are inserted/removed from the system and
prints the list of cards
"""
def update(self, observable, actions):
(addedcards, removedcards) = actions
for card in addedcards:
print("+Inserted: ", toHexString(card.atr))
for card in removedcards:
print("-Removed: ", toHexString(card.atr))
if __name__ == "__main__":
print("Insert or remove a smartcard in the system.")
print("This program will exit in 10 seconds")
print("")
cardmonitor = CardMonitor()
cardobserver = PrintObserver()
cardmonitor.addObserver(cardobserver)
sleep(10)
# don't forget to remove observer, or the
# monitor will poll forever...
cardmonitor.deleteObserver(cardobserver)
import sys
if "win32" == sys.platform:
print("press Enter to continue")
sys.stdin.read(1)
Sending APDUs to a Smart Card Obtained from Card Monitoring¶
The update method of the CardObserver receives two lists of Cards objects, the recently added cards and the recently removed cards. A connection can be created to each Card object of the added card list for sending APDUS.
The following sample code implements a CardObserver class named selectDFTELECOMObserver, that connects to inserted cards and transmit an APDU, in our case SELECT DF_TELECOM.
To monitor card insertion, connect to inserted cards and send the APDU, create an instance of selectDFTELECOMObserver and add it to the CardMonitor:
#! /usr/bin/env python3
"""
Sample script that monitors smartcard insertion/removal and select
DF_TELECOM on inserted cards
__author__ = "https://www.gemalto.com/"
Copyright 2001-2012 gemalto
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
This file is part of pyscard.
pyscard is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
pyscard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with pyscard; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from time import sleep
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.CardMonitoring import CardMonitor, CardObserver
from smartcard.util import toHexString
# define the apdus used in this script
GET_RESPONSE = [0xA0, 0xC0, 00, 00]
SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
DF_TELECOM = [0x7F, 0x10]
# a simple card observer that tries to select DF_TELECOM on an inserted card
class selectDFTELECOMObserver(CardObserver):
"""A simple card observer that is notified
when cards are inserted/removed from the system and
prints the list of cards
"""
def __init__(self):
self.observer = ConsoleCardConnectionObserver()
def update(self, observable, actions):
(addedcards, removedcards) = actions
for card in addedcards:
print("+Inserted: ", toHexString(card.atr))
card.connection = card.createConnection()
card.connection.connect()
card.connection.addObserver(self.observer)
apdu = SELECT + DF_TELECOM
response, sw1, sw2 = card.connection.transmit(apdu)
if sw1 == 0x9F:
apdu = GET_RESPONSE + [sw2]
response, sw1, sw2 = card.connection.transmit(apdu)
for card in removedcards:
print("-Removed: ", toHexString(card.atr))
if __name__ == "__main__":
print("Insert or remove a SIM card in the system.")
print("This program will exit in 60 seconds")
print("")
cardmonitor = CardMonitor()
selectobserver = selectDFTELECOMObserver()
cardmonitor.addObserver(selectobserver)
sleep(60)
# don't forget to remove observer, or the
# monitor will poll forever...
cardmonitor.deleteObserver(selectobserver)
import sys
if "win32" == sys.platform:
print("press Enter to continue")
sys.stdin.read(1)
Connections¶
Connecting to a card and sending APDUs is done thru a CardConnection object. CardConnection objects are created using a CardRequest, or by the CardMonitoring.
Creating a Connection from a CardRequest¶
A successful CardRequest returns a CardService matching the requested card service for the card, or a PassThruCardService if no specific card service was required:
>>> from smartcard.CardType import AnyCardType
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.util import toHexString
>>>
>>> cardtype = AnyCardType()
>>> cardrequest = CardRequest(timeout=1, cardType=cardtype)
>>> cardservice = cardrequest.waitforcard()
>>>
>>> cardservice.connection.connect()
>>> print(toHexString(cardservice.connection.getATR()))
3B 16 94 20 02 01 00 00 0D
>>> print(cardservice.connection.getReader())
SchlumbergerSema Reflex USB v.2 0
Each CardService has a connection attribute, which is a CardConnection for the card.
Creating Connection from CardMonitoring¶
The update method of a CardObserver receives a tuple with a list of connected cards and a list of removed cards. To create a CardConnection from a card object, use the createConnection() method of the desired card:
class myobserver(CardObserver):
def update(self, observable, (addedcards, removedcards)):
for card in addedcards:
print("+Inserted:", toHexString(card.atr))
card.connection = card.createConnection()
card.connection.connect()
response, sw1, sw2 = card.connection.transmit(SELECT_DF_TELECOM)
print("%.2x %.2x" % (sw1, sw2))
Full sample code¶
#! /usr/bin/env python3
"""
Sample script that monitors card insertions,
connects to cards and transmit an apdu
__author__ = "https://www.gemalto.com/"
Copyright 2001-2012 gemalto
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
This file is part of pyscard.
pyscard is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
pyscard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with pyscard; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from time import sleep
from smartcard.CardMonitoring import CardMonitor, CardObserver
from smartcard.util import *
# replace by your favourite apdu
SELECT_DF_TELECOM = [0xA0, 0xA4, 0x00, 0x00, 0x02, 0x7F, 0x10]
class transmitobserver(CardObserver):
"""A card observer that is notified when cards are inserted/removed
from the system, connects to cards and SELECT DF_TELECOM"""
def __init__(self):
self.cards = []
def update(self, observable, actions):
(addedcards, removedcards) = actions
for card in addedcards:
if card not in self.cards:
self.cards += [card]
print("+Inserted: ", toHexString(card.atr))
card.connection = card.createConnection()
card.connection.connect()
response, sw1, sw2 = card.connection.transmit(SELECT_DF_TELECOM)
print(f"{sw1:.2x} {sw2:.2x}")
for card in removedcards:
print("-Removed: ", toHexString(card.atr))
if card in self.cards:
self.cards.remove(card)
if __name__ == "__main__":
print("Insert or remove a smartcard in the system.")
print("This program will exit in 100 seconds")
print("")
cardmonitor = CardMonitor()
cardobserver = transmitobserver()
cardmonitor.addObserver(cardobserver)
sleep(100)
Card Connection Decorators¶
APDUs are transmitted to a card using the CardConnection object. It is sometime useful to change transparently the behaviour of a smart card connection, for example to establish automatically a secure channel, or filter and modify on the fly some APDU commands or responses, or the smart card ATR. pyscard uses the decorator design pattern to dynamically change the behaviour of a smart card connection. A CardConnectionDecorator modifies the behaviour of a CardConnection object. For example, the following CardConnectionDecorator overwrites the CardConnection getATR() method:
class FakeATRConnection(CardConnectionDecorator):
'''This decorator changes the fist byte of the ATR.'''
def __init__(self, cardconnection):
CardConnectionDecorator.__init__(self, cardconnection)
def getATR(self):
"""Replace first BYTE of ATR by 3F"""
atr = CardConnectionDecorator.getATR(self)
return [0x3f] + atr [1:]
To apply the decorator, just construct the decorator around the CardConnection instance to wrap and use the decorator in place of the card connection object:
# request any card type
cardtype = AnyCardType()
cardrequest = CardRequest(timeout=1.5, cardType=cardtype)
cardservice = cardrequest.waitforcard()
# attach the console tracer
observer = ConsoleCardConnectionObserver()
cardservice.connection.addObserver(observer)
# attach our decorator
cardservice.connection = FakeATRConnection(cardservice.connection)
# connect to the card and perform a few transmits
cardservice.connection.connect()
print('ATR', toHexString(cardservice.connection.getATR()))
Decorators can be nested. For example to nest a FakeATRConnection with a SecureChannelConnection, use the following construction:
# attach our decorator
FakeATRConnection(SecureChannelConnection(cardservice.connection))
# connect to the card and perform a few transmits
cardservice.connection.connect()
print('ATR', toHexString(cardservice.connection.getATR()))
Full sample code:
#! /usr/bin/env python3
"""
Sample script that illustrates card connection decorators.
__author__ = "https://www.gemalto.com/"
Copyright 2001-2012 gemalto
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
This file is part of pyscard.
pyscard is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
pyscard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with pyscard; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from smartcard.CardConnectionDecorator import CardConnectionDecorator
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.CardRequest import CardRequest
from smartcard.CardType import AnyCardType
from smartcard.util import toHexString
# define two custom CardConnectionDecorator
# the decorators are very simple, just to illustrate
# shortly how several decorators can be added to the
# card connection
class SecureChannelConnection(CardConnectionDecorator):
"""This decorator is a mockup of secure channel connection.
It merely pretends to cypher/uncypher upon apdu transmission."""
def __init__(self, cardconnection):
CardConnectionDecorator.__init__(self, cardconnection)
def cypher(self, data):
"""Cypher mock-up; you would include the secure channel logics here."""
print("cyphering", toHexString(data))
return data
def uncypher(self, data):
"""Uncypher mock-up;
you would include the secure channel logics here."""
print("uncyphering", toHexString(data))
return data
def transmit(self, command, protocol=None):
"""Cypher/uncypher APDUs before transmission"""
cypheredbytes = self.cypher(command)
data, sw1, sw2 = CardConnectionDecorator.transmit(self, cypheredbytes, protocol)
if [] != data:
data = self.uncypher(data)
return data, sw1, sw2
class FakeATRConnection(CardConnectionDecorator):
"""This decorator changes the fist byte of the ATR. This is just an example
to show that decorators can be nested."""
def __init__(self, cardconnection):
CardConnectionDecorator.__init__(self, cardconnection)
def getATR(self):
"""Replace first BYTE of ATR by 3F"""
atr = CardConnectionDecorator.getATR(self)
return [0x3F] + atr[1:]
# define the apdus used in this script
GET_RESPONSE = [0xA0, 0xC0, 00, 00]
SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
DF_TELECOM = [0x7F, 0x10]
# request any card type
cardtype = AnyCardType()
cardrequest = CardRequest(timeout=1.5, cardType=cardtype)
cardservice = cardrequest.waitforcard()
# attach the console tracer
observer = ConsoleCardConnectionObserver()
cardservice.connection.addObserver(observer)
# attach our decorator
cardservice.connection = FakeATRConnection(
SecureChannelConnection(cardservice.connection)
)
# connect to the card and perform a few transmits
cardservice.connection.connect()
print("ATR", toHexString(cardservice.connection.getATR()))
apdu = SELECT + DF_TELECOM
response, sw1, sw2 = cardservice.connection.transmit(apdu)
if sw1 == 0x9F:
apdu = GET_RESPONSE + [sw2]
response, sw1, sw2 = cardservice.connection.transmit(apdu)
import sys
if "win32" == sys.platform:
print("press Enter to continue")
sys.stdin.read(1)
Exclusive Card Connection Decorator¶
The ExclusiveConnectCardConnection object performs an exclusive connection to the card, i.e. no other thread or process will be able to connect to the card. With PCSC readers, this is done by performing a SCardConnect with the SCARD_SHARE_EXCLUSIVE attribute.
from smartcard.CardType import AnyCardType
from smartcard.CardRequest import CardRequest
from smartcard.CardConnection import CardConnection
from smartcard.util import toHexString
from smartcard.ExclusiveConnectCardConnection import ExclusiveConnectCardConnection
# request any card type
cardtype = AnyCardType()
cardrequest = CardRequest(timeout=5, cardType=cardtype)
cardservice = cardrequest.waitforcard()
# attach our decorator
cardservice.connection = ExclusiveConnectCardConnection(cardservice.connection)
# connect to the card and perform a few transmits
cardservice.connection.connect()
print('ATR', toHexString(cardservice.connection.getATR()))
Exclusive Transmit Card Connection Decorator¶
The ExclusiveTransmitCardConnection performs an exclusive transaction to the card, i.e. a series of transmit that cannot be interrupted by other threads’ transmits. To do so, include the desired transmits between an lock() and unlock() method call on the ExclusiveTransmitCardConnection:
#! /usr/bin/env python3
"""
Sample script that illustrates exclusive card connection decorators.
__author__ = "https://www.gemalto.com/"
Copyright 2001-2012 gemalto
Author: Jean-Daniel Aussel, mailto:jean-daniel.aussel@gemalto.com
This file is part of pyscard.
pyscard is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
pyscard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with pyscard; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.CardRequest import CardRequest
from smartcard.CardType import AnyCardType
from smartcard.ExclusiveConnectCardConnection import ExclusiveConnectCardConnection
from smartcard.ExclusiveTransmitCardConnection import ExclusiveTransmitCardConnection
from smartcard.util import toHexString
# define the apdus used in this script
GET_RESPONSE = [0xA0, 0xC0, 00, 00]
SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
DF_TELECOM = [0x7F, 0x10]
# request any card type
cardtype = AnyCardType()
cardrequest = CardRequest(timeout=5, cardType=cardtype)
cardservice = cardrequest.waitforcard()
# attach the console tracer
observer = ConsoleCardConnectionObserver()
cardservice.connection.addObserver(observer)
# attach our decorator
cardservice.connection = ExclusiveTransmitCardConnection(
ExclusiveConnectCardConnection(cardservice.connection)
)
# connect to the card and perform a few transmits
cardservice.connection.connect()
print("ATR", toHexString(cardservice.connection.getATR()))
try:
# lock for initiating transaction
cardservice.connection.lock()
apdu = SELECT + DF_TELECOM
response, sw1, sw2 = cardservice.connection.transmit(apdu)
if sw1 == 0x9F:
apdu = GET_RESPONSE + [sw2]
response, sw1, sw2 = cardservice.connection.transmit(apdu)
finally:
# unlock connection at the end of the transaction
cardservice.connection.unlock()
import sys
if "win32" == sys.platform:
print("press Enter to continue")
sys.stdin.read(1)
Secure Channel Card Connection Decorator¶
Another sample of application of CardConnection decorators is to implement secure channel. The following sample is a template CardConnection decorator for secure channel, where each command APDU is cyphered and each response APDU is uncyphered:
class SecureChannelConnection(CardConnectionDecorator):
'''This decorator is a mockup of secure channel connection.
It merely pretends to cypher/uncypher upon apdu transmission.'''
def __init__(self, cardconnection):
CardConnectionDecorator.__init__(self, cardconnection)
def cypher(self, data):
'''Cypher mock-up; you would include the secure channel logics here.'''
print('cyphering', toHexString(data))
return data
def uncypher(self, data):
'''Uncypher mock-up; you would include the secure channel logics here.'''
print('uncyphering', toHexString(data))
return data
def transmit(self, data, protocol=None):
"""Cypher/uncypher APDUs before transmission"""
cypheredbytes = self.cypher(data)
data, sw1, sw2 = CardConnectionDecorator.transmit(self, cypheredbytes, protocol)
if [] != data:
data = self.uncypher(data)
return data, sw1, sw2
A word on cryptography¶
Smart card are security devices. As a result, smart card applications usually require some kind cryptography, for example to establish a secure channel with the smart card. One of the reference cryptographic modules for Python is pycrypto, the Python cryptographic toolkit. This section shows briefly the basics of pycrypto to give you a quick start to include cryptography in your Python smart card applications.
Binary strings and list of bytes¶
Cryptography packages frequently process bytes
objects like b"\x01\x42\x70\x23"
,
whereas pyscard processes APDUs as list of integers, such as [0x01, 0x42, 0x70, 0x23]
.
It’s possible to convert between these objects like this:
test_data = [0x01, 0x42, 0x70, 0x23]
bytes_object = bytes(test_data)
list_of_ints = list(bytes_object)
print(bytes_object, list_of_ints)
To hash several bytes of data with SHA-1:
import hashlib
test_data = [0x01, 0x42, 0x70, 0x23]
digest = hashlib.sha1(bytes(test_data)).hexdigest()
print(digest)
To perform MD5 hashing, just replace the .sha1()
function with .md5()
in the previous script.
Secret key cryptography¶
pycrypto supports several secret key algorithms, such as DES, triple DES, AES, blowfish, or IDEA. To perform triple DES ciphering in ECB mode:
from Crypto.Cipher import DES3
from smartcard.util import toBytes
key = "31323334353637383132333435363738"
key_as_binstring = bytes(toBytes(key))
zdes = DES3.new(key_as_binstring, DES3.MODE_ECB)
message = "71727374757677787172737475767778"
message_as_binstring = bytes(toBytes(message)))
encrypted_as_string = zdes.encrypt(message_as_binstring)
decrypted_as_string = zdes.decrypt(encrypted_as_string)
print(message_as_binstring, encrypted_as_string, decrypted_as_string)