javax.smartcardio and EstEID
Since Java 1.6 you can communicate with any smart card you wish by using the Smart Card I/O API defined by JSR268. In this post EstEID is taken as an example.The first step is to list all card terminals/readers your computer is aware of:
TerminalFactory factory = TerminalFactory.getDefault();
List<CardTerminal> terminals = factory.terminals().list();
After listing all known terminals you have to choose one to operate with. Probably you only have one:
CardTerminal terminal = terminals.get(0);
The CardTerminal class has some useful methods like isCardPresent() and waitForCardPresent(long timeout) which you can use to recognize whether a card is in reader or not:
if (terminal.isCardPresent()){
...
}
After you are sure a card is present you can create a connection:
Card card = terminal.connect("T=0");
The “T=0” is the protocol that is chosen to communicate with Estonian ID card. This card also supports “T=1”, but as you can expect some commands have to be implemented differently. You can also use a ”*” when defining the protocol. Then the PC/SC layer decides which protocol to use.Each smartcard type has unique sequence of bytes, called ATR, which identifies the card. More precisely there could be 2 (“cold” and “warm”) ATRs. EstEID ATRs are well documented. You can read the ATR with following command:
ATR atr = card.getATR()
Let’s move on to more interesting part: sending commands and reading responses. Each command packet (called command APDU) consists of 4-byte mandatory header and a optional body of variable length. The response APDU in turn consists of a conditional body and a 2-byte trailer. At least in EstEID the 2-byte trailer showing that the command was successful is 90 00 (hex). Anything else and you can be sure the command was not successful. In such cases those 2 bytes are indicating an error code, which again is documented.To send and receive commands you first have to obtain a channel object:
CardChannel channel = card.getBasicChannel();
Sending a command and receiving response looks like this:
ResponseAPDU responseAPDU = channel.transmit(new CommandAPDU(new byte[]{…}))
Don’t forget to verify the response:
if (responseAPDU.getSW() != 0x9000){
// response not ok, handle error situation
}
Having the basic principles in mind, let’s look at an example. The following code reads out the personal file of EstEID card holder. There are few things you should remember:1. The card has it’s own file structure and before you can read the personal file you have to go to correct directory in filesystem. 2. If you convert bytes to string then the encoding to be used is windows-1252.3. All commands are documented in EstEID documentation (Estonian version). A helper class which is responsible for sending commands and handling responses.
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import java.io.UnsupportedEncodingException;
public class EstEIDUtil {
static String ENCODING = "windows-1252";
public static String bytesToString(byte[] data) {
try {
return new String(data, ENCODING);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Encoding " + ENCODING + " not supported");
}
}
public static byte[] sendCommand(CardChannel channel, CommandAPDU command) throws CardException {
ResponseAPDU responseAPDU = channel.transmit(command);
int responseStatus = responseAPDU.getSW();
if (!isResponseOk(responseStatus)){
throw new RuntimeException("Error code: " + responseStatus);
}
return responseAPDU.getData();
}
private static boolean isResponseOk(int responseStatus){
return responseStatus == 0x9000;
}
}
Class that represents the personal file itself. Just create a new object of this class and voila!
import javax.smartcardio.*;
import java.util.Arrays;
import static ee.esteid.EstEIDUtil.bytesToString;
import static ee.esteid.EstEIDUtil.sendCommand;
public class PersonalFile {
String[] data = new String[16];
public static final CommandAPDU SELECT_MASTER_FILE = new CommandAPDU(new byte[]{0x00, (byte)0xA4, 0x00, 0x0C});
public static final CommandAPDU SELECT_FILE_EEEE = new CommandAPDU(new byte[]{0x00, (byte)0xA4, 0x01, 0x0C, 0x02, (byte)0xEE, (byte)0xEE});
public static final CommandAPDU SELECT_FILE_5044 = new CommandAPDU(new byte[]{0x00, (byte)0xA4, 0x02, 0x04, 0x02, (byte)0x50, (byte)0x44});
public PersonalFile(CardChannel channel) throws CardException {
init(channel);
}
public String getSurName() {
return data[0];
}
public String getGivenName() {
return (data[1] + " " + data[2]).trim();
}
public String getSex() {
return data[3];
}
public String getCitizenship() {
return data[4];
}
public String getDateOfBirth() {
return data[5];
}
public String getPersonalCode() {
return data[6];
}
public String getDocumentNumber() {
return data[7];
}
public String getDateOfExpiry() {
return data[8];
}
public String getPlaceOfBirth() {
return data[9];
}
public String getDateOfIssue() {
return data[10];
}
public String getResidencePermitType() {
return data[11];
}
public String getRemark1() {
return data[12];
}
public String getRemark2() {
return data[13];
}
public String getRemark3() {
return data[14];
}
public String getRemark4() {
return data[15];
}
@Override
public String toString() {
return "PersonalFile{" +
"data=" + Arrays.asList(data) +
'}';
}
private void init(CardChannel channel) throws CardException {
sendCommand(channel, SELECT_MASTER_FILE);
sendCommand(channel, SELECT_FILE_EEEE);
sendCommand(channel, SELECT_FILE_5044);
for (byte i = 1; i ```
Exactly the same way you can change and unlock PINs, read certificates etc. Have fun with your own experiments!
Our recent stories
Partnering with Nicigas in their Energy Development Business
In Codeborne we have built energy information systems in Estonia, Sweden, Norway, Denmark, Luxembourg, Austria, and Japan. With Nicigas we combined our expertise and skillset to create something new on the Japanese market.
From wine tasting to digital innovation- the birth of the Wine Experience Club
We recently sat down with our client, Rait Maasikas, to go deeper into the story behind one of our more unusual recent projects- the Wine Experience Club.
Innovating the Austrian energy market with Spotty Smart Energy Partner
Spotty Smart Energy Partner GmbH, an Austrian energy provider, partnered with Codeborne to enhance their services and bring innovative energy solutions to the market