- Joined
- Nov 26, 2020
- Messages
- 716
Table of contents
pyscard user manual
Copyright
Copyright 2001-2009 Gemalto
Author: Jean-Daniel Aussel, [email protected]
This file is part of a pyscard.
pyscard is free software; you can redistribute 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 details.
You should have received a copy of the GNU Lesser General Public License along with a pyscard; otherwise, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
Introduction
The pyscard smart card library is a platform for building smart card-enabled applications in Python. The smart card module is built on top of the PCSC API Python wrapper module.
pyscard supports Windows 2000 and XP using Microsoft Smart Card Base components , and Linux and Mac OS X using PCSC-lite .
Smart cards
Smart cards are plastic cards, usually the size of a credit card and containing a microprocessor. Smart cards communicate with the outside world through a serial port interface and half-duplex protocol. Smart cards are usually connected to a special terminal, such as a point-of-sale terminal or mobile phone. Sometimes smart cards need to be paired with personal computers. This includes some applications such as secure login, encrypted mail, or digital signature, but also some PC-based smart card tools used to personalize or edit smart card content. Smart cards are connected to 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,
The PCSC Working Group has defined a standard API for connecting smart cards and smart card readers to a PC. The resulting reference implementation in Linux and Mac OS X operating systems is PC / SC-lite. All Windows operating systems also include built-in smart card support, commonly referred to as PCSC.
The PCSC API is implemented in C, and multiple bridges are provided to access the PCSC API from different languages such as java or Visual Basic. pyscard is a Python environment for developing smart card PC applications on Linux, Mac OS X and Windows. The pyscard sublayer interface to the PCSC API for accessing smart cards and smart card readers.
Quick Beginnings
In this section, we will see some options for how to send APDU commands to a smart card.
Reader-Oriented Approach
A PC application communicates with the card by sending a list of bytes known as Application Protocol Data Units (APDUs). The format of these APDUs is defined in the ISO7816-4 standard. To send an APDU to a card, the app must first connect to the card through a smart card reader. Smart card-enabled applications that first select a smart card reader and then connect to a card inserted into the smart card reader use a reader-centric approach.
In a reader-centric approach, we open a connection to the card through the smart card reader and send APDU commands to the card using the connection:
Code:
>>> from smartcard.System import readers
>>> from smartcard.util import toHexString
>>>
>>> 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 can be obtained using the reader () function. We create a connection to the first reader (index 0 for reader 1, 1 for reader 2, ...) with a call to r [0] .createConnection () and connect to the card using the connect () method of connection. We can then send APDU commands to the card using the transfer () method.
However, scripts written with a reader in mind have the following disadvantages:
Reset response (ATR)
The first response from a smart card inserted into a smart card reader is an ATR call. The purpose of ATR is to describe the supported communication parameters. The smart card reader, smart card reader driver, and the operating system will use these parameters to communicate with the card. ATR is described in the ISO7816-3 standard. The first ATR bytes describe the voltage convention (forward or reverse), followed by bytes describing the available communication interfaces and their corresponding parameters. These interface bytes are followed by historical bytes that are not standardized and are useful for transferring service information such as card type, firmware version, or card status. Finally, these history bytes are ultimately followed by a checksum byte.
The smartcard.ATR class is a pyscard utility class that can interpret the ATR content:
Code:
#! /usr/bin/env python
"""
Sample script for the smartcard.ATR utility class.
from __future__ import print_function
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())
This produces the following result:
Code:
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, ATR can be used to locate a specific card, either by trying to match a card with a full ATR, or by matching a card with some data in history bytes. Smartcard-enabled PC applications that detect smartcards based on ATR content take a card-centric approach, regardless of the smartcard reader in which the card is inserted.
Card-centric approach
In the card -centric approach , we create a request for a specific type of card and wait until the card matching the request is inserted. Once the corresponding card is entered, a card connection is created automatically and we can send APDU commands to the card using that connection.
Card request via ATR
The following scripts request a map with a known ATR:
Code:
>>> 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 known ATR, you must first create an ATRCardType object with the desired ATR:
Code:
>>> cardtype = ATRCardType ( toBytes ( "3B 16 94 20 02 01 00 00 0D" ) )
And then create a CardRequest for that card type. In the example, we are requesting a timeout of 1 second.
Code:
>>> cardrequest = CardRequest( timeout=1, cardType=cardtype )
>>> cardservice = cardrequest.waitforcard()
The waitforcard () function will either return with the card service or timed out. The card service connection attribute can then be used to send APDU commands to the card, as in the reader-centric approach.
Code:
>>> cardservice.connection.connect()
>>> print toHexString( cardservice.connection.getATR() )
If necessary, the reader used for the connection can be accessed through the CardConnection object:
Code:
>>> print cardservice.connection.getReader()
SchlumbergerSema Reflex USB v.2 0
ATRCardType also supports mask:
Code:
>>> 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 types of cards are available, and you can create new types of cards as described below.
Request any card
AnyCardType is useful for requesting any card in any reader:
Code:
>>> 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 Card Types
Custom card types can be created, for example a card type that checks ATR and historical card bytes. To create your own CardType, inherit your CardType class from the base CardType class (or any other CardType) and override the match () method. For example, to create a DCCardType that will match cards with a direct convention (the first ATR byte is 0x3b):
Code:
>>> 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 using a map-centric approach eliminate the problems of a reader-centered approach:
parameters are most important for negotiating a protocol between the smart card reader and the card. The main smart card protocols are T = 0 and T = 1 for transferring bytes or blocks, respectively. The required protocol can be specified when connecting a card or transferring a card.
By default, the connect () method of the CardConnection object. Will try to connect using the T = 0 or T = 1 protocol. To force the connection protocol, you can pass the required protocol to the connect () method.
Code:
>>> 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 () transfer method:
Code:
>>> from smartcard.CardType import AnyCardType
>>> from smartcard.CardConnection import CardConnection
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.util import toHexString, toBytes
>>>
>>> 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
>>>
Object-Oriented Approach
In the object-oriented approach, we associate a high-level object with a set of smart cards supported by the object. For example, we associate the javacard loader class with the javacard smartcard suite. We create a request for a specific object and wait until the map supported by the object is inserted. After the map supported by the object is inserted, we perform the required function by calling the methods of the object.
To be written ...
Tracking APDU
Brute Force
An easy way to keep track of APDU commands and responses is to insert print statements around calls to the transfer () method:
Code:
>>> 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 rather difficult to read because there are more trace statements than the actual apdu passes.
A slight improvement in visibility would be replacing print instructions with functions, for example:
Code:
>>> 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 None==response: 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 Map Attachment Watchers to track apdu transmission The
preferred solution is to implement a Map Attachment Observer and register the observer with a Map Attachment . The card connection will then notify the observer when the card connection events occur (e.g. connect, disconnect, apdu command, or apdu response). This is shown in the following script:
Code:
>>> 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 scenario, the ConsoleCardConnectionObserver attaches to the card service connection after the call to watiforcard () returns.
Code:
>>> observer=ConsoleCardConnectionObserver()
>>> cardservice.connection.addObserver( observer )
On card connection events (connect, disconnect, send apdu command, receive apdu response), the card connection notifies its observers using CarConnectionEvent, including the event type and event data. The ConsoleCardConnectionObserver is a simple observer that will print event connection cards to the console. The class definition is as follows:
Code:
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:])
So the console map connection explorer prints the connect, disconnect, command and response apdu events:
Code:
>>> 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
The map connection observer update method is called on a map connection event with a connection parameter and connection event. CardConnectionEvent class definition is as follows:
Code:
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 map connection browser, for example to do fancy output in a wxWindows frame, or interpreted by apdu. The following scripts define a small interpreter apdu SELECT and GET RESPONSE:
Code:
>>> 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
>>>
Complete code example
Code:
#! /usr/bin/env python
"""
Sample script that defines a custom card connection observer.
"""
from __future__ import print_function
from smartcard.CardType import AnyCardType
from smartcard.CardRequest import CardRequest
from smartcard.CardConnectionObserver import CardConnectionObserver
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
After the APDU has been transmitted and processed, the smart card returns a pair of status words, SW1 and SW2, to report various success or error codes after the necessary processing. Some of these success or error codes are standardized, for example, in ISO7816-4, ISO7816-8, or ISO7816-9. Other status word codes are standardized by standards bodies such as Open Platform (eg javacard), 3GPP (eg SIM or USIM cards), or Eurocard-Mastercard-Visa (EMV) (eg bank cards). Finally, any smart card application developer can define their own application-specific codes; for example, the MUSCLE applet defines a set of private codes associated with the functions of the MUSCLE applet.
Some of these status word codes are unique, but others have different meanings depending on the type of card and the supported standards. For example, ISO7816-4 defines error code 0x62 0x82 as "File invalid", whereas in Open Platform 2.1, the same error code is defined as "Card lifecycle - CARD_LOCKED". The resulting list of error codes that can be returned by a smart card and their interpretation depends on the type of card. The following discussion describes possible strategies for checking and reporting smart card status word errors.
Brute force to check for APDU transfer errors
As far as APDU tracing is concerned, an easy way to check for errors in response APDUs during script execution is to insert test statements after the call to the transfer () method:
Code:
>>> 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 messages than the actual apdu reports.
Improving visibility is to wrap the transfer instruction inside the mytransmit function, for example:
Code:
>>> 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 for error checking is to use smarcard.sw.ErrorChecker, as described in the next section.
Checking for APDU Transmission Errors with Error Checking
Status word errors can arise from a variety of sources. The ISO7816-4 standards define status words for sw1 in the range 0x62 to 0x6F and some values for sw2, with the exception of 0x66, which is reserved for security issues. The ISO7816-8 standards define other status words such as sw1 = 0x68 and sw2 = 0x83 or 0x84 for command concatenation errors. Other standards, such as Open Platform, define additional error status words, such as sw1 = 0x94 and sw2 = 0x84.
The preferred strategy for checking status word errors is based on separate error checking tools (smartcard.sw.ErrorChecker) that can be chained together to form the error checking chain (smartcars.sw.ErrorCheckingChain).
Error Checking Tools The Error
Checker is a class derived from ErrorChecker that checks for recognized error conditions sw1, sw2 when invoked, and throws an exception when such a condition is encountered. This is shown in the following example:
Code:
>>> 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 the error checker does not throw an exception because 90 00 does not report errors. However, the second call throws CheckingErrorException.
Checking Chains Error Checking
tools can be combined into an error checking chain. Each checker in the chain is called until an error condition is met, in which case an exception is thrown. This is shown in the following example:
Code:
>>> 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!'
>>>
This example creates an error checking chain that first checks for iso 7816-9 errors, then iso7816-8 errors, and finally iso7816-4 errors.
The first call in the error chain does not throw an exception because 90 00 does not report errors. However, the second call throws a CheckingErrorException thrown by the iso7816-9 error checker.
Filtering Exceptions
You can filter out unwanted exceptions in the chain by adding the filtered exception to the error checking chain:
Code:
>>> 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 in the error chain with sw1 sw2 = 62 00 throws a WarningProcessingException.
Code:
...
>>> 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 throw an exception:
Code:
>>> from smartcard.sw.SWExceptions import WarningProcessingException
>>>
>>> errorchain[0].addFilterException( WarningProcessingException )
>>> errorchain[0]( [], 0x62, 0x00 )
>>>
Complete code example
Code:
#! /usr/bin/env python
"""Sample script for APDU error checking.
"""
from __future__ import print_function
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.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 APDU Response Errors for Card Attachment
To detect APDU response errors during transmission, simply set up an error checking chain for the connection used for transmission:
Code:
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 the SIM will result in an output similar to the following:
Code:
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 will result in:
Code:
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 map connection error checking chain. The map connection will use chaining to check for errors when it receives apdu's response:
Writing a Custom Error Checker
Implementing a custom error checker requires implementing the op21_ErrorChecker subclass and overriding the __call__ method. The following error checker throws a SecurityRelatedException when sw1 = 0x66 and sw2 = 0x00.
Custom checkers can be used alone, as in the following example, or linked to other error checking tools.
Code:
#! /usr/bin/env python
"""Sample script for APDU error checking with a custom error checker.
"""
from __future__ import print_function
from smartcard.CardType import AnyCardType
from smartcard.CardRequest import CardRequest
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.sw.ErrorCheckingChain import ErrorCheckingChain
from smartcard.sw.ErrorChecker import ErrorChecker
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, "%x %x" % (e.sw1, e.sw2))
cardservice.connection.disconnect()
import sys
if 'win32' == sys.platform:
print('press Enter to continue')
sys.stdin.read(1)
Smart card readers
output reading device list of smart cards
The easiest way to get a list of smart card reader devices - a feature smartcard.System.readers ():
Code:
>>> import smartcard.System
>>> print smartcard.System.readers()
['Schlumberger e-gate 0', 'SchlumbergerSema Reflex USB v.2 0', 'Utimaco CardManUSB 0']
>>>
Organizing Smart Card Readers into Reader
Groups Managing Reader Groups is only available on Windows, since PCSC-lite does not currently support Reader Group Management.
Readers can be organized into reading groups. To get groups of smart card readers use readergroups ():
Code:
>>> import smartcard.System
>>> print smartcard.System.readergroups()
['SCard$DefaultReaders']
>>>
The readergroups () object has all the attributes of the list. To add a group of readers, just use the + operator, for example:
Code:
>>> 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 add and insert methods as well as the + operator, for example:
Code:
>>> 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
Code:
>>> 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']
>>>
Smart card reader groups are not saved until the reader is added to the group. To add a reader to a reader group, use addreadertogroups ():
Code:
>>> 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 group of readers, all list operators are available to manipulate groups of readers, including pop () or remove ():
Code:
>>> 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
Code:
>>> 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 track the reader insertion, create a ReaderObserver object that implements the update () method that will be called when the reader / insert is removed. The following code example implements ReaderObserver, which simply prints inserted / removed readers to standard output:
Code:
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 track the installation / removal of a reader, simply add an observer to ReaderMonitor:
Code:
#! /usr/bin/env python
"""
Sample script that monitors smartcard readers.
"""
from __future__ import print_function
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 keep track of card insertion and retrieval, create a CardObserver object that implements the update () method that will be called when a card is inserted / retrieved. The following code example implements CardObserver, which simply prints inserted / removed cards to standard output named printobserver. To track the insertion / removal of the card, simply add the card explorer to CardMonitor:
Code:
#! /usr/bin/env python
"""
Sample script that monitors smartcard insertion/removal.
"""
from __future__ import print_function
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 Smart Card Obtained from Card Monitoring
The CardObserver update method retrieves two lists of Cards objects: recently added cards and recently removed cards. A connection can be created with each Card object from the added card list to send APDUS.
The following code example implements a CardObserver class named selectDFTELECOMObserver that connects to the inserted cards and passes the APDU, in our case SELECT DF_TELECOM.
To monitor card insertion, connect to inserted cards and send APDUs, instantiate selectDFTELECOMObserver and add it to CardMonitor:
Code:
#! /usr/bin/env python
"""
Sample script that monitors smartcard insertion/removal and select DF_TELECOM on inserted cards
"""
from __future__ import print_function
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 an APDU is done through the CardConnection object. CardConnection objects are created using CardRequest or CardMonitoring.
Making a connection from CardRequest
A successful CardRequest returns the CardService corresponding to the requested card service for the card, or PassThruCardService if no specific card service was required:
Code:
>>> 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 the CardConnection for the card.
Creating a Connection from CardMonitoring
The CardObserver update method retrieves 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:
Code:
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)
Complete code example
Code:
#! /usr/bin/env python
"""
Sample script that monitors card insertions, connects to cards and transmit an apdu
"""
from __future__ import print_function
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("%.2x %.2x" % (sw1, sw2))
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)
APDU card connection decorators are passed to the card using the CardConnection object. It is sometimes useful to transparently change the connection behavior of a smart card, for example to automatically establish a secure channel or filter and change on the fly some of the commands or responses from the APDU or ATR of the smart card. pyscard uses the decorator design pattern to dynamically change the connection behavior of a smart card. CardConnectionDecorator changes the behavior of the CardConnection object. For example, the following CardConnectionDecorator overwrites the CardConnection getATR () method:
Code:
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 a decorator, simply create a decorator around the CardConnection instance to wrap it and use the decorator instead of the card connection object:
Code:
# 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 FakeATRConnection in a SecureChannelConnection, use the following construct:
Code:
# 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() )
Complete code example:
Code:
#! /usr/bin/env python
"""
Sample script that illustrates card connection decorators.
"""
from __future__ import print_function
from smartcard.CardType import AnyCardType
from smartcard.CardRequest import CardRequest
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.CardConnectionDecorator import CardConnectionDecorator
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, bytes):
'''Cypher mock-up; you would include the secure channel logics here.'''
print('cyphering', toHexString(bytes))
return bytes
def uncypher(self, data):
'''Uncypher mock-up;
you would include the secure channel logics here.'''
print('uncyphering', toHexString(data))
return data
def transmit(self, bytes, protocol=None):
"""Cypher/uncypher APDUs before transmission"""
cypheredbytes = self.cypher(bytes)
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 map. With PCSC readers, this is done by executing a SCardConnect with the SCARD_SHARE_EXCLUSIVE attribute.
Code:
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() )
The exclusive transfer card connection decorator
ExclusiveTransmitCardConnection performs an exclusive card transaction, that is, a series of transfers that cannot be interrupted by transfers from other streams. To do this, include the desired transfers between calls to the lock () and unlock () methods in the ExclusiveTransmitCardConnection:
Code:
#! /usr/bin/env python
"""
Sample script that illustrates exclusive card connection decorators.
"""
from __future__ import print_function
from smartcard.CardType import AnyCardType
from smartcard.CardRequest import CardRequest
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.util import toHexString
from smartcard.ExclusiveConnectCardConnection import \
ExclusiveConnectCardConnection
from smartcard.ExclusiveTransmitCardConnection import \
ExclusiveTransmitCardConnection
# 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 example of the use of CardConnection decorators is to implement a secure channel. The following example is a templated CardConnection decorator for a secure channel, where each command APDU is encrypted and each response APDU is not encrypted:
Code:
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, bytes ):
'''Cypher mock-up; you would include the secure channel logics here.'''
print 'cyphering', toHexString( bytes )
return bytes
def uncypher( self, data ):
'''Uncypher mock-up; you would include the secure channel logics here.'''
print 'uncyphering', toHexString( data )
return data
def transmit
)
return data, sw1, sw2
A few words about cryptography
Smart cards are security devices. As a result, smart card applications usually require some kind of cryptography, such as establishing a secure channel with a smart card. One of the reference cryptographic modules for Python is pycrypto , a Python cryptographic toolkit. This section briefly shows the basics of pycrypto so you can quickly get started incorporating cryptography into Python smart card applications.
Binary strings and list of bytes
pycrypto handles binary strings, that is, Python strings containing characters such as '01427023', while pyscard treats APDUs as a list of bytes, such as [0x01, 0x42, 0x70, 0x23]. The HexListToBinString and BinStringToHexList utility function (and their versions with the short names hl2bs and bs2hl) provide conversion between the two types.
Code:
from smartcard.util import HexListToBinString, BinStringToHexList
test_data = [ 0x01, 0x42, 0x70, 0x23 ]
binstring = HexListToBinString( test_data )
hexlist = BinStringToHexList( binstring )
print binstring, hexlist
pycrypto supports the following hashing algorithms: SHA-1, MD2, MD4, and MD5. To hash 16 bytes of data using SHA-1:
Code:
from Crypto.Hash import SHA
from smartcard.util import toHexString, PACK
test_data = [ 0x01, 0x42, 0x70, 0x23 ]
binstring = HexListToBinString( test_data )
zhash = SHA.new( binstring )
hash_as_string = zhash.digest()[:16]
hash_as_bytes = BinStringToHexList( hash_as_string )
print hash_as_string, ',', toHexString( hash_as_bytes, PACK )
To perform MD5 hashing, simply replace SHA with MD5 in the previous script. Private
key cryptography
pycrypto supports multiple secret key algorithms such as DES, triple DES, AES, blowfish, or IDEA. To perform triple DES encryption in ECB mode:
Code:
from Crypto.Cipher import DES3
from smartcard.util import toBytes
key = "31323334353637383132333435363738"
key_as_binstring = HexListToBinString( toBytes( key ) )
zdes = DES3.new( key_as_binstring, DES3.MODE_ECB )
message = "71727374757677787172737475767778"
message_as_binstring = HexListToBinString( 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
pyscard user manual
- Copyright
- Introduction
- Smart cards
- Fast start
- Reader-centered approach
- Reset response (ATR)
- Card-centric approach
- Card request via ATR
- Request any card
- Custom card types
- Selecting the card communication protocol
- Object Oriented Approach
- APDU Tracking
- Brute force
- Using map connection watchers to track apdu transfer
- Complete code example
- Testing for APDU transmission errors
- Brute force to check for APDU transmission errors
- Checking for APDU Transmission Errors Using Error Checking Tools
- Error checking tools
- Error checking chains
- Filtering Exceptions
- Complete code example
- Detecting APDU errors in response to connect the card
- Writing a custom error checker
- Smart Card Readers
- Listing Smart Card Readers
- Organizing smart card readers into reader groups
- Reader monitoring
- Smart cards
- Monitoring smart cards
- Sending an APDU to a smart card resulting from card monitoring
- Connections
- Creating a connection from CardRequest
- Making a connection from CardMonitoring
- Complete code example
- Map connection decorators
- Exclusive card connection decorator
- Exclusive decorator of the transfer card connection
- Secure channel card connection decorator
- A few words about cryptography
- Binary strings and list of bytes
- Secret Key Cryptography
Copyright
Copyright 2001-2009 Gemalto
Author: Jean-Daniel Aussel, [email protected]
This file is part of a pyscard.
pyscard is free software; you can redistribute 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 details.
You should have received a copy of the GNU Lesser General Public License along with a pyscard; otherwise, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
Introduction
The pyscard smart card library is a platform for building smart card-enabled applications in Python. The smart card module is built on top of the PCSC API Python wrapper module.
pyscard supports Windows 2000 and XP using Microsoft Smart Card Base components , and Linux and Mac OS X using PCSC-lite .
Smart cards
Smart cards are plastic cards, usually the size of a credit card and containing a microprocessor. Smart cards communicate with the outside world through a serial port interface and half-duplex protocol. Smart cards are usually connected to a special terminal, such as a point-of-sale terminal or mobile phone. Sometimes smart cards need to be paired with personal computers. This includes some applications such as secure login, encrypted mail, or digital signature, but also some PC-based smart card tools used to personalize or edit smart card content. Smart cards are connected to 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,
The PCSC Working Group has defined a standard API for connecting smart cards and smart card readers to a PC. The resulting reference implementation in Linux and Mac OS X operating systems is PC / SC-lite. All Windows operating systems also include built-in smart card support, commonly referred to as PCSC.
The PCSC API is implemented in C, and multiple bridges are provided to access the PCSC API from different languages such as java or Visual Basic. pyscard is a Python environment for developing smart card PC applications on Linux, Mac OS X and Windows. The pyscard sublayer interface to the PCSC API for accessing smart cards and smart card readers.
Quick Beginnings
In this section, we will see some options for how to send APDU commands to a smart card.
Reader-Oriented Approach
A PC application communicates with the card by sending a list of bytes known as Application Protocol Data Units (APDUs). The format of these APDUs is defined in the ISO7816-4 standard. To send an APDU to a card, the app must first connect to the card through a smart card reader. Smart card-enabled applications that first select a smart card reader and then connect to a card inserted into the smart card reader use a reader-centric approach.
In a reader-centric approach, we open a connection to the card through the smart card reader and send APDU commands to the card using the connection:
Code:
>>> from smartcard.System import readers
>>> from smartcard.util import toHexString
>>>
>>> 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 can be obtained using the reader () function. We create a connection to the first reader (index 0 for reader 1, 1 for reader 2, ...) with a call to r [0] .createConnection () and connect to the card using the connect () method of connection. We can then send APDU commands to the card using the transfer () method.
However, scripts written with a reader in mind have the following disadvantages:
- the reader's index or reader's name is hardcoded in scripts; scripts need to be edited to match the configuration of each user; for example, in the previous script, we would have to edit the script and change r [0] to r [1] to use the second reader
- there is no a priori information that the card is in the reader; to detect the insertion of the card, we would have to execute the script and eventually catch a CardConnectionException, which 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 expected card type; in the previous example, we could try to select the DF_TELECOM of the EMV card.
Reset response (ATR)
The first response from a smart card inserted into a smart card reader is an ATR call. The purpose of ATR is to describe the supported communication parameters. The smart card reader, smart card reader driver, and the operating system will use these parameters to communicate with the card. ATR is described in the ISO7816-3 standard. The first ATR bytes describe the voltage convention (forward or reverse), followed by bytes describing the available communication interfaces and their corresponding parameters. These interface bytes are followed by historical bytes that are not standardized and are useful for transferring service information such as card type, firmware version, or card status. Finally, these history bytes are ultimately followed by a checksum byte.
The smartcard.ATR class is a pyscard utility class that can interpret the ATR content:
Code:
#! /usr/bin/env python
"""
Sample script for the smartcard.ATR utility class.
from __future__ import print_function
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())
This produces the following result:
Code:
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, ATR can be used to locate a specific card, either by trying to match a card with a full ATR, or by matching a card with some data in history bytes. Smartcard-enabled PC applications that detect smartcards based on ATR content take a card-centric approach, regardless of the smartcard reader in which the card is inserted.
Card-centric approach
In the card -centric approach , we create a request for a specific type of card and wait until the card matching the request is inserted. Once the corresponding card is entered, a card connection is created automatically and we can send APDU commands to the card using that connection.
Card request via ATR
The following scripts request a map with a known ATR:
Code:
>>> 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 known ATR, you must first create an ATRCardType object with the desired ATR:
Code:
>>> cardtype = ATRCardType ( toBytes ( "3B 16 94 20 02 01 00 00 0D" ) )
And then create a CardRequest for that card type. In the example, we are requesting a timeout of 1 second.
Code:
>>> cardrequest = CardRequest( timeout=1, cardType=cardtype )
>>> cardservice = cardrequest.waitforcard()
The waitforcard () function will either return with the card service or timed out. The card service connection attribute can then be used to send APDU commands to the card, as in the reader-centric approach.
Code:
>>> cardservice.connection.connect()
>>> print toHexString( cardservice.connection.getATR() )
If necessary, the reader used for the connection can be accessed through the CardConnection object:
Code:
>>> print cardservice.connection.getReader()
SchlumbergerSema Reflex USB v.2 0
ATRCardType also supports mask:
Code:
>>> 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 types of cards are available, and you can create new types of cards as described below.
Request any card
AnyCardType is useful for requesting any card in any reader:
Code:
>>> 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 Card Types
Custom card types can be created, for example a card type that checks ATR and historical card bytes. To create your own CardType, inherit your CardType class from the base CardType class (or any other CardType) and override the match () method. For example, to create a DCCardType that will match cards with a direct convention (the first ATR byte is 0x3b):
Code:
>>> 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 using a map-centric approach eliminate the problems of a reader-centered approach:
- no assumptions about the reader's index or the name of the reader; the desired card will fit in any reader
- the request will be blocked or time out if the desired card type is not inserted, since we are requesting the desired card type, the script does not play on an unknown or incompatible card
- the scenario is limited to a specific type of card; we have to change the script if we want to execute the script for a different type of map. For example, we have to change the ATR of the card if we are using ATRCardType. However, this can be partially resolved by using a custom CardType that matches multiple ATRs.
parameters are most important for negotiating a protocol between the smart card reader and the card. The main smart card protocols are T = 0 and T = 1 for transferring bytes or blocks, respectively. The required protocol can be specified when connecting a card or transferring a card.
By default, the connect () method of the CardConnection object. Will try to connect using the T = 0 or T = 1 protocol. To force the connection protocol, you can pass the required protocol to the connect () method.
Code:
>>> 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 () transfer method:
Code:
>>> from smartcard.CardType import AnyCardType
>>> from smartcard.CardConnection import CardConnection
>>> from smartcard.CardRequest import CardRequest
>>> from smartcard.util import toHexString, toBytes
>>>
>>> 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
>>>
Object-Oriented Approach
In the object-oriented approach, we associate a high-level object with a set of smart cards supported by the object. For example, we associate the javacard loader class with the javacard smartcard suite. We create a request for a specific object and wait until the map supported by the object is inserted. After the map supported by the object is inserted, we perform the required function by calling the methods of the object.
To be written ...
Tracking APDU
Brute Force
An easy way to keep track of APDU commands and responses is to insert print statements around calls to the transfer () method:
Code:
>>> 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 rather difficult to read because there are more trace statements than the actual apdu passes.
A slight improvement in visibility would be replacing print instructions with functions, for example:
Code:
>>> 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 None==response: 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 Map Attachment Watchers to track apdu transmission The
preferred solution is to implement a Map Attachment Observer and register the observer with a Map Attachment . The card connection will then notify the observer when the card connection events occur (e.g. connect, disconnect, apdu command, or apdu response). This is shown in the following script:
Code:
>>> 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 scenario, the ConsoleCardConnectionObserver attaches to the card service connection after the call to watiforcard () returns.
Code:
>>> observer=ConsoleCardConnectionObserver()
>>> cardservice.connection.addObserver( observer )
On card connection events (connect, disconnect, send apdu command, receive apdu response), the card connection notifies its observers using CarConnectionEvent, including the event type and event data. The ConsoleCardConnectionObserver is a simple observer that will print event connection cards to the console. The class definition is as follows:
Code:
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:])
So the console map connection explorer prints the connect, disconnect, command and response apdu events:
Code:
>>> 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
The map connection observer update method is called on a map connection event with a connection parameter and connection event. CardConnectionEvent class definition is as follows:
Code:
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 map connection browser, for example to do fancy output in a wxWindows frame, or interpreted by apdu. The following scripts define a small interpreter apdu SELECT and GET RESPONSE:
Code:
>>> 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
>>>
Complete code example
Code:
#! /usr/bin/env python
"""
Sample script that defines a custom card connection observer.
"""
from __future__ import print_function
from smartcard.CardType import AnyCardType
from smartcard.CardRequest import CardRequest
from smartcard.CardConnectionObserver import CardConnectionObserver
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
After the APDU has been transmitted and processed, the smart card returns a pair of status words, SW1 and SW2, to report various success or error codes after the necessary processing. Some of these success or error codes are standardized, for example, in ISO7816-4, ISO7816-8, or ISO7816-9. Other status word codes are standardized by standards bodies such as Open Platform (eg javacard), 3GPP (eg SIM or USIM cards), or Eurocard-Mastercard-Visa (EMV) (eg bank cards). Finally, any smart card application developer can define their own application-specific codes; for example, the MUSCLE applet defines a set of private codes associated with the functions of the MUSCLE applet.
Some of these status word codes are unique, but others have different meanings depending on the type of card and the supported standards. For example, ISO7816-4 defines error code 0x62 0x82 as "File invalid", whereas in Open Platform 2.1, the same error code is defined as "Card lifecycle - CARD_LOCKED". The resulting list of error codes that can be returned by a smart card and their interpretation depends on the type of card. The following discussion describes possible strategies for checking and reporting smart card status word errors.
Brute force to check for APDU transfer errors
As far as APDU tracing is concerned, an easy way to check for errors in response APDUs during script execution is to insert test statements after the call to the transfer () method:
Code:
>>> 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 messages than the actual apdu reports.
Improving visibility is to wrap the transfer instruction inside the mytransmit function, for example:
Code:
>>> 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 for error checking is to use smarcard.sw.ErrorChecker, as described in the next section.
Checking for APDU Transmission Errors with Error Checking
Status word errors can arise from a variety of sources. The ISO7816-4 standards define status words for sw1 in the range 0x62 to 0x6F and some values for sw2, with the exception of 0x66, which is reserved for security issues. The ISO7816-8 standards define other status words such as sw1 = 0x68 and sw2 = 0x83 or 0x84 for command concatenation errors. Other standards, such as Open Platform, define additional error status words, such as sw1 = 0x94 and sw2 = 0x84.
The preferred strategy for checking status word errors is based on separate error checking tools (smartcard.sw.ErrorChecker) that can be chained together to form the error checking chain (smartcars.sw.ErrorCheckingChain).
Error Checking Tools The Error
Checker is a class derived from ErrorChecker that checks for recognized error conditions sw1, sw2 when invoked, and throws an exception when such a condition is encountered. This is shown in the following example:
Code:
>>> 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 the error checker does not throw an exception because 90 00 does not report errors. However, the second call throws CheckingErrorException.
Checking Chains Error Checking
tools can be combined into an error checking chain. Each checker in the chain is called until an error condition is met, in which case an exception is thrown. This is shown in the following example:
Code:
>>> 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!'
>>>
This example creates an error checking chain that first checks for iso 7816-9 errors, then iso7816-8 errors, and finally iso7816-4 errors.
The first call in the error chain does not throw an exception because 90 00 does not report errors. However, the second call throws a CheckingErrorException thrown by the iso7816-9 error checker.
Filtering Exceptions
You can filter out unwanted exceptions in the chain by adding the filtered exception to the error checking chain:
Code:
>>> 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 in the error chain with sw1 sw2 = 62 00 throws a WarningProcessingException.
Code:
...
>>> 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 throw an exception:
Code:
>>> from smartcard.sw.SWExceptions import WarningProcessingException
>>>
>>> errorchain[0].addFilterException( WarningProcessingException )
>>> errorchain[0]( [], 0x62, 0x00 )
>>>
Complete code example
Code:
#! /usr/bin/env python
"""Sample script for APDU error checking.
"""
from __future__ import print_function
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.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 APDU Response Errors for Card Attachment
To detect APDU response errors during transmission, simply set up an error checking chain for the connection used for transmission:
Code:
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 the SIM will result in an output similar to the following:
Code:
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 will result in:
Code:
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 map connection error checking chain. The map connection will use chaining to check for errors when it receives apdu's response:
Writing a Custom Error Checker
Implementing a custom error checker requires implementing the op21_ErrorChecker subclass and overriding the __call__ method. The following error checker throws a SecurityRelatedException when sw1 = 0x66 and sw2 = 0x00.
Custom checkers can be used alone, as in the following example, or linked to other error checking tools.
Code:
#! /usr/bin/env python
"""Sample script for APDU error checking with a custom error checker.
"""
from __future__ import print_function
from smartcard.CardType import AnyCardType
from smartcard.CardRequest import CardRequest
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.sw.ErrorCheckingChain import ErrorCheckingChain
from smartcard.sw.ErrorChecker import ErrorChecker
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, "%x %x" % (e.sw1, e.sw2))
cardservice.connection.disconnect()
import sys
if 'win32' == sys.platform:
print('press Enter to continue')
sys.stdin.read(1)
Smart card readers
output reading device list of smart cards
The easiest way to get a list of smart card reader devices - a feature smartcard.System.readers ():
Code:
>>> import smartcard.System
>>> print smartcard.System.readers()
['Schlumberger e-gate 0', 'SchlumbergerSema Reflex USB v.2 0', 'Utimaco CardManUSB 0']
>>>
Organizing Smart Card Readers into Reader
Groups Managing Reader Groups is only available on Windows, since PCSC-lite does not currently support Reader Group Management.
Readers can be organized into reading groups. To get groups of smart card readers use readergroups ():
Code:
>>> import smartcard.System
>>> print smartcard.System.readergroups()
['SCard$DefaultReaders']
>>>
The readergroups () object has all the attributes of the list. To add a group of readers, just use the + operator, for example:
Code:
>>> 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 add and insert methods as well as the + operator, for example:
Code:
>>> 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
Code:
>>> 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']
>>>
Smart card reader groups are not saved until the reader is added to the group. To add a reader to a reader group, use addreadertogroups ():
Code:
>>> 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 group of readers, all list operators are available to manipulate groups of readers, including pop () or remove ():
Code:
>>> 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
Code:
>>> 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 track the reader insertion, create a ReaderObserver object that implements the update () method that will be called when the reader / insert is removed. The following code example implements ReaderObserver, which simply prints inserted / removed readers to standard output:
Code:
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 track the installation / removal of a reader, simply add an observer to ReaderMonitor:
Code:
#! /usr/bin/env python
"""
Sample script that monitors smartcard readers.
"""
from __future__ import print_function
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 keep track of card insertion and retrieval, create a CardObserver object that implements the update () method that will be called when a card is inserted / retrieved. The following code example implements CardObserver, which simply prints inserted / removed cards to standard output named printobserver. To track the insertion / removal of the card, simply add the card explorer to CardMonitor:
Code:
#! /usr/bin/env python
"""
Sample script that monitors smartcard insertion/removal.
"""
from __future__ import print_function
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 Smart Card Obtained from Card Monitoring
The CardObserver update method retrieves two lists of Cards objects: recently added cards and recently removed cards. A connection can be created with each Card object from the added card list to send APDUS.
The following code example implements a CardObserver class named selectDFTELECOMObserver that connects to the inserted cards and passes the APDU, in our case SELECT DF_TELECOM.
To monitor card insertion, connect to inserted cards and send APDUs, instantiate selectDFTELECOMObserver and add it to CardMonitor:
Code:
#! /usr/bin/env python
"""
Sample script that monitors smartcard insertion/removal and select DF_TELECOM on inserted cards
"""
from __future__ import print_function
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 an APDU is done through the CardConnection object. CardConnection objects are created using CardRequest or CardMonitoring.
Making a connection from CardRequest
A successful CardRequest returns the CardService corresponding to the requested card service for the card, or PassThruCardService if no specific card service was required:
Code:
>>> 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 the CardConnection for the card.
Creating a Connection from CardMonitoring
The CardObserver update method retrieves 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:
Code:
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)
Complete code example
Code:
#! /usr/bin/env python
"""
Sample script that monitors card insertions, connects to cards and transmit an apdu
"""
from __future__ import print_function
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("%.2x %.2x" % (sw1, sw2))
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)
APDU card connection decorators are passed to the card using the CardConnection object. It is sometimes useful to transparently change the connection behavior of a smart card, for example to automatically establish a secure channel or filter and change on the fly some of the commands or responses from the APDU or ATR of the smart card. pyscard uses the decorator design pattern to dynamically change the connection behavior of a smart card. CardConnectionDecorator changes the behavior of the CardConnection object. For example, the following CardConnectionDecorator overwrites the CardConnection getATR () method:
Code:
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 a decorator, simply create a decorator around the CardConnection instance to wrap it and use the decorator instead of the card connection object:
Code:
# 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 FakeATRConnection in a SecureChannelConnection, use the following construct:
Code:
# 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() )
Complete code example:
Code:
#! /usr/bin/env python
"""
Sample script that illustrates card connection decorators.
"""
from __future__ import print_function
from smartcard.CardType import AnyCardType
from smartcard.CardRequest import CardRequest
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.CardConnectionDecorator import CardConnectionDecorator
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, bytes):
'''Cypher mock-up; you would include the secure channel logics here.'''
print('cyphering', toHexString(bytes))
return bytes
def uncypher(self, data):
'''Uncypher mock-up;
you would include the secure channel logics here.'''
print('uncyphering', toHexString(data))
return data
def transmit(self, bytes, protocol=None):
"""Cypher/uncypher APDUs before transmission"""
cypheredbytes = self.cypher(bytes)
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 map. With PCSC readers, this is done by executing a SCardConnect with the SCARD_SHARE_EXCLUSIVE attribute.
Code:
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() )
The exclusive transfer card connection decorator
ExclusiveTransmitCardConnection performs an exclusive card transaction, that is, a series of transfers that cannot be interrupted by transfers from other streams. To do this, include the desired transfers between calls to the lock () and unlock () methods in the ExclusiveTransmitCardConnection:
Code:
#! /usr/bin/env python
"""
Sample script that illustrates exclusive card connection decorators.
"""
from __future__ import print_function
from smartcard.CardType import AnyCardType
from smartcard.CardRequest import CardRequest
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
from smartcard.util import toHexString
from smartcard.ExclusiveConnectCardConnection import \
ExclusiveConnectCardConnection
from smartcard.ExclusiveTransmitCardConnection import \
ExclusiveTransmitCardConnection
# 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 example of the use of CardConnection decorators is to implement a secure channel. The following example is a templated CardConnection decorator for a secure channel, where each command APDU is encrypted and each response APDU is not encrypted:
Code:
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, bytes ):
'''Cypher mock-up; you would include the secure channel logics here.'''
print 'cyphering', toHexString( bytes )
return bytes
def uncypher( self, data ):
'''Uncypher mock-up; you would include the secure channel logics here.'''
print 'uncyphering', toHexString( data )
return data
def transmit
)
return data, sw1, sw2
A few words about cryptography
Smart cards are security devices. As a result, smart card applications usually require some kind of cryptography, such as establishing a secure channel with a smart card. One of the reference cryptographic modules for Python is pycrypto , a Python cryptographic toolkit. This section briefly shows the basics of pycrypto so you can quickly get started incorporating cryptography into Python smart card applications.
Binary strings and list of bytes
pycrypto handles binary strings, that is, Python strings containing characters such as '01427023', while pyscard treats APDUs as a list of bytes, such as [0x01, 0x42, 0x70, 0x23]. The HexListToBinString and BinStringToHexList utility function (and their versions with the short names hl2bs and bs2hl) provide conversion between the two types.
Code:
from smartcard.util import HexListToBinString, BinStringToHexList
test_data = [ 0x01, 0x42, 0x70, 0x23 ]
binstring = HexListToBinString( test_data )
hexlist = BinStringToHexList( binstring )
print binstring, hexlist
pycrypto supports the following hashing algorithms: SHA-1, MD2, MD4, and MD5. To hash 16 bytes of data using SHA-1:
Code:
from Crypto.Hash import SHA
from smartcard.util import toHexString, PACK
test_data = [ 0x01, 0x42, 0x70, 0x23 ]
binstring = HexListToBinString( test_data )
zhash = SHA.new( binstring )
hash_as_string = zhash.digest()[:16]
hash_as_bytes = BinStringToHexList( hash_as_string )
print hash_as_string, ',', toHexString( hash_as_bytes, PACK )
To perform MD5 hashing, simply replace SHA with MD5 in the previous script. Private
key cryptography
pycrypto supports multiple secret key algorithms such as DES, triple DES, AES, blowfish, or IDEA. To perform triple DES encryption in ECB mode:
Code:
from Crypto.Cipher import DES3
from smartcard.util import toBytes
key = "31323334353637383132333435363738"
key_as_binstring = HexListToBinString( toBytes( key ) )
zdes = DES3.new( key_as_binstring, DES3.MODE_ECB )
message = "71727374757677787172737475767778"
message_as_binstring = HexListToBinString( 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