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

91 views Asked by At

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.

2

There are 2 answers

1
Slavek On BEST ANSWER

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();
}
1
Michael Fehr On

This is not an answer to the original question but an explanation on the comment.

When the NFC technology started to get popular (around 2010, see https://www.paragon-rfid.com/en/the-history-of-nfc/ and https://en.wikipedia.org/wiki/Near-field_communication for more details) nearly nobody was concerned abot "Privacy". But nowadays with connected systems and large data centers a lot of people rethink their opinion.

Think of a country where the NFC tag readers are connected to a data center that observes each NFC tag that is tapped to a reader ("Smart City" projects e.g. are combining access control, public transport, vendor machines with one card). What is the consequence of a static and unique NFC card UID ? Yes, you are getting trackable by the system as each tag is assigned to one person (the card holder).

For that reason the "newer" NFC tags provide an option to show a random UID (the UID may start with 08h) on tapping. You asked about this: it is part of the anti-collision workflow between the NFC reader and NFC device. This protocol usually ends with a "beep" of the reader and in this protocol the UID is exchanged with the reader.

For the same reason any Android device using "Host Based Card Emulation" ("HCE") is not giving a static but always a random UID.

If I (as a card issuer) e.g. config my Mifare DESFire EVx tag to "reveal" a random UID you will observe the same behaviour - each time you tap the tag to the reader the reader will detect a random number that makes it useless for tracking purposes.

If e.g. an access control system relies only the UID transmitted by protocol this system gets useless as well, as there is no "whitelist" to check the (random) UID against).

How to retrieve the real card's UID: A Mifare DESFire will give it's ("real" and unique) card UID after a the command (51h) and after an Authentication Session has run. During this Authentication a "sessionkey" is defined between both partners (reader and tag) and the tag will send the card UID in an encrypted form. Any NFC card reader observer will only notice a byte sequence that is random and not trackable. Only the system behind the reader who has the knowledge about the authentication key is been able to decrypt the data and can match the card UID against a whitelist or is doing something else).

To anser your last question "So why are there two UIDs at all?": the UID by protocol is just for anti-collision purpose and the "real" card UID is for whitelist purposes.