Last active
August 29, 2015 14:07
-
-
Save abihf/37864ed37b84a50c2500 to your computer and use it in GitHub Desktop.
Virtual Java Card
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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