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

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.

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.

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;
}

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

Related

Sockets -- Different bytes from python and java

I have been working on a networking project recently to revive a dead mmo game for personal learning, I have a python implementation which works to decode the game data using blowfish(pypi / pycryptodome) and would like to transfer this 'server' into a java project.
Initially using blowfish decryption in java (BouncyCastle and Cipher -- default) I was getting entirely different results between java and python. Through some research I found that java (along with most things) actually use blowfish-compat big endian.
This one python library seems to be the only one which correctly decodes the data. Next I decided to use a python asyncio server as a middle relay simply for encryption and decryption. The network flow now looks like this:
GameClient -> Java SocketServer -> Python server (decryption) -> Java SocketServer.
The original Python implementation results in these bytes in hex format:
32004a815f49367cc3691be26d7b668132506dc972d5a6bbad38299640c6e222c6e55096f50ff33711250675431633ca9ede
The Java implementation produces these results in hex format(using apache commons Hex.encodeHexString())
32004a815f49367cc3691be26d7b668132506dc972d5a6bbad38299640c6e222c6e5c65830d65f9b4d60eb26730685f486d7
Both of these hex representations are pre-blowfish decryption in Python they are just the raw bytes being sent from the game client.
My question is why do these bytes start off the same and then it seems java trails off? The python results are the correct results they are tested and work. I have tried wrapping the bytes in java in a buffer and then calling flip() however this did not produce the correct results either. Using another stackoverflow post (I do not have link I am sorry) I tried casting this byte[] into a BigInteger and that also did not produce the correct results.
Any help is greatly appreciated.
Python Implementation
#!/usr/bin/env python3
import asyncio
import binascii
import blowfish
import ipaddress
import os
import struct
import sys
AUTH_BLOWFISHKEY = b"[;'.]94-31==-%&#!^+]\000"
bf = blowfish.Cipher(AUTH_BLOWFISHKEY, byte_order="little")
class EncryptionRelay(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
self.client = (transport.get_extra_info('peername')[0] + ":" # IP
+ str(transport.get_extra_info('peername')[1])) # port
print("Connection from: " + self.client)
def connection_lost(self, exc):
print("Connection closed: " + self.client)
def data_received(self, data):
print(data.hex()) #python output above
pt = b''.join(bf.decrypt_ecb(data[2:]))
self.transport.write(pt)
def closeSocket(self, reason):
print(reason)
self.transport.close()
def main():
loop = asyncio.get_event_loop()
coroutine = loop.create_server(EncryptionRelay, host=None, port=54556)
server = loop.run_until_complete(coroutine)
for socket in server.sockets:
print("Listening on: " + socket.getsockname()[0] + ":" +
str(socket.getsockname()[1]))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
if __name__ == "__main__":
main()
Java Implementation
public AuthServer(int port) {
serverGUI = new AuthServerGUI(port);
try {
serverSocket = new ServerSocket(port);
relay = new PythonEncryptionRelay(this);
new Thread(relay).start();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void run() {
while(true) {
try {
Socket socket = serverSocket.accept();
onConnection(socket); //sends an init packet to client -- irrelevant to question
byte[] incomingData = new byte[0];
byte[] temp = new byte[1024];
int k = -1;
while((k = socket.getInputStream().read(temp, 0, temp.length)) > -1) {
byte[] tbuff = new byte[incomingData.length + k];
System.arraycopy(incomingData, 0, tbuff, 0, incomingData.length);
System.arraycopy(temp, 0, tbuff, incomingData.length, k);
incomingData = tbuff;
receiveData(socket, incomingData);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void receiveData(Socket socket, byte[] data) {
int lenLo = (int) (data[0]);
int lenHi = (int) (data[1]);
int length = lenHi * 256 + lenLo;
if(lenHi < 0) {
System.out.println("Invalid Packet Length");
}
if(data.length != length) {
System.out.println("Incomplete Packet Received");
}
serverGUI.serverDebug("DATA RECEIVED");
serverGUI.serverDebug(Hex.encodeHexString(data)); //this is the java ouput above serverGUI is simply a jframe i built no data manipulation
serverGUI.serverDebug("DATA_RECEIVED DONE");
this.relay.sendData(data); //this function sends the data from socket server to the python asyncio server
}
public void receiveDataFromPythonRelay(Socket socket, byte[] data) {
serverGUI.debugPythonRelay("DATA RECEIVED");
serverGUI.debugPythonRelay(Hex.encodeHexString(data)); //this will be the output from the python script aka data decrypted.
//The data byte[] is created in the exact same way the incomingData array is built in the AuthServer run function
serverGUI.debugPythonRelay("DATA_RECEIVED DONE");
}
Additionally the way I am importing the data byte[] from sockets is programmed as such because the client does not send endl therefore readLine will not work from the streams.
A byte has 8 bits which means you can have maximum 0xff as value.
But Java uses signed byte which means msb is reserved for signed bit. This leaves you with only 7 bits for your value, so you can have maximum as 0x7f stored in byte type of variable. Any number greater than 0x07f will result in overflow.
Try using an int array. As int uses 4 bytes (32 bits) hence there will always be space for 8 bits.
use byte[] to read from the stream and then copy the contents in int[] , Use int intArr[i] = byteArr[i] & 0xFF; for ith value from byte[] to avoid negative numbers due to byte overflow

Unkown error 0x16 on smartcard reader access

I am trying to change the buzzer duration on the ACR1252U.
Link to API:
http://www.acs.com.hk/download-manual/6402/API-ACR1252U-1.09.pdf
According to the API documentation I need the 'E0000028010A' command to change the buzzer status, whereby '0A' marks the duration as 0A*10ms (Page 44).
Following Java code is used:
public static void main(String[] args) {
try {
byte[] send = new byte[6];
send[0] = (byte) 0xE0; // Commandclass
send[1] = (byte) 0x00; // Protocoll
send[2] = (byte) 0x00; // Param 1
send[3] = (byte) 0x28; // Param 2: Buzzerstatus
send[4] = (byte) 0x01; // Change Flag
send[5] = (byte) 0x0A; // Duration: 0A*10ms => 100ms
Card card = getCard("DIRECT"); // Works!
CardChannel channel = card.getBasicChannel(); // Works!
CommandAPDU command = new CommandAPDU(send); // Works!
channel.transmit(command); // EXCEPTION!
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static Card getCard(String target) throws Exception {
TerminalFactory factory = TerminalFactory.getDefault();
List<CardTerminal> terminals = factory.terminals().list();
for (CardTerminal t : terminals) {
if (t.getName().equals("ACS ACR1252 Dual Reader PICC 0")) {
Card card = t.connect(target);
return card;
}
}
throw new Exception();
}
But this results in the following stacktrace indicating the "unkown error 0x16":
javax.smartcardio.CardException: sun.security.smartcardio.PCSCException: Unknown error 0x16
at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:219)
at sun.security.smartcardio.ChannelImpl.transmit(ChannelImpl.java:90)
at readerconfig.TagConfig.main(TagConfig.java:24)
Caused by: sun.security.smartcardio.PCSCException: Unknown error 0x16
at sun.security.smartcardio.PCSC.SCardTransmit(Native Method)
at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:188)
... 2 more
I've spent hours on searching for anything in this direction however I couldn't find anything. I have even tried another device, which still generated this error.
Either I have completely gone blind or something is not set up correctly with my computer. All I can say is, that I have already successfully written and read from NFC tags using this reader. But I just can't change the config of the reader itself.
EDIT:
I've also found this alternative way to send the command:
byte[] send = new byte[5];
send[0] = (byte) 0xE0;
send[1] = (byte) 0x0;
send[2] = (byte) 0x0;
send[3] = (byte) 0x18; // Tries to read firmware version
send[4] = (byte) 0x0;
Card card = CardUtils.getCard("DIRECT"); // Works!
card.transmitControlCommand(3500, send);
But this results in the "unknown error 0x1":
javax.smartcardio.CardException: transmitControlCommand() failed
at sun.security.smartcardio.CardImpl.transmitControlCommand(CardImpl.java:236)
at readerconfig.ReaderConfig.main(ReaderConfig.java:28)
Caused by: sun.security.smartcardio.PCSCException: Unknown error 0x1
at sun.security.smartcardio.PCSC.SCardControl(Native Method)
at sun.security.smartcardio.CardImpl.transmitControlCommand(CardImpl.java:232)
... 1 more
There are two ways to interact with this reader over the Java Smartcard IO API:
The first is to open a regular APDU transmission channel (from a PC/SC point of view this maps to T=0 or T=1 protocol). You can do this using
Card card = getCard("*");
However, this will require the reader to report the presence of a card. Otherwise you can't open a connection that way.
You can then transmit APDU commands to the card (on the basic channel or a logical channel) and you can send special commands to the reader on the basic channel. These special commands have their class byte set to 0xFF to indicate that the command is intended to be interpreted by the reader (instead of being forwarded to the card). So this is not applicable for the "peripherals control" commands that start with 0xE0.
Those "peripherals control" commands have to be sent to the reader using control commands with the control code SCARD_CTL_CODE(3500). As with opening a connection to the card, you can use getCard("*") if there is a card present on the reader. However, if you want to be able to send those commands to the reader even if there is no card present, you have to open a connection in "direct" mode:
Card card = getCard("DIRECT");
You can then send control commands using the method card.transmitControlCommand(). This method takes the control code as the first argument and the command (as byte array) as the second argument. Exchanging commands on the basic channel or any logical channel using channel.transmit() will usually not work in "direct" mode (hence the error code 0x16).
The control code is calculated as
public static final int SCARD_CTL_CODE(int command) {
boolean isWindows = System.getProperty("os.name").startsWith("Windows");
if (isWindows) {
return 0x00310000 | (command << 2);
} else {
return 0x42000000 | command;
}
}
Note the difference between Windows and other platforms.
For instance, to send the buzzer control command, you would use
byte[] command = new byte[] { (byte)0xE0, (byte)0x00, (byte)0x00, (byte)0x28, (byte)0x01, (byte)0x0A };
byte[] response = card.transmitControlCommand(SCARD_CTL_CODE(3500), command);
Finally, be aware that sending IOCTL control codes over PC/SC requires special driver support. Specifically, the standard CCID driver provided by Microsoft does not support sending escape commands by default (see USB CCID Class Driver Details). This driver supports escape commands only after enabling them through the registry value "EscapeCommandEnable". The error 0x1 that you showed in your question is a typical result of this missing support for escape commands.
To reliably support all features of the reader (including escape commands) you need to use the "PC/SC Drivers" package provided by ACS on their website.
Try to use
card.transmitControlCommand(int controlCode, byte[] command)
instead of transmit. According to section 5.8 (page 41 of the pdf you linked to)
controlcode is 3500, although it is unclear to me, if that is a hex or an int, so compare to SCARD_CTL_CODE, if you are able to. At least, I interpret the documentation this.
Usually you use transmitControlCommand to talk to the reader and transmit to talk to the card.
Fixed typo in ControlCode. Kudos to Torhan Bartel for telling me.

Converting pointer to string and send as char array not working properly

I have a problem with sending directory names over socket from my C++ client, to my Java server.
Sending ordinary messages like "hello world", works great , but the following doesn't and I can not figure out what the problem is:
char const * files = ffd.cFileName; // get directory name
string str(files, 0, strlen(files)); // convert pointer to string, right?
char mess[str.size()];
strcpy(mess, str.c_str()); // make char array :)
cout << "Send file: " << mess << " with strlen: " << strlen(mess) << " and sizeof: " << sizeof(mess) << endl;
int sent = 0;
if ((sent = send(connectSocket, mess, sizeof(mess), 0)) == SOCKET_ERROR)
{
closesocket(connectSocket);
WSACleanup();
connectToServer();
}
The java server just receives the directory names like this:
wam
p
Win
dow
s
Win
dow
s.o
ld
wxW
idg
ets
I can not understand what I'm missing because I have tried every possible way to do this and the C++ client prints like:
"Send file: windows with strlen: 7 and sizeof: 7"
I do not think that the java server is the problem since I can receive normal strings and messages perfectly, but anyway here is the JAVA code:
is = socket.getInputStream();
byteArray = new byteArray[1024];
while (true) {
c = is.read(byteArray, 0, byteArray.length);
String recv = new String(byteArray, 0, c);
System.out.println(recv);
if (recv.equals("<EOF>")){
break;
}
list.add(recv);
}
If you request something else or anything just leave a comment and I will fix it.
Question: are you sending via TCP or UDP? I'm guessing TCP, and if that is the case, you need to treat the socket as more of a stream. That stream may get broken up into a bunch of packets - you don't really control that. What I might do is to prefix the string length of each directory (ex, 3foo, 4barz, etc), read from the socket and determine what constitutes as a logical block or string, and then assemble / print the strings based on that. If you go with that route, you need to track how much you read each time until you think you are done.
I solved it, Just added byteArray = new byte[1024]; and now it works:
while (true) {
byteArray = new byte[1024]; // I ADDED THIS AND NOW THE JAVA SERVER RECEIVES IT CORRECTLY!
c = is.read(byteArray, 0, byteArray.length);
recv = new String(byteArray, 0, c);
System.out.println(recv);
if (recv.equals("<EOF>")){
break;
}
list.add(recv);
}

Java Convert 7bit Charset Octets to Readable String (From PDU SMS)

I'm receiving SMS from GSM modem in PDU format; the TP-User-Data is "C8329BFD06DDDF72363904"
and what I get is: "�2����r69", while the sent sms is "Hello World!".
Here is my java code:
private String fromPDUText(String PDUSMSText) {
String endoding = PDUSMSText.substring(0, 2);
PDUSMSText = PDUSMSText.substring(18);
byte bs[] = new byte[PDUSMSText.length() / 2];
for(int i = 0; i < PDUSMSText.length(); i += 2) {
bs[i / 2] = (byte) Integer.parseInt(PDUSMSText.substring(i, i + 2), 16);
}
try {
String out = new String(bs, "ASCII");
} catch(UnsupportedEncodingException e) {
e.printStackTrace();
return "";
} finally {
return out;
}
}
The input is packed in 7-bits per character, which means that every 8 bytes encode 9 characters. Constructing a parser for this format can be a fun exercise or a frustrating experience, depending on how you take it. You are probably better off using a library, and a quick Google search reveals several code examples.
This is how 7Bit characters are packed:
Encoding-Decoding-7-bit-User-Data-for-SMS-PDU-PDU
Personally I find it easiest to attack this kind of problem by viewing it as having a pipe where you feed 8 bits in one end and retrieve 7 bits in the other. As long as there is at least 7 bits in the pipe you read from it. When there are less than 7 bits you need to add some more so you write 8 new bits to it. So what you need is:
A pipe that can hold at least 14 bits (but why be cheap? Go with a 32-bit int!).
A counter keeping track of how many bits are in the pipe at any given moment.
The algorithm in pseudo code is as follows:
pipe = 0;
bitCount = 0;
while(hasMoreData())
{
pipe |= readByte() << bitCount;
bitCount += 8;
while(bitCount >= 7)
{
writeByte(pipe & 0x7F);
pipe >>= 7;
bitCount -= 7;
}
}

Decode "send" message from WebSocket with Java

I'm trying to write my own WebSocket Server.
I know that there are some frameworks for this but i would like to learn, so I do it on my own :)
My JavaScript client reacts currently with onOpen, so I think the handshake is valid and
the connection is established.
Now the problem:
My Java server uses a selector thread for reading on an Channel.
If I do *.send("test message") on the WebSocket object at my client, my server can't decode the byte array.
I found no information about the used encode/decode technology, so I tried many versions to decode.
for example:
new String(Base64.decodeBase64(buffer.array()))
or
Charset set = Charset.forName("UTF-8");
new String(Base64.decodeBase64(set.decode(buffer).toString()))
The message is completely read from the Channel into an ByteBuffer, so I don't think this is the problem.
Can anyone help me?
okay this Post helps me to send data to the Client. This works fine :)
But I don't understand how to decode the data received from the Client :(
At the Client i send only one Letter
socket.send("x");
The Server receives 7 byte ???
ByteBuffer buffer = ByteBuffer.allocate(8192);
int read = client.getInputStream().read(buffer2.array());
System.out.println("read: " + read);
// read: 7
for (int i = 0; i < read; i++) {
int j = buffer.get(i) & 0xff;
System.out.println("i: " + i + " => " + j + "=>" + new BigInteger(j + "").toString(2));
}
//i: 0 => 129=>10000001
//i: 1 => 129=>10000001
//i: 2 => 195=>11000011
//i: 3 => 235=>11101011
//i: 4 => 4=>100
//i: 5 => 96=>1100000
//i: 6 => 187=>10111011
If i do this
secondByte AND 0111 1111
the result of (i: 1) is "1" i think this means that there are only one byte data. Then why read is 7 ???
As for your second issue - the data from client to server is always masked as I also explained at the link above. Masks take 4 bytes.
So, the length of the actual data is indeed 1. Only the last 7 bits of the second byte say something about the length - the first bit doesn't, so just discard that one to get 000 0001 which is 1.
The bytes are categorised as follows in this case:
0, 1 are meta bytes
3, 4, 5, 6 are masks
7 is the data
To get the data, calculate data XOR masks[data_index MOD 4], i.e. 187 XOR 195 in this case, which is 120 - the character code for x.
private String decodeMessage(){
try {
byte[] data = new byte[1024];
int size = in.read(data);
if (size == -1) return null;
byte[] decoded = new byte[size-6];
byte[] key = new byte[]{ data[2], data[3], data[4], data[5] };
for (int i = 0; i < size-6; i++) {
decoded[i] = (byte) (data[i+6] ^ key[i & 0x3]);
}
return new String(decoded, "UTF-8");
}catch (IOException ex){
ex.printStackTrace();
}
return "ping";
}
This code is probably bad, but it works for me

Categories

Resources