Greetings!
I have developed successful solutions for reading DESFire smart cards used in Croatian public services, both for:
Right now, I'm trying to develop a multi platform solution in Java, using TapLinx SDK for Java and high-level approach.
I rely on javax.smartcardio for polling connected card terminals to detect when smart card is inserted or removed, and it works perfectly. In my testing app (JavaFX if that matters), in onCardInserted event, I call 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());
// WHAT NOW, I WONDER ???
desFire.getReader().connect(); // This does not work, of course. HOW TO associate the context of the javaCard object with getting a concrete reader, so that...
byte[] uid = desFire.getUID(); //... I can do this, and more, 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 apps.
I also tried raw APDU approach in Java, without TapLinx SDK at all, 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.
Please help.
Solved! Go to 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!?!?!!!
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 MyCardApduHandler(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 MyCardReader(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();
}
Too bad that most of that is not anywhere near the sample apps...
I've found the solution. So, in case someone encounters similar problem
code goes like this. BTW, the key is in the setTransceive method!?!?!!!
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 MyCardApduHandler(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 MyCardReader(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();
}
Too bad that most of that is not anywhere near the sample apps...