javax.smartcardio: как отправить собственные команды на карту Desfire?
Я создаю Java-приложение, связывающееся с картой Mifare DESFire через бесконтактный считыватель PC/SC и API javax.smartcardio. Мне удается отправлять обычные APDU ISO 7816 (CLA, INS, P1-P2, Lc, данные команд, Le).
Я читал в блоге Ridrix, что карты DESFire (по крайней мере, версия EV1, которую я использую) поддерживают как APDU, так и собственные команды, где большинство команд имеют длину только 1 байт.
Например, команда " Получить версию ":
Command: 60
Response: af 04 01 01 00 02 18 05
Я протестировал эту команду с помощью программы PC/SC Diag от SpringCard ( доступна здесь) и получил правильный ответ.
Но я не могу отправить эту команду с помощью javax.smartcardio: этот API, похоже, был создан для реальных APDU и поэтому не допускает команд длиной 1 байт.
Вот что я сделал:
public static void main(String[] args){
TerminalFactory factory = TerminalFactory.getDefault();
CardTerminals terminalList = factory.terminals();
try {
CardTerminal ct = terminalList.list().get(0);
ct.waitForCardPresent(0);
Card card = ct.connect("*");
CardChannel channel = card.getBasicChannel();
byte[] command = { 0x60 };
channel.transmit(new CommandAPDU(command));
} catch (CardException e) {
e.printStackTrace();
}
}
Это дает мне следующую ошибку:
Exception in thread "main" java.lang.IllegalArgumentException: apdu must be at least 4 bytes long
at javax.smartcardio.CommandAPDU.parse(Unknown Source)
at javax.smartcardio.CommandAPDU.<init>(Unknown Source)
Я попробовал единственный (AFAIK) другой способ отправить команду:
ByteBuffer command = ByteBuffer.allocate(1);
command.put((byte) 0x60);
ByteBuffer response = ByteBuffer.allocate(512);
channel.transmit(command, response);
и получите похожую ошибку:
Exception in thread "main" java.lang.IllegalArgumentException: Command APDU must be at least 4 bytes long
at sun.security.smartcardio.ChannelImpl.checkManageChannel(Unknown Source)
at sun.security.smartcardio.ChannelImpl.doTransmit(Unknown Source)
at sun.security.smartcardio.ChannelImpl.transmit(Unknown Source)
Знаете ли вы какой-либо способ отправки такого рода команды с помощью javax.smartcardio или что-то еще?
Я знаю, что можно обернуть эти команды, но я бы предпочел использовать (более простые) собственные команды.
Благодарю.
3 ответа
Спустя почти 4 года, но на тот случай, если кто-то натолкнется на этот вопрос, я нашел ответ на этот вопрос. Сегодня многие читатели поддерживают перенос кадров Desfire APDU в команду ISO 7816-4. Я обнаружил ограничение, согласно которому данные не могут превышать 55 байт.
Проверьте страницу 23 в этом документе для полной информации: http://neteril.org/files/M075031_desfire.pdf
Это означает, что вы можете указать следующее, чтобы обернуть кадр APDU
CLA = 0x90
INC = {Your Desfire Command e.g. 0x60 - Get Version}
P1 = 0
P2 = 0
Data = 1st byte = length of data followed by byte data. Terminate data with a 0x00 byte
Ответ также упакован следующим образом:
SW1 = 0x91
SW2 = Result Status
Data = Response Data
Таким образом, следующий код может быть использован
public static byte CMD_WRAP_START = (byte)0x90;
public static byte CMD_WRAP_END = (byte)0x00;
private CommandAPDU wrapAPDUFrameUsingISO7816_4(byte[] apdu) throws CardException {
if (apdu.length > 55){
throw new CardException("The length of the wrapped DESFire command must not be longer than 55 bytes, checksum included.");
}
boolean hasData = apdu.length > 1;
byte[] result;
if (hasData) {
result = new byte[apdu.length + 5];
} else {
result = new byte[apdu.length + 4];
}
result[0] = CMD_WRAP_START; // CLA
result[1] = apdu[0]; // DESFIRE CMD CODE
result[2] = 0; // P1
result[3] = 0; // P2
if (hasData) {
result[4] = (byte) (apdu.length - 1); // Length of wrapped data, ONLY IF DATA EXISTS
System.arraycopy(apdu,1,result,5,apdu.length-1); // DESFIRE Command data
}
result[result.length-1] = CMD_WRAP_END;
return new CommandAPDU(result);
}
private static byte [] unwrapFromISO7816_4(byte[] wrapped) throws CardException {
if (wrapped.length<2){
throw new CardException("Expected at least 2 bytes for ISO 7816-4 wrapped response: " + String.valueOf(Hex.encodeHex(wrapped, false)));
}
if (wrapped[wrapped.length-2]!=(byte)0x91){
throw new CardException("Expected 0x91 in SW1 for ISO 7816-4 wrapped response: " + String.valueOf(Hex.encodeHex(wrapped, false)));
}
byte[] result = new byte[wrapped.length-1];
System.arraycopy(wrapped,0,result,1,wrapped.length-2); // The DESFIRE response
result[0] = wrapped[wrapped.length-1]; // The DESFIRE Status
return result;
}
javax.smartcardio
API, написанный для использования команд ISO 7816-4 Поэтому невозможно отправлять "родные" команды. По сути, нативные команды могут быть любыми, поэтому их будет сложно поддерживать.
Либо вы вернетесь к JNI, либо попытаетесь найти что-то, что использует transmitControlCommand
, Но я боюсь, что нет реального способа использования DESFire без дополнительной библиотеки.
Лично я думаю, что гораздо проще использовать слой-обертку.
Здесь у вас есть ответ: Команда APDU должна быть не менее 4 байтов.
* case 1 : |CLA|INS|P1 |P2 | len = 4
* case 2s: |CLA|INS|P1 |P2 |LE | len = 5
* case 3s: |CLA|INS|P1 |P2 |LC |...BODY...| len = 6..260
* case 4s: |CLA|INS|P1 |P2 |LC |...BODY...|LE | len = 7..261
*
* (Extended length is not currently supported)
* case 2e: |CLA|INS|P1 |P2|00 |LE1|LE2| len = 7
* case 3e: |CLA|INS|P1 |P2 |00|LC1|LC2|...BODY...| len = 8..65542
* case 4e: |CLA|INS|P1 |P2 |00|LC1|LC2|...BODY...|LE1|LE2| len =10..65544
*
* EMV