How to send commands to smart card reader (and not to the smart card) while no card present?

16.2k views Asked by At

Preface:

I have a dual interface smart card reader that has some extended capabilities (other than sending APDU commands to card and receiving APDU responses).

For example in its document it is mentioned that you can get firmware version of your reader using following command:

GET_FIRMWARE_VERSION: FF 69 44 42 05 68 92 00 05 00

In its tool, there is a button for this function and it works fine:

enter image description here

I even sniffed USB port to see what exactly exchanged in the connection between my PC and my reader for this function:

Command: enter image description here

Response: enter image description here

Problem:

I want to get my reader version (and maybe send other extended commands) using other tools or via code, but I must insert a card in the card reader to be able sending commands, otherwise I receive No Card Present exception, while I don't want to send commands to card! (The reader tool answers successfully to GET_FIRMWARE_VERSION without any card available in the reader's slots)

What I did so far:

1.I tried some tools, including OpenSCTool , PyAPDUTool, and another reader's tool. 2.I wrote following python script to send extended commands.

#--- Importing required modules.
import sys
import time
sys.path.append("D:\\PythonX\\Lib\\site-packages")
from smartcard.scard import *
import smartcard.util
from smartcard.System import readers


#---This is the list of commands that we want to send device
cmds =[[,0xFF,0x69,0x44,0x42,0x05,0x68,0x92,0x00,0x04,0x00],]


#--- Let's to make a connection to the card reader
r=readers()
print "Available Readers :",r
print
target_reader = input("--- Select Reader (0, 1 , ...): ")
print

while(True):
    try:
        print "Using :",r[target_reader]
        reader = r[target_reader]
        connection=reader.createConnection()
        connection.connect()
        break
    except:
        print "--- Exception occured! (Wrong reader or No card present)"
        ans = raw_input("--- Try again? (0:Exit/1:Again/2:Change Reader)")
        if int(ans)==0:
            exit()
        elif int(ans)==2:
            target_reader = input("Select Reader (0, 1 , ...): ")

#--- An struct for APDU responses consist of Data, SW1 and SW2
class stru:
    def __init__(self):
        self.data = list()
        self.sw1 = 0
        self.sw2 = 0

resp = stru()

def send(cmds):
    for cmd in cmds:

        #--- Following 5 line added to have a good format of command in the output.
        temp = stru() ;
        temp.data[:]=cmd[:]
        temp.sw1=12
        temp.sw2=32
        modifyFormat(temp)
        print "req: ", temp.data

        resp.data,resp.sw1,resp.sw2 = connection.transmit(cmd)
        modifyFormat(resp)
        printResponse(resp)

def modifyFormat(resp):
    resp.sw1=hex(resp.sw1)
    resp.sw2=hex(resp.sw2)   
    if (len(resp.sw2)<4):
        resp.sw2=resp.sw2[0:2]+'0'+resp.sw2[2]
    for i in range(0,len(resp.data)):
        resp.data[i]=hex(resp.data[i])
        if (len(resp.data[i])<4):
            resp.data[i]=resp.data[i][0:2]+'0'+resp.data[i][2]

def printResponse(resp):
    print "res: ", resp.data,resp.sw1,resp.sw2


send(cmds)
connection.disconnect()

Output:

>>> ================================ RESTART ================================
Available Readers : ['CREATOR CRT-603 (CZ1) CCR RF 0', 'CREATOR CRT-603 (CZ1) CCR SAM 0']

--- Select Reader (0, 1 , ...): 0

Using : CREATOR CRT-603 (CZ1) CCR RF 0
--- Exception occured! (Wrong reader or No card present)
--- Try again? (0:Exit/1:Again/2:Change Reader)

>>> ================================ RESTART ================================
Available Readers : ['CREATOR CRT-603 (CZ1) CCR RF 0', 'CREATOR CRT-603 (CZ1) CCR SAM 0']

--- Select Reader (0, 1 , ...): 1

Using : CREATOR CRT-603 (CZ1) CCR SAM 0
--- Exception occured! (Wrong reader or No card present)
--- Try again? (0:Exit/1:Again/2:Change Reader)

But both have the mentioned problem!

Questions:

1- How to send extended commands to reader while there is no card available?

2- Why I can't see command header in the sniffed data? (Note that, as Header is a pre-designated fixed value for all extended commands, I think the reader tool doesn't send the header with GET_FIRMWARE_VERSION command and it send only the data! but how does it works?)


Update:

Using trial and error I found something useful.

Assumptions:

  • Pseudo-APDUs fixed header = FF 69 44 42
  • Pseudo-APDU data field for GET_READER_FIRMWARE_VERSION = 68 92 00 04 00
  • Pseudo-APDU data field for CHANGE_SAM_SLOT = 68 92 01 00 03 XX 00 00 (My reader has two SAM slots, so XX can be 01 or 02)
  • SELECT APDU command = 00 A4 04 00 00

Ok, I wrote the following Java Program:

import java.util.List;
import java.util.Scanner;
import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;
import javax.xml.bind.DatatypeConverter;

public class TestPCSC {

    public static void main(String[] args) throws CardException {

        TerminalFactory tf = TerminalFactory.getDefault();
        List< CardTerminal> terminals = tf.terminals().list();
        System.out.println("Available Readers:");
        System.out.println(terminals + "\n");

        Scanner scanner = new Scanner(System.in);
        System.out.print("Which reader do you want to send your commands to? (0 or 1 or ...): ");
        String input = scanner.nextLine();
        int readerNum = Integer.parseInt(input);
        CardTerminal cardTerminal = (CardTerminal) terminals.get(readerNum);
        Card connection = cardTerminal.connect("DIRECT");
        CardChannel cardChannel = connection.getBasicChannel();

        System.out.println("Write your commands in Hex form, without '0x' or Space charaters.");
        System.out.println("\n---------------------------------------------------");
        System.out.println("Pseudo-APDU Mode:");
        System.out.println("---------------------------------------------------");
        while (true) {
            System.out.println("Pseudo-APDU command: (Enter 0 to send APDU command)");
            String cmd = scanner.nextLine();
            if (cmd.equals("0")) {
                break;
            }
            System.out.println("Command  : " + cmd);
            byte[] cmdArray = hexStringToByteArray(cmd);
            byte[] resp = connection.transmitControlCommand(CONTROL_CODE(), cmdArray);
            String hex = DatatypeConverter.printHexBinary(resp);
            System.out.println("Response : " + hex + "\n");
        }

        System.out.println("\n---------------------------------------------------");
        System.out.println("APDU Mode:");
        System.out.println("---------------------------------------------------");

        while (true) {
            System.out.println("APDU command: (Enter 0 to exit)");
            String cmd = scanner.nextLine();
            if (cmd.equals("0")) {
                break;
            }
            System.out.println("Command  : " + cmd);
            byte[] cmdArray = hexStringToByteArray(cmd);
            ResponseAPDU resp = cardChannel.transmit(new CommandAPDU(cmdArray));
            byte[] respB = resp.getBytes();
            String hex = DatatypeConverter.printHexBinary(respB);
            System.out.println("Response : " + hex + "\n");
        }

        connection.disconnect(true);

    }

    public static int CONTROL_CODE() {
        String osName = System.getProperty("os.name").toLowerCase();
        if (osName.indexOf("windows") > -1) {
            /* Value used by both MS' CCID driver and SpringCard's CCID driver */
            return (0x31 << 16 | 3500 << 2);
        } else {
            /* Value used by PCSC-Lite */
            return 0x42000000 + 1;
        }

    }

    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

}

In the above program, I can send commands to my reader using both connection.transmitControlCommand and cardChannel.transmit() methods. The point is that, all the commands that send to the reader using first method, are assumed as Pseudo-APDU command and I should not use the Psedo-APDU header for them! And all the commands that send to the reader using second method, are assumed as regular APDU commands, so if I need to send Pseudo-APDU commands via second method, I must add the Pseudo-APDU header to it.

Let see output for the contact-less reader:

run:
Available Readers:
[PC/SC terminal ACS ACR122 0, 
PC/SC terminal CREATOR CRT-603 (CZ1) CCR RF 0,
PC/SC terminal CREATOR CRT-603 (CZ1) CCR SAM 0]

Which reader do you want to send your commands to? (0 or 1 or ...): 1
Write your commands in Hex form, without '0x' or Space charaters.

---------------------------------------------------
Pseudo-APDU Mode:
---------------------------------------------------
Pseudo-APDU command: (Enter 0 to send APDU command)
00A4040000
Command  : 00A4040000
Response : 6800
//Based on reader's documents, 0x6800 means "Class byte is not correct"
//As I have a regular java card in the RF field of my  reader, I conclude that 
//this response is Reader's response (and not card response)

Pseudo-APDU command: (Enter 0 to send APDU command)
6892000400
Command  : 6892000400
Response : 433630335F435A375F425F31353038323100039000

Pseudo-APDU command: (Enter 0 to send APDU command)
FF694442056892000400
Command  : FF694442056892000400
Response : 6800
//Pseudo-APDU commands doesn't work in Pseudo-APDU mode if I add the Pseudo-APDU header to them. 

Pseudo-APDU command: (Enter 0 to send APDU command)
00A4040000
Command  : 00A4040000
Response : 6800

Pseudo-APDU command: (Enter 0 to send APDU command)
0

---------------------------------------------------
APDU Mode:
---------------------------------------------------
APDU command: (Enter 0 to exit)
00A4040000
Command  : 00A4040000
Response : 6F198408A000000018434D00A50D9F6E061291921101009F6501FF9000

APDU command: (Enter 0 to exit)
6892000400
Command  : 6892000400
Response : 6E00
//This is the response of my card. I can't receive Firmware version in APDU mode using this command without Pseudo-APDU header. 

APDU command: (Enter 0 to exit)
FF694442056892000400
Command  : FF694442056892000400
Response : 433630335F435A375F425F31353038323100099000
//I successfully received Firmware version in APDU mode using the fixed Pseudo-APDU header.

APDU command: (Enter 0 to exit)
00A4040000
Command  : 00A4040000
Response : 6F198408A000000018434D00A50D9F6E061291921101009F6501FF9000

APDU command: (Enter 0 to exit)
0
BUILD SUCCESSFUL (total time: 1 minute 36 seconds)

Is there any problem still?

Yes, two problems!:

1-the above program works fine only for its first run. I mean, if I stop running and rerun it, the second method throw an exception:

run:
Available Readers:
[PC/SC terminal ACS ACR122 0, PC/SC terminal CREATOR CRT-603 (CZ1) CCR RF 0, PC/SC terminal CREATOR CRT-603 (CZ1) CCR SAM 0]

Which reader do you want to send your commands to? (0 or 1 or ...): 1
Write your commands in Hex form, without '0x' or Space charaters.

---------------------------------------------------
Pseudo-APDU Mode:
---------------------------------------------------

Pseudo-APDU command: (Enter 0 to send APDU command)
00A4040000
Command  : 00A4040000
Response : 6800

Pseudo-APDU command: (Enter 0 to send APDU command)
FF694442056892000400
Command  : FF694442056892000400
Response : 6800

Pseudo-APDU command: (Enter 0 to send APDU command)
6892000400
Command  : 6892000400
Response : 433630335F435A375F425F31353038323100049000

Pseudo-APDU command: (Enter 0 to send APDU command)
00A4040000
Command  : 00A4040000
Response : 6800

Pseudo-APDU command: (Enter 0 to send APDU command)
0

---------------------------------------------------
APDU Mode:
---------------------------------------------------
APDU command: (Enter 0 to exit)
00A4040000
Command  : 00A4040000
Exception in thread "main" javax.smartcardio.CardException: sun.security.smartcardio.PCSCException: Unknown error 0x16
    at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:219)
    at sun.security.smartcardio.ChannelImpl.transmit(ChannelImpl.java:90)
    at TestPCSC.main(TestPCSC.java:58)
Caused by: sun.security.smartcardio.PCSCException: Unknown error 0x16
    at sun.security.smartcardio.PCSC.SCardTransmit(Native Method)
    at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:188)
    ... 2 more
Java Result: 1
BUILD SUCCESSFUL (total time: 39 seconds)

As you see above, I can't use second method anymore and I need to power-off the reader and power on it again to make it work fine again.

2-The contact interface (I mean the SAM reader) throw previous exception always! I mean the second method doesn`t work at all (neither first run, nor second and third .... )

Note that I tried different readers, it seems that this is not limited only ti this reader. Some ACS readers also have a similar or exactly the same problem with rerun

Does anyone have any idea?

And as a side question, does Python have any equal methods for sending Pseudo-APDU like Java?

And finally, from the view of Reader, what's the difference between connection.transmitControlCommand and cardChannel.transmit() methods?

2

There are 2 answers

4
arminb On BEST ANSWER

When you stop "Smart Card" service, does the tool still return a firmware version? If yes, then the tool might use raw IOCTL commands (DeviceIoControl) to communicate with the driver.


Also take a look at this question. The author says you have to set SCARD_PROTOCOL_UNDEFINED as protocol parameter:

SCardConnect(hSC,
             readerState.szReader,
             SCARD_SHARE_DIRECT,
             SCARD_PROTOCOL_UNDEFINED,
             &hCH,
             &dwAP
            );

I just tried it and it seems to work for Windows 10 at least. Communication was possible with no card inserted. I did not test for other Windows versions though.

0
EngBashir On

readers() is array index of available readers

reader = r[target_reader]

convert

reader = r[int(target_reader)]

output

Available Readers : ['JAVACOS Virtual Contact Reader 0', 'JAVACOS Virtual Contactless Reader 1', 'OMNIKEY CardMan 3x21 0']
--- Select Reader (0, 1 , ...): 2
Using : OMNIKEY CardMan 3x21 0
ATR :  3B 9E 94 80 1F 47 80 31 A0 73 BE 21 13 66 86 88 02 14 4B 10 19