Как отправлять команды считывателю смарт-карт (а не смарт-карте), пока нет карты?

Введение:

У меня есть устройство для чтения смарт-карт с двумя интерфейсами, которое имеет некоторые расширенные возможности (кроме отправки команд APDU на карту и получения ответов APDU).

Например, в своем документе упоминается, что вы можете получить версию прошивки вашего читателя, используя следующую команду:

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

В своем инструменте есть кнопка для этой функции, и она отлично работает:

введите описание изображения здесь

Я даже понюхал порт USB, чтобы узнать, что именно обменивается в связи между моим ПК и моим читателем для этой функции:

Команда: введите описание изображения здесь

Ответ: введите описание изображения здесь

Проблема:

Я хочу получить версию моего читателя (и, возможно, отправить другие расширенные команды), используя другие инструменты или через код, но я должен вставить карту в устройство чтения карт, чтобы отправлять команды, иначе я получаю исключение No Card Present, тогда как Я не хочу отправлять команды на карту! (Инструмент для чтения успешно отвечает на GET_FIRMWARE_VERSION без какой-либо карты, доступной в слотах для считывателя)

Что я сделал до сих пор:

1. Я пробовал некоторые инструменты, включая OpenSCTool, PyAPDUTool и другой инструмент для чтения.  2.I написал следующий python script для отправки расширенных команд.

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

Вывод:

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

Но обе имеют указанную проблему!

Вопросы:

1 Как отправить расширенные команды читателю, пока нет карты?

2 Почему я не вижу заголовка команды в данных, которые были обнюханы? (Обратите внимание, что поскольку Header является предварительно заданным фиксированным значением для всех расширенных команд, я думаю, что инструмент чтения не отправляет заголовок с помощью команды GET_FIRMWARE_VERSION, и он отправляет только данные!, но как это работает?)


Update:

Используя проб и ошибок, я нашел что-то полезное.

Предположения:

  • Псевдо-APDUs фиксированный заголовок = FF 69 44 42
  • Поле данных псевдо-APDU для GET_READER_FIRMWARE_VERSION = 68 92 00 04 00
  • Поле данных псевдоданных APDU для CHANGE_SAM_SLOT = 68 92 01 00 03 XX 00 00 (У моего читателя два слота SAM, поэтому XX может быть 01 или 02)
  • Команда SELECT APDU = 00 A4 04 00 00

Хорошо, я написал следующую программу Java:

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 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;
    }

}

В приведенной выше программе я могу отправлять команды моему читателю, используя методы connection.transmitControlCommand и cardChannel.transmit(). Дело в том, что все команды, которые отправляются читателю с использованием первого метода, принимаются как команда Pseudo-APDU, и я не должен использовать для них заголовок Psedo-APDU! И все команды, которые отправляют читателю, используя второй метод, считаются регулярными командами APDU, поэтому, если мне нужно отправить команды Pseudo-APDU через второй метод, я должен добавить к нему заголовок Pseudo-APDU.

Посмотрите вывод для бесконтактного считывателя:

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

Есть ли еще проблема?

Да, две проблемы!:

1 - вышеуказанная программа отлично работает только для ее первого запуска. Я имею в виду, что если я перестаю работать и повторю его, второй метод выдает исключение:

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)

Как вы видите выше, я больше не могу использовать второй метод, и мне нужно отключить его и снова включить его, чтобы он снова работал нормально.

2 - Контактный интерфейс (я имею в виду считыватель SAM) всегда исключает предыдущее исключение! Я имею в виду, что второй метод вообще не работает (ни первый, ни второй, ни третий....)

Обратите внимание, что я пробовал разные читатели, кажется, что это не ограничивается только этим читателем. У некоторых читателей ACS также есть аналогичная или точно такая же проблема с повторением

Есть ли у кого-нибудь идеи?

И как побочный вопрос, имеет ли Python какие-либо равные методы для отправки Pseudo-APDU, например Java?

И, наконец, с точки зрения Reader, какая разница между методами connection.transmitControlCommand и cardChannel.transmit()?

Ответы

Ответ 1

Когда вы останавливаете услугу "Смарт-карта", инструмент все еще возвращает версию прошивки? Если да, тогда инструмент может использовать команды raw IOCTL (DeviceIoControl) для связи с драйвером.


Также рассмотрите этот вопрос. Автор говорит, что вы должны установить SCARD_PROTOCOL_UNDEFINED в качестве параметра протокола:

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

Я просто попробовал, и, по крайней мере, он работает на Windows 10. Связь возможна без вставленной карты. Однако я не тестировал другие версии Windows.