/*
* Copyright 2015 Licel Corporation.
*
* 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 java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import static org.objectweb.asm.Opcodes.ASM4;
import org.objectweb.asm.commons.RemappingClassAdapter;
import org.objectweb.asm.commons.SimpleRemapper;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
/**
* Injects jCardSim’s code into Java Card Api Reference Classes
*/
public class JavaCardApiProcessor {
public static void main(String args[]) throws Exception {
File buildDir = new File(args[0]);
if (!buildDir.exists() || !buildDir.isDirectory()) {
throw new RuntimeException("Invalid directory: " + buildDir);
}
HashMap<String, String> allMap = new HashMap();
proxyClass(buildDir, "com.licel.jcardsim.framework.AIDProxy", "javacard.framework.AID", false);
allMap.put("com.licel.jcardsim.framework.APDUProxy".replace(".", "/"), "javacard.framework.APDU".replace(".", "/"));
proxyClass(buildDir, "com.licel.jcardsim.framework.APDUProxy", "javacard.framework.APDU", false);
copyClass(buildDir, "com.licel.jcardsim.framework.APDUProxy$1", "javacard.framework.APDU$1", allMap);
proxyExceptionClass(buildDir, "javacard.framework.APDUException");
proxyClass(buildDir, "com.licel.jcardsim.framework.AppletProxy", "javacard.framework.Applet", false);
proxyClass(buildDir, "com.licel.jcardsim.framework.CardExceptionProxy", "javacard.framework.CardException", false);
proxyClass(buildDir, "com.licel.jcardsim.framework.CardRuntimeExceptionProxy", "javacard.framework.CardRuntimeException", false);
proxyExceptionClass(buildDir, "javacard.framework.ISOException");
proxyClass(buildDir, "com.licel.jcardsim.framework.JCSystemProxy", "javacard.framework.JCSystem", false);
proxyExceptionClass(buildDir, "javacard.framework.PINException");
proxyExceptionClass(buildDir, "javacard.framework.SystemException");
proxyExceptionClass(buildDir, "javacard.framework.TransactionException");
proxyExceptionClass(buildDir, "javacard.framework.UserException");
proxyClass(buildDir, "com.licel.jcardsim.framework.UtilProxy", "javacard.framework.Util", false);
proxyClass(buildDir, "com.licel.jcardsim.framework.OwnerPINProxy", "javacard.framework.OwnerPIN", false);
proxyClass(buildDir, "com.licel.jcardsim.crypto.ChecksumProxy", "javacard.security.Checksum", true);
proxyClass(buildDir, "com.licel.jcardsim.crypto.CipherProxy", "javacardx.crypto.Cipher", true);
proxyClass(buildDir, "com.licel.jcardsim.crypto.KeyAgreementProxy", "javacard.security.KeyAgreement", true);
proxyClass(buildDir, "com.licel.jcardsim.crypto.KeyPairProxy", "javacard.security.KeyPair", false);
proxyClass(buildDir, "com.licel.jcardsim.crypto.KeyBuilderProxy", "javacard.security.KeyBuilder", true);
proxyClass(buildDir, "com.licel.jcardsim.crypto.MessageDigestProxy", "javacard.security.MessageDigest", true);
proxyClass(buildDir, "com.licel.jcardsim.crypto.RandomDataProxy", "javacard.security.RandomData", true);
proxyClass(buildDir, "com.licel.jcardsim.crypto.SignatureProxy", "javacard.security.Signature", true);
proxyExceptionClass(buildDir, "javacard.framework.service.ServiceException");
proxyExceptionClass(buildDir, "javacard.security.CryptoException");
}
public static void proxyClass(File buildDir, String proxyClassFile, String targetClassFile, boolean skipConstructor) throws IOException {
File proxyFile = new File(buildDir, proxyClassFile.replace(".", File.separator) + ".class");
FileInputStream fProxyClass = new FileInputStream(proxyFile);
FileInputStream fTargetClass = new FileInputStream(new File(buildDir, targetClassFile.replace(".", File.separator) + ".class"));
ClassReader crProxy = new ClassReader(fProxyClass);
ClassNode cnProxy = new ClassNode();
crProxy.accept(cnProxy, 0);
ClassReader crTarget = new ClassReader(fTargetClass);
ClassNode cnTarget = new ClassNode();
crTarget.accept(cnTarget, 0);
ClassNode cnProxyRemapped = new ClassNode();
HashMap<String, String> map = new HashMap();
map.put(cnProxy.name, cnTarget.name);
// inner classes
for (int i = 0; i < 10; i++) {
map.put(cnProxy.name + "$1", cnTarget.name + "$1");
}
RemappingClassAdapter ra = new RemappingClassAdapter(cnProxyRemapped, new SimpleRemapper(map));
cnProxy.accept(ra);
ClassWriter cw = new ClassWriter(crTarget, 0);
MergeAdapter ma = new MergeAdapter(cw, cnProxyRemapped, skipConstructor);
cnTarget.accept(ma);
fProxyClass.close();
fTargetClass.close();
FileOutputStream fos = new FileOutputStream(new File(buildDir, targetClassFile.replace(".", File.separator) + ".class"));
fos.write(cw.toByteArray());
fos.close();
// remove proxy class
proxyFile.delete();
}
public static void copyClass(File buildDir, String proxyClassFile, String targetClassName, Map map) throws IOException {
File sourceFile = new File(buildDir, proxyClassFile.replace(".", File.separator) + ".class");
FileInputStream fProxyClass = new FileInputStream(sourceFile);
ClassReader crProxy = new ClassReader(fProxyClass);
ClassNode cnProxy = new ClassNode();
crProxy.accept(cnProxy, 0);
ClassWriter cw = new ClassWriter(0);
map.put(cnProxy.name, targetClassName.replace(".", "/"));
RemappingClassAdapter ra = new RemappingClassAdapter(cw, new SimpleRemapper(map));
cnProxy.accept(ra);
fProxyClass.close();
FileOutputStream fos = new FileOutputStream(new File(buildDir, targetClassName.replace(".", File.separator) + ".class"));
fos.write(cw.toByteArray());
fos.close();
// remove source class
sourceFile.delete();
}
public static void proxyExceptionClass(File buildDir, String targetClassName) throws IOException {
FileInputStream fTargetClass = new FileInputStream(new File(buildDir, targetClassName.replace(".", File.separator) + ".class"));
ClassReader crTarget = new ClassReader(fTargetClass);
ClassNode cnTarget = new ClassNode();
crTarget.accept(cnTarget, 0);
ClassWriter cw = new ClassWriter(0);
ExceptionClassProxy ecc = new ExceptionClassProxy(cw, cnTarget.version, cnTarget.name, cnTarget.superName);
cnTarget.accept(ecc);
fTargetClass.close();
FileOutputStream fos = new FileOutputStream(new File(buildDir, targetClassName.replace(".", File.separator) + ".class"));
fos.write(cw.toByteArray());
fos.close();
}
static class ExceptionClassProxy extends ClassVisitor implements Opcodes {
String superClassName;
String className;
public ExceptionClassProxy(ClassWriter cv, int classVersion, String exceptionClassName, String superClassName) {
super(ASM4, cv);
this.superClassName = superClassName;
this.className = exceptionClassName;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return null;
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
// skip jc 2.2.2 api impl
if ((access & ACC_PUBLIC) != ACC_PUBLIC) {
return null;
}
return super.visitField(access, name, desc, signature, value);
}
@Override
public void visitEnd() {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "(S)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKESPECIAL, superClassName, "<init>", "(S)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
mv = cv.visitMethod(ACC_PUBLIC + ACC_STATIC, "throwIt", "(S)V", null, null);
mv.visitCode();
mv.visitTypeInsn(NEW, className);
mv.visitInsn(DUP);
mv.visitVarInsn(ILOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "(S)V", false);
mv.visitInsn(ATHROW);
mv.visitMaxs(3, 1);
mv.visitEnd();
}
}
static class ClassAdapter extends ClassNode implements Opcodes {
public ClassAdapter(ClassVisitor cv) {
super(ASM4);
this.cv = cv;
}
@Override
public void visitEnd() {
accept(cv);
}
}
static class MergeAdapter extends ClassAdapter {
private ClassNode cn;
private String cname;
private HashMap<String, MethodNode> cnMethods = new HashMap();
private HashMap<String, FieldNode> cnFields = new HashMap();
private boolean skipConstructor;
public MergeAdapter(ClassVisitor cv,
ClassNode cn, boolean skipConstructor) {
super(cv);
this.cn = cn;
this.skipConstructor = skipConstructor;
for (Iterator it = cn.methods.iterator();
it.hasNext();) {
MethodNode mn = (MethodNode) it.next();
if (skipConstructor && mn.name.equals("<init>")) {
continue;
}
cnMethods.put(mn.name + mn.desc, mn);
}
for (Iterator it = cn.fields.iterator();
it.hasNext();) {
FieldNode fn = (FieldNode) it.next();
cnFields.put(fn.name + fn.desc, fn);
}
}
@Override
public void visit(int version, int access,
String name, String signature,
String superName, String[] interfaces) {
super.visit(version, access, name,
signature, superName, interfaces);
this.cname = name;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
// skip jc 2.2.2 api impl
if (cnMethods.containsKey(name + desc) || ((access & (ACC_PUBLIC | ACC_PROTECTED)) == 0)) {
System.out.println("skip method: " + cname + name + desc);
return null;
}
System.out.println("Use original:" + cname + name + desc);
return super.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
// skip jc 2.2.2 api impl
if ((access & ACC_PUBLIC) != ACC_PUBLIC) {
System.out.println("skip field: " + cname + name + desc);
return null;
}
return super.visitField(access, name, desc, signature, value);
}
@Override
public void visitEnd() {
for (Iterator it = cn.fields.iterator();
it.hasNext();) {
FieldNode fn = (FieldNode) it.next();
cv.visitField(fn.access, fn.name, fn.desc, fn.signature, fn.value);
}
for (Iterator it = cn.methods.iterator();
it.hasNext();) {
MethodNode mn = (MethodNode) it.next();
if (skipConstructor && mn.name.equals("<init>")) {
continue;
}
String[] exceptions
= new String[mn.exceptions.size()];
mn.exceptions.toArray(exceptions);
MethodVisitor mv
= cv.visitMethod(
mn.access, mn.name, mn.desc,
mn.signature, exceptions);
mn.instructions.resetLabels();
mn.accept(mv);
}
super.visitEnd();
}
}
}