Introduction and Tutorial

A SCardFace primer

here is a short example, highlighting some of the features of SCardFace:

from scardface import SCard, readers, C_APDU, R_APDU

print("My readers are {0}".format(readers()))

print("Insert a smart card in any reader")

h = SCard( )                        # wait forever until a card
                                    # is inserted in any reader
                                    # or Ctrl-C hit

print(h.atr)                        # print card ATR (bytes)

select_rootdir = C_APDU( ins=0x00, cla=0xa4, data=b"\x3f\x00" )

response = h.send( select_rootdir )

if not response.is_successful():
    print("The card returned this error code: {0}"format(response.ashex()))

data,sw12 = response.data, response.sw12

print("Please remove card")
h.wait()                            # wait for removal

The first function call, readers(), is used to fetch the list of available readers on the system. it returns a tuple of unicode strings containing reader names.:

print("My readers are {0}".format(readers()))

readers() optionally supports a group name as argument, see scardface — Library reference for more details. Also, There is groups() function, that can be to retrieve groups of readers.

Obtaining a smartcard interface

Then, the SCard constructor is called, with no parameters.:

h = SCard( )                        # wait forever until a card
                                    # is inserted in any reader
                                    # or Ctrl-C hit

By default, the initializer will wait until a card insertion is detected on any available reader of the system, and then attempt to establish an exclusive connection with that card. Since, in this example, no timeout is given, it waits forever, unless CTRL-C is hit, SCard.cancel() or cancel_all_requests() being called from another thread.

SCard instances have several properties and method, which are listed inside scardface — Library reference. In this example, the Answer To Reset (ATR) bytes are available from the atr property:

print(h.atr)                        # print card ATR (bytes)

Once a valid connection is established with a card, one can send commands and receive responses to/from the card. The library attempts to hide the gory details of T=0 and T=1 protocols, and is proposing a consistent API and object collection to talk with the cards, regardless of the underlying protocol.

Building a command APDU

One of the easiest way to build a request is through the class method C_APDU.fromhex(), which accepts a hex string of a valid APDU command.

select_rootdir = C_APDU.fromhex("00 A4 02 00 02 3F 00")

The C_APDU.fromhex() class method call will attempt to build a C_APDU instance from the hex string it is given as an argument. It can deal with ISO long and short cases. Please note however that you sometimes need to explicitely add the le byte if the command is Case 3 or 4 (in other word, when an answer is expected). That knowledge varies from command to command and even sometimes from implementation to implementation.

The regular way to build a C_APDU object is by invoking its initializer:

my_command = C_APDU( 0x00, 0xA4, data=b'\x3F\x00')

The two first arguments are mandatory. They represent the class and intruction of the command. If a data field is given, the command see its lc parameter adjusted to reflect the lenght of the data. By default, no data is expected, i.e. the ISO case is either 1 or 3. To force a length on the return, specify the expected length through le:

my_command = C_APDU( 0x00, 0xA4, data=b'\x3F\x00', le=8) # we expect 8 bytes

Alternatively, the expected length can be specified afterwards:

my_command = C_APDU( 0x00, 0xA4, data=b'\x3F\x00')
my_command.le = 8            # expecting exactly 8 bytes

If the expected length is unknown, the value of le shall be set to -1:

my_command = C_APDU( 0x00, 0xA4, data=b'\x3F\x00')
my_command.le = -1           # expecting data, let SCard() object deal
                             # with actual length received

In such case, the SCard.send() method is taking care of determining how much data will be returned. see REF for more details.

sending command and dealing with the response

Once created, the C_APDU instance can be tweaked through its properties, see REF. Then, the instance can be directly used as an argument to the send method of the SCard instance:

response = h.send( select_rootdir )
print("Response back is {0}"format(response.ashex()))

data,sw12 = response.data, response.sw12

This command will deal with TPDU and specific ISO commands like GET RESPONSE and GET ENVELOPPE when necessary, it is therefore no more needed to manage the TPDU layer.

This call will return an R_APDU instance, whose properties can be accessed for further processing.

R_APDU instances have several methods and properties, and amongst them the is_successful() returns True if the R-APDU response code equals b"\x90\x00" (in hexadecimal).

Finally, the card removal can be watched through calling wait() method. This method accepts optionally one parameter, which is a timeout after which it returns a SCardError() timeout event, see REF for more details.

advanced use of SCard

Often, smart card commands are chained, i.e. several commands must be sent to implement a functionality. The way to do this is

h = SCard()

r = h.send(command1)
if not r.is_successful():
   raise RuntimeError("command 1 did not succeed")

r = h.send(command2)
if not r.is_successful():
   raise RuntimeError("command 2 did not succeed")

r = h.send(command2)
if not r.is_successful():
   raise RuntimeError("command 3 did not succeed")

There is a quicker way to formulate this: the send method can receive an list of commands to send, instead of just one

h = SCard()

r = send( command1, command2, command3)

By default, commands are sent until one of the R-APDU is not successful, or until the list is exhausted. As a drawback, this way does not return the offending command.

Hopefully, it is possible to provide send with a callback.

def my_callback(cmd, resp):
   """
   callback function for chained sending.
   """
   if not resp.is_successful():
      raise RuntimeError("offendig command:{0}\nresponse is {1}".format(cmd.ashex(), resp.ashex()))

   return True

h = SCard()

r = send( command1, command2, command3, callback=my_callback)
The callback function, if provided, receives two arguments
  • the first argument is the C_APDU instance being sent to the card
  • the second argument is the R_APDU instance returned by the card

This callback is called after each call to SCard.send(). If it returns True, the call proceeds to the next command in the chain.