Search code examples
javasdkmifaredesfiretaplinx

Using TapLinx SDK for Java, NOT Android, for reading MIFARE DESFire contactless smart cards


I have developed successful solutions for reading DESFire smart cards used in Croatian public services, both for:

  1. Dot Net. Multi platform, using pcsc-sharp and "raw" APDU approach.
  2. Android. Using TapLinx SDK for Android and high level-approach. For example: myDesFire.getUID() instead of raw APDU commands.

Right now, I'm trying to develop multi platform solution in Java, using TapLinx SDK for Java and high-level approach.

I rely on javax.smartcardio for polling connected card terminal to see when smart card is inserted or removed, and it works perfectly. In my testing app (JavaFX if that matters), in onCardInserted event, I'm calling my own cardLogic method. That method tries to connect to smart card.

In Android, it is very simple:

public static Card cardLogic(final Intent intent, NxpNfcLib nxpNfcLib, MyCardKeys keys) {

//... some code ommited for brevity

desFireEV = DESFireFactory.getInstance().getDESFireEV2(nxpNfcLib.getCustomModules());

desFireEV.getReader().connect();

byte[] uid = desFireEV.getUID();

// ...and so on.

In Java, similar method of mine is:

public static Card cardLogic(CardTerminal terminal, MyCardKeys keys) {

//... some code ommited for brevity

javax.smartcardio.Card javaCard = terminal.connect("*");

desFire = DESFireFactory.getInstance().getDESFireEV2(TapLinx.getCustomModules());

// **NOW WHAT!???**

desFire.getReader().connect(); // **This does not work, of course. HOW TO connect context of javaCard object with getting a concrete reader, so that...**

byte[] uid = desFire.getUID(); // **... I can do this without raising an exception?**

So, questions are those in comments of the above code snippets. I'm totally stucked. Probably missing something obvious. but I can not figure out the solution, not even when reading code of both sample desktop apps.

Please help.

I also tried raw APDU approach without TapLinx SDK, using just javax.smartcardio, but since smartcardio was actually developed to support contact cards, one can not make more complex operations targeting contactless cards. So, I can read DESFire's UID, select identity app, read app IDs, but I can not read BER TLV files, for example, even if not encrypted.


Solution

  • I've found the solution. So, in case someone encounters similar problem code goes like this. BTW, the key is in the setTransceive method!?!?!!! And, it seems that I initially put this answer to the similar thread by mistake.

    MyApp.TapLinx.getCustomModules().setTransceive(new MyCardApduHandler(new MyCardReader(terminal)));
    desFire = DESFireFactory.getInstance().getDESFireEV2(MyApp.TapLinx.getCustomModules());
    desFire.getReader().connect();
    
    // Read UID.
    byte[] uid = desFire.getUID();
    // To do anything further, and unlike Android , you have to set Command Set to ISO.
    desFire.setCommandSet(IDESFireEV1.CommandSet.ISO);
    // Select ID app...
    desFire.selectApplication(0);
    // ...and so on
    

    MyCardApduHandler is barebone:

    public class MyCardApduHandler implements IApduHandler {
        IReader reader;
    
        public SCardApduHandler(IReader reader) {
            this.reader = reader;
        }
    
        @Override
        public byte[] apduExchange(byte[] bytes) {
            return reader.transceive(bytes);
        }
    
        @Override
        public IReader getReader() {
            return reader;
        }
    }
    

    MyCardReader is as follows:

    public class MyCardReader implements IReader {
        CardTerminal mTerminal;
        CardChannel mKanal;
        Card mJavaCard;
        ProtocolDetails mProtokol;
        boolean isConnected = false;
    
        public SCardReader(CardTerminal terminal) {
            mTerminal = terminal;
        }
    
        @Override
        public byte[] transceive(byte[] bytes) {
            ResponseAPDU res;
            try {
                res = mKanal.transmit(new CommandAPDU(bytes));
            } catch (CardException e) {
                throw new NxpNfcLibException(e, e.getMessage());
            }
            return res.getBytes();
        }
    
        @Override
        public void connect() {
            if (!isConnected) {
                try {
                    mTerminal.waitForCardPresent(0);
                    mJavaCard = mTerminal.connect("*");
                    mKanal = mJavaCard.getBasicChannel();
                    mProtokol = new ProtocolDetails();
                    mProtokol.uid = Commands.uid(mKanal);
                    // TODO: Other components of the protocol.
                    isConnected = true;
                } catch (CardException e) {
                    throw new NxpNfcLibException(e, e.getMessage());
                }
            }
        }
    
        @Override
        public void close() {
            if (isConnected) {
                try {
                    if (mKanal.getChannelNumber() != 0) mKanal.close();
                    mJavaCard.disconnect(false);
                    isConnected = false;
                } catch (Exception e) {
                    throw new NxpNfcLibException(e, e.getMessage());
                }
            }
        }
    
        @Override
        public boolean isConnected() {
            return isConnected;
        }
    
        @Override
        public void setTimeout(long l) {
            throw new NotSupportedException("SCardReader: metoda setTimeout nije podržana. ");
        }
    
        @Override
        public long getTimeout() {
            throw new NotSupportedException("SCardReader: metoda getTimeout nije podržana. ");
        }
    
        @Override
        public ProtocolDetails getProtocolDetails() {
            return mProtokol;
        }
    }
    

    And, Commands.uid(mKanal) is achieved with raw APDU:

    public static byte[] uid(CardChannel kanal) throws CardException, RuntimeException {
        CommandAPDU cmd = new CommandAPDU(new byte[] { (byte) 0xFF, (byte) 0xCA, (byte) 0x00, (byte) 0x00, (byte) 0x00 });
        ResponseAPDU res = kanal.transmit(cmd);
        if (res.getSW1() != 0x90 && res.getSW2() != 0x00) throw new RuntimeException(String.format("uid: greška SW1 SW2 = %02X %02X", res.getSW1(), res.getSW2()));
        return res.getData();
    }