/*
* Copyright 2012 Licel LLC.
*
* 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.
*/
package com.licel.jcardsim.utils;
import com.licel.jcardsim.smartcardio.JCardSimProvider;
import java.io.*;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import javax.smartcardio.*;
/**
* Execute APDU script in C-APDU format.
* @author LICEL LLC
*/
public class APDUScriptTool {
// printing to output
static boolean outputOn = true;
public static void main(String args[]) throws FileNotFoundException, IOException, NoSuchAlgorithmException, CardException {
if (args.length < 2) {
System.out.println("Usage: java com.licel.jcardsim.utils.APDUScriptTool <jcardsim.cfg> <apdu script> [out file]");
System.exit(-1);
}
Properties cfg = new Properties();
// init Simulator
FileInputStream fis = null;
try {
fis = new FileInputStream(args[0]);
cfg.load(fis);
} catch (Throwable t) {
System.err.println("Unable to load configuration " + args[0] + " due to: " + t.getMessage());
System.exit(-1);
} finally {
if (fis != null) {
fis.close();
}
}
PrintStream out = args.length == 3 ? new PrintStream(args[2]) : System.out;
fis = new FileInputStream(args[1]);
try {
executeCommands(cfg, fis, out);
} catch (Throwable t) {
System.err.println("Unable to execute " + args[1] + " due to: " + t.getMessage());
System.exit(-1);
} finally {
if (fis != null) {
fis.close();
}
if (args.length == 3 && out != null) {
out.close();
}
}
}
public static void executeCommands(Properties cfg, InputStream commandsStream,
PrintStream out) throws IOException, ParseException, NoSuchAlgorithmException, CardException {
Enumeration keys = cfg.propertyNames();
while(keys.hasMoreElements()) {
String propertyName = (String) keys.nextElement();
System.setProperty(propertyName, cfg.getProperty(propertyName));
}
ArrayList<CommandAPDU> commands = APDUScriptTool.parseAPDUStream(new InputStreamReader(commandsStream));
if (Security.getProvider("jCardSim") == null) {
JCardSimProvider provider = new JCardSimProvider();
Security.addProvider(provider);
}
TerminalFactory tf = TerminalFactory.getInstance("jCardSim", null);
CardTerminals ct = tf.terminals();
List<CardTerminal> list = ct.list();
CardTerminal jcsTerminal = null;
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getName().equals("jCardSim.Terminal")) {
jcsTerminal = list.get(i);
break;
}
}
Card jcsCard = jcsTerminal.connect("T=0");
CardChannel jcsChannel = jcsCard.getBasicChannel();
if (commands.size() > 0) {
for (int i = 0; i < commands.size(); i++) {
CommandAPDU command = commands.get(i);
ResponseAPDU response = jcsChannel.transmit(command);
String dump = APDUScriptTool.commandToStr(command) + APDUScriptTool.responseToStr(response);
if(out == null) {
System.out.println(dump);
} else {
out.println(dump);
}
}
}
}
private static ArrayList<CommandAPDU> parseAPDUStream(InputStreamReader in) throws IOException, ParseException {
ArrayList<CommandAPDU> apduCommands = new ArrayList<CommandAPDU>();
BufferedReader br = new BufferedReader(in);
String line = br.readLine();
StringBuilder command = new StringBuilder();
while (line != null) {
line = line.trim();
if (line.startsWith("//") || line.isEmpty()) {
line = br.readLine();
continue;
} else if (line.indexOf(";") < 0) {
command.append(line);
} else {
command.append(line.substring(0, line.indexOf(";")));
String cmd = command.toString();
String[] words = cmd.split("\\s+");
// skip some commands
if(cmd.equalsIgnoreCase("powerup")
|| cmd.equalsIgnoreCase("powerdown")
|| cmd.equalsIgnoreCase("contacted")
|| cmd.equalsIgnoreCase("contactless")) {
command = new StringBuilder();
command.append(line.substring(line.indexOf(";") + 1));
line = br.readLine();
continue;
} else if(words.length>=6) {
apduCommands.add(parseAPDUCommand(cmd));
command = new StringBuilder();
command.append(line.substring(line.indexOf(";") + 1));
}
}
line = br.readLine();
}
return apduCommands;
}
private static int parseNumber(String str) {
// hex number
if (str.startsWith("0x")) {
return Integer.parseInt(str.substring(2), 16);
} else {
return Integer.parseInt(str);
}
}
private static String toHex(int i) {
return i > 0xF ? Integer.toHexString(i) : "0" + Integer.toHexString(i);
}
private static CommandAPDU parseAPDUCommand(String command) throws ParseException {
String[] bytes = command.split("\\s+");
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>; "+command, 6);
}
int cla = parseNumber(bytes[0]);
int ins = parseNumber(bytes[1]);
int p0 = parseNumber(bytes[2]);
int p1 = parseNumber(bytes[3]);
int lc = parseNumber(bytes[4]);
int le = parseNumber(bytes[bytes.length - 1]);
// check lc
if (lc + 6 > bytes.length) {
throw new ParseException("Unexpected end of C-APDU: " + command, lc + 5);
}
byte[] data = new byte[lc];
for (int i = 0; i < lc; i++) {
data[i] = (byte) parseNumber(bytes[i + 5]);
}
return new CommandAPDU(cla, ins, p0, p1, data, le);
}
private static String commandToStr(CommandAPDU command) {
StringBuilder sb = new StringBuilder();
sb.append("CLA: ").append(toHex(command.getCLA())).append(", ");
sb.append("INS: ").append(toHex(command.getINS())).append(", ");
sb.append("P1: ").append(toHex(command.getP1())).append(", ");
sb.append("P2: ").append(toHex(command.getP2())).append(", ");
sb.append("Lc: ").append(toHex(command.getNc())).append(", ");
byte[] data = command.getData();
for (int i = 0; i < command.getNc(); i++) {
sb.append(toHex(data[i] & 0xFF)).append(", ");
}
return sb.toString();
}
private static String responseToStr(ResponseAPDU response) {
StringBuilder sb = new StringBuilder();
sb.append("Le: ").append(toHex(response.getNr())).append(", ");
byte[] data = response.getData();
for (int i = 0; i < data.length; i++) {
sb.append(toHex(data[i] & 0xFF)).append(", ");
}
sb.append("SW1: ").append(toHex(response.getSW1())).append(", ");
sb.append("SW2: ").append(toHex(response.getSW2()));
return sb.toString();
}
}