GChabot
GChabot

Reputation: 143

javax.smartcardio: how to send native commands to Desfire card?

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

Answers (3)

bludginozzie
bludginozzie

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

spg0x01
spg0x01

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

Maarten Bodewes
Maarten Bodewes

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

Related Questions