Skip to content

Instantly share code, notes, and snippets.

@abihf
Last active August 29, 2015 14:07
Show Gist options
  • Save abihf/37864ed37b84a50c2500 to your computer and use it in GitHub Desktop.
Save abihf/37864ed37b84a50c2500 to your computer and use it in GitHub Desktop.
Virtual Java Card
/**
* Virtual Java Card
* Copyright 2014-2015 Abi Hafshin < abi [at] hafs [dot] in >
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// original https://github.com/licel/jcardsim/blob/master/src/main/java/com/licel/jcardsim/utils/APDUScriptTool.java
import com.licel.jcardsim.io.JavaxSmartCardInterface;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Properties;
import javacard.framework.AID;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
/**
* Main Class
*
* Use:
* java -cp <path/to/jcardsim.jar> VirtualJCard <jcardsim.cfg> port [host]
*
* Dependency:
* Virtual Smart Card (https://frankmorgner.github.io/vsmartcard/)
* jCardSim (http://jcardsim.org/)
*/
public class VirtualJCard {
private static final byte[] DEFAULT_ATR = {(byte)0x3B, (byte)0x95, (byte)0x13, (byte)0x81, (byte)0x01,
(byte)0x80, (byte)0x73, (byte)0xFF, (byte)0x01, (byte)0x00, (byte)0x0B};
private static final byte[] COMMAND_SELECT = {(byte)0x00, (byte)0xa4, (byte)0x00, (byte)0x00, (byte)0x09};
private static final byte[] RESPONSE_OK = {(byte)0x90, (byte)0x00};
private static final byte[] RESPONSE_INVALID_APDU = {(byte)0x6D, (byte)0x00};
protected JavaxSmartCardInterface simulator;
protected Properties config = new Properties();
protected byte[] atr = DEFAULT_ATR;
/**
* Entry point
*
* @param args
*/
public static void main(String args[]) {
if (args.length < 2) {
System.out.println("Usage: java -cp jcardsim-2.2.1-all.jar VirtualJCard <jcardsim.cfg> port [host]");
System.exit(-1);
}
int port = Integer.parseInt(args[1]);
String host = "127.0.0.1";
if (args.length > 2) {
host = args[2];
}
try {
VirtualJCard vjc = new VirtualJCard();
vjc.loadConfig(args[0]);
vjc.run(host, port);
} catch (IOException t) {
System.err.println("Unable to execute due to: " + t.getMessage());
}
}
/**
* Load configuration from file
*
* @param configFile
* @throws FileNotFoundException
* @throws IOException
*/
public void loadConfig(String configFile) throws FileNotFoundException, IOException {
try (FileInputStream fis = new FileInputStream(configFile)) {
config.load(fis);
String strAtr = config.getProperty("card.atr", null);
if (strAtr != null) {
atr = hexToBytes(strAtr);
}
}
}
/**
* Run VirtualJCard !!!
*
* @param host
* @param port
* @throws IOException
*/
public void run(String host, int port) throws IOException {
simulator = new JavaxSmartCardInterface();
installApplets();
Socket sock = new Socket(host, port);
while (true) {
byte[] buff = recv(sock);
if (buff == null || buff.length == 0) {
break;
} else if (buff.length == 1) {
switch (buff[0]) {
case 0:
System.out.println("power off");
break;
case 1:
System.out.println("power on");
break;
case 2:
System.out.println("reset");
break;
case 4:
send(sock, atr);
break;
}
} else if (commandIsSelect(buff)) {
simulator.selectApplet(new AID(buff, (short) 5, (byte) (buff.length - 6)));
send(sock, RESPONSE_OK);
} else {
CommandAPDU command;
ResponseAPDU response;
try {
command = parseAPDUByte(buff);
response = simulator.transmitCommand(command);
} catch (ParseException ex) {
response = new ResponseAPDU(RESPONSE_INVALID_APDU);
System.err.println("Invalid apdu " + Arrays.toString(buff));
}
send(sock, response.getBytes());
}
}
}
/**
* Install applets into simulator based on configurations
*/
private void installApplets() {
for (int i= 0;; i++) {
String strAid = config.getProperty("applet.aid." + i, null);
String className = config.getProperty("applet.class." + i, null);
if (null == strAid || null == className)
break;
try {
Class cls = Class.forName(className);
byte[] bAid = hexToBytes(strAid);
simulator.installApplet(new AID(bAid, (short)0, (byte)bAid.length), cls);
} catch (ClassNotFoundException ex) {
System.out.println("WARNING: CLass Nout Found: " + className);
}
}
}
/**
* Check if APDU command is select command
*
* @param cmd APDU as byte[]
* @return
*/
private static boolean commandIsSelect(byte[] cmd) {
for (int i = 0; i < COMMAND_SELECT.length; i++) {
if (COMMAND_SELECT[i] != cmd[i]) return false;
}
return true;
}
/**
* Convert hex string to byte array
*
* @param hex
* @return
*/
private static byte[] hexToBytes(String hex) {
byte len = (byte) (hex.length() / 2);
byte[] bytes = new byte[len];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) (hexToByte(hex.charAt(i*2)) << 4 |
hexToByte(hex.charAt(i*2+1)));
}
return bytes;
}
/**
* Convert single character hex to byte
*
* @param c
* @return
*/
private static byte hexToByte(char c) {
if (c >= '0' && c <= '9')
return (byte) (c - '0');
else if (c >= 'a' && c <= 'f')
return (byte) (c - 'a' + 10);
else if (c >= 'A' && c <= 'F')
return (byte) (c - 'A' + 10);
throw new NumberFormatException();
}
/**
* Receive data from socket
*
* @param sock connected socket
* @return data
* @throws IOException
*/
private static byte[] recv(Socket sock) throws IOException {
InputStream in = sock.getInputStream();
byte[] bSize = new byte[2];
if (in.read(bSize) < 2) {
return null;
}
int size = ((short)(bSize[0]) << 8) | (short)(bSize[1]);
byte[] buff = new byte[size];
int realLen = in.read(buff);
if (realLen != size) {
System.out.println("warning: invalid length");
}
return buff;
}
/**
* Send data via socket
*
* @param sock
* @param buff
* @param size
* @throws IOException
*/
private static void send(Socket sock, byte[] buff) throws IOException {
OutputStream out = sock.getOutputStream();
int size = buff.length;
byte[] bSize = {(byte)(size << 8), (byte)(size & 0xff)};
out.write(bSize);
out.write(buff, 0, size);
}
private static CommandAPDU parseAPDUByte(byte[] bytes) throws ParseException {
if (bytes.length < 6) {
throw new ParseException("C-APDU format must be: <CLA> <INS> <P1> <P2> <LC> [<byte 0> <byte 1> ... <byte LC-1>] <LE>; ", 6);
}
int cla = bytes[0];
int ins = bytes[1];
int p0 = bytes[2];
int p1 = bytes[3];
int lc = bytes[4];
int le = bytes[bytes.length - 1];
// check lc
if (lc + 6 > bytes.length) {
throw new ParseException("Unexpected end of C-APDU: " + new String(bytes), lc + 5);
}
byte[] data = new byte[lc];
for (int i = 0; i < lc; i++) {
data[i] = (byte) bytes[i + 5];
}
return new CommandAPDU(cla, ins, p0, p1, data, le);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment