/*
* Copyright 2011 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.base;
import com.licel.jcardsim.io.JavaCardInterface;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.Properties;
import com.licel.jcardsim.utils.AIDUtil;
import com.licel.jcardsim.utils.ByteUtil;
import javacard.framework.*;
import org.bouncycastle.util.encoders.Hex;
/**
* Simulates a JavaCard.
*/
public class Simulator implements JavaCardInterface {
// default ATR - NXP JCOP 31/36K
static final String DEFAULT_ATR = "3BFA1800008131FE454A434F5033315632333298";
// ATR system property name
static final String ATR_SYSTEM_PROPERTY = "com.licel.jcardsim.card.ATR";
// card ATR
private byte[] atr = null;
static final String PROPERTY_PREFIX = "com.licel.jcardsim.card.applet.";
static final String OLD_PROPERTY_PREFIX = "com.licel.jcardsim.smartcardio.applet.";
// Applet AID system property template
static final MessageFormat AID_SP_TEMPLATE = new MessageFormat("{0}.AID");
// Applet ClassName system property template
static final MessageFormat APPLET_CLASS_SP_TEMPLATE = new MessageFormat("{0}.Class");
// Applet Class Loader
final AppletClassLoader cl = new AppletClassLoader(new URL[]{});
/** The simulator runtime */
protected final SimulatorRuntime runtime;
// current protocol
private String protocol = "T=0";
/**
* Create a Simulator object using the default SimulatorRuntime.
*
* <ul>
* <li>All <code>Simulator</code> instances share one <code>SimulatorRuntime</code>.</li>
* <li>SimulatorRuntime#resetRuntime is called</li>
* <li>If your want multiple independent simulators use <code>Simulator(SimulatorRuntime)</code></li>
* </ul>
*/
public Simulator() {
this(SimulatorSystem.DEFAULT_RUNTIME, System.getProperties());
}
/**
* Create a Simulator object using a provided Runtime.
*
* <ul>
* <li>SimulatorRuntime#resetRuntime is called</li>
* </ul>
*
* @param runtime SimulatorRuntime instance to use
* @throws java.lang.NullPointerException if <code>runtime</code> is null
*/
public Simulator(SimulatorRuntime runtime) {
this(runtime, System.getProperties());
}
protected Simulator(SimulatorRuntime runtime, Properties properties) {
if (runtime == null) {
throw new NullPointerException("runtime");
}
this.runtime = runtime;
synchronized (this.runtime) {
this.runtime.resetRuntime();
}
changeProtocol(protocol);
atr = Hex.decode(properties.getProperty(ATR_SYSTEM_PROPERTY, DEFAULT_ATR));
// init pre-installed applets
for (int i = 0; i < 10 && !properties.isEmpty(); i++) {
String selectedPrefix = PROPERTY_PREFIX;
String aidPropertyName = PROPERTY_PREFIX + AID_SP_TEMPLATE.format(new Object[]{i});
String aidPropertyOldName = OLD_PROPERTY_PREFIX + AID_SP_TEMPLATE.format(new Object[]{i});
String appletAID = properties.getProperty(aidPropertyName);
if (appletAID == null) {
appletAID = properties.getProperty(aidPropertyOldName);
if (appletAID != null) {
selectedPrefix = OLD_PROPERTY_PREFIX;
}
}
if (appletAID != null) {
String appletClassName = properties.getProperty(selectedPrefix + APPLET_CLASS_SP_TEMPLATE.format(new Object[]{i}));
if (appletClassName != null) {
byte[] aidBytes = Hex.decode(appletAID);
if (aidBytes == null || aidBytes.length < 5 || aidBytes.length > 16) {
// skip incorrect applet
continue;
}
loadApplet(new AID(aidBytes, (short) 0, (byte) aidBytes.length), appletClassName);
}
}
}
}
public AID loadApplet(AID aid, String appletClassName, byte[] appletJarContents) throws SystemException {
// simple method, but emulate real card login
// download data
byte[] aidData = new byte[16];
aid.getBytes(aidData, (short) 0);
Class<? extends Applet> appletClass = null;
try {
cl.addAppletContents(appletJarContents);
appletClass = requireExtendsApplet(cl.loadClass(appletClassName));
} catch (Exception e) {
SystemException.throwIt(SystemException.ILLEGAL_VALUE);
}
if (appletClass != null) {
return loadApplet(aid, appletClass);
} else {
SystemException.throwIt(SystemException.ILLEGAL_VALUE);
return null;
}
}
public AID loadApplet(AID aid, String appletClassName) throws SystemException {
Class<? extends Applet> appletClass = null;
try {
appletClass = requireExtendsApplet(cl.loadClass(appletClassName));
} catch (ClassNotFoundException ex) {
SystemException.throwIt(SystemException.ILLEGAL_VALUE);
}
return loadApplet(aid, appletClass);
}
/**
* Load
* <code>Applet</code> into Simulator
*
* @param aid applet aid
* @param appletClass applet class
* @return applet <code>AID</code>
* @throws SystemException if <code>appletClass</code> not instanceof
* <code>javacard.framework.Applet</code>
*/
public AID loadApplet(AID aid, Class<? extends Applet> appletClass) throws SystemException {
synchronized (runtime) {
runtime.loadApplet(aid, requireExtendsApplet(appletClass));
}
return aid;
}
public AID createApplet(AID aid, byte bArray[], short bOffset,
byte bLength) throws SystemException {
try {
synchronized (runtime) {
runtime.installApplet(aid, bArray, bOffset, bLength);
}
}
catch (Exception e) {
SystemException.throwIt(SimulatorSystem.SW_APPLET_CREATION_FAILED);
}
return aid;
}
/**
* Install
* <code>Applet</code> into Simulator without installing data
*
* @param aid applet aid or null
* @param appletClass applet class
* @return applet <code>AID</code>
* @throws SystemException if <code>appletClass</code> not instanceof
* <code>javacard.framework.Applet</code>
*/
public AID installApplet(AID aid, Class<? extends Applet> appletClass) throws SystemException {
return installApplet(aid, appletClass, new byte[]{}, (short) 0, (byte) 0);
}
/**
* Install
* <code>Applet</code> into Simulator. This method is equal to:
* <code>
* loadApplet(...);
* createApplet(...);
* </code>
*
* @param aid applet aid or null
* @param appletClass applet class
* @param bArray the array containing installation parameters
* @param bOffset the starting offset in bArray
* @param bLength the length in bytes of the parameter data in bArray
* @return applet <code>AID</code>
* @throws SystemException if <code>appletClass</code> not instanceof
* <code>javacard.framework.Applet</code>
*/
public AID installApplet(AID aid, Class<? extends Applet> appletClass, byte bArray[], short bOffset,
byte bLength) throws SystemException {
synchronized (runtime) {
loadApplet(aid, appletClass);
return createApplet(aid, bArray, bOffset, bLength);
}
}
public AID installApplet(AID aid, String appletClassName, byte bArray[], short bOffset,
byte bLength) throws SystemException {
synchronized (runtime) {
loadApplet(aid, appletClassName);
return createApplet(aid, bArray, bOffset, bLength);
}
}
public AID installApplet(AID aid, String appletClassName, byte[] appletContents, byte bArray[], short bOffset,
byte bLength) throws SystemException {
synchronized (runtime) {
loadApplet(aid, appletClassName, appletContents);
return createApplet(aid, bArray, bOffset, bLength);
}
}
/**
* Delete an applet
* @param aid applet aid
*/
public void deleteApplet(AID aid) {
synchronized (runtime) {
runtime.deleteApplet(aid);
}
}
public boolean selectApplet(AID aid) throws SystemException {
byte[] resp = selectAppletWithResult(aid);
return ByteUtil.getSW(resp) == ISO7816.SW_NO_ERROR;
}
public byte[] selectAppletWithResult(AID aid) throws SystemException {
synchronized (runtime) {
return runtime.transmitCommand(AIDUtil.select(aid));
}
}
public byte[] transmitCommand(byte[] command) {
synchronized (runtime) {
return runtime.transmitCommand(command);
}
}
public void reset() {
synchronized (runtime) {
runtime.reset();
}
}
public final void resetRuntime() {
synchronized (runtime) {
runtime.resetRuntime();
}
}
public byte[] getATR() {
return atr;
}
protected byte getProtocolByte(String protocol) {
if (protocol == null) {
throw new NullPointerException("protocol");
}
String p = protocol.toUpperCase(Locale.ENGLISH).replace(" ","");
byte protocolByte;
if (p.equals("T=0") || p.equals("*")) {
protocolByte = APDU.PROTOCOL_T0;
}
else if (p.equals("T=1")) {
protocolByte = APDU.PROTOCOL_T1;
}
else if (p.equals("T=CL,TYPE_A,T0") || p.equals("T=CL")) {
protocolByte = APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_A;
protocolByte |= APDU.PROTOCOL_T0;
}
else if (p.equals("T=CL,TYPE_A,T1")) {
protocolByte = APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_A;
protocolByte |= APDU.PROTOCOL_T1;
}
else if (p.equals("T=CL,TYPE_B,T0")) {
protocolByte = APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_B;
protocolByte |= APDU.PROTOCOL_T0;
}
else if (p.equals("T=CL,TYPE_B,T1")) {
protocolByte = APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_B;
protocolByte |= APDU.PROTOCOL_T1;
}
else {
throw new IllegalArgumentException("Unknown protocol: " + protocol);
}
return protocolByte;
}
/**
* @see com.licel.jcardsim.io.JavaCardInterface#changeProtocol(String)
*/
public void changeProtocol(String protocol) {
synchronized (runtime) {
runtime.changeProtocol(getProtocolByte(protocol));
this.protocol = protocol;
}
}
/**
* @see com.licel.jcardsim.io.JavaCardInterface#getProtocol()
*/
public String getProtocol() {
return protocol;
}
@SuppressWarnings("unchecked")
private Class<? extends Applet> requireExtendsApplet(Class<?> aClass) {
if (!Applet.class.isAssignableFrom(aClass)) {
throw new SystemException(SystemException.ILLEGAL_VALUE);
}
return (Class<? extends Applet>) aClass;
}
class AppletClassLoader extends URLClassLoader {
AppletClassLoader(URL[] urls) {
super(urls, Simulator.class.getClassLoader());
}
void addAppletContents(byte[] appletJarContents) throws IOException {
File downloadedAppletJar = File.createTempFile("applet", "contents");
downloadedAppletJar.deleteOnExit();
FileOutputStream fos = new FileOutputStream(downloadedAppletJar);
fos.write(appletJarContents);
fos.close();
addURL(downloadedAppletJar.toURI().toURL());
}
}
}