Reputation: 143
I am creating a java application communicating with a Mifare DESFire card through a PC/SC contactless reader and the javax.smartcardio API. I manage to send regular ISO 7816 APDUs (CLA, INS, P1-P2, Lc, Command data, Le).
I have read on Ridrix's Blog that DESFire cards (at least the EV1 version that I am using) support both APDUs and Native commands where most of the commands are only 1 byte long.
For example, the "Get Version" command:
Command: 60
Response: af 04 01 01 00 02 18 05
I tested that command with the PC/SC Diag program from SpringCard (available here) and I get a correct response.
But I cannot send this command with javax.smartcardio: this API seems to have been created for real APDUs and therefore does not allow 1 byte long commands.
Here is what I did:
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();
}
}
It gives me the following error:
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)
I tried the only (AFAIK) other way to send a command:
ByteBuffer command = ByteBuffer.allocate(1);
command.put((byte) 0x60);
ByteBuffer response = ByteBuffer.allocate(512);
channel.transmit(command, response);
and get a similar error:
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)
Do you know of any way to send this kind of command using javax.smartcardio or something else?
I know it is possible to wrap these commands but I would prefer to use the (simpler) native commands.
Thanks.
Upvotes: 2
Views: 8299
Reputation: 119
Nearly 4 years later but just in case someone stubbles across this question, I did find an answer to this. Many readers today support wrapping Desfire APDU frames in a ISO 7816-4 command. I did discover a limitation whereby the data cannot exceed 55 bytes.
Checkout page 23 in this doc for full info: http://neteril.org/files/M075031_desfire.pdf
This means that you can specify the following to wrap the APDU frame
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
The response is also wrapped as follows:
SW1 = 0x91
SW2 = Result Status
Data = Response Data
So the following code can be used
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;
}
Upvotes: 3
Reputation: 21
Here you have the answer: Command APDU must be at least 4 bytes.
* 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
Upvotes: 1
Reputation: 93948
javax.smartcardio
is an API written to use ISO 7816-4 commands. Therefore it is not possible to send "native" commands. Basically, native commands can be anything, so it would be hard to support those.
Either you revert to JNI or you might try and find something that uses transmitControlCommand
. But I'm afraid there is no real way of using DESFire without an additional library.
Personally I think it is much easier to use the wrapping layer.
Upvotes: 3