From 3a37cded205d2894ebf8f6bd144aee4d1decdf33 Mon Sep 17 00:00:00 2001 From: shaozhuguang Date: Wed, 17 Jul 2019 17:56:30 +0800 Subject: [PATCH] Modify contract-maven-plugin's verification scheme to be implemented by ASM! --- source/contract/contract-maven-plugin/pom.xml | 10 +- .../contract/maven/AbstractContract.java | 98 +++++ .../contract/maven/ContractCheckMojo.java | 216 ---------- .../blockchain/contract/maven/ContractClass.java | 66 +++ .../contract/maven/ContractCompileMojo.java | 150 ++++++- .../contract/maven/ContractConstant.java | 9 + .../contract/maven/ContractDeployExeUtil.java | 176 -------- .../contract/maven/ContractDeployMojo.java | 120 ------ .../blockchain/contract/maven/ContractField.java | 43 ++ .../blockchain/contract/maven/ContractMethod.java | 81 ++++ .../contract/maven/ContractResolveEngine.java | 459 --------------------- .../contract/maven/asm/ASMClassVisitor.java | 22 + .../contract/maven/asm/ASMMethodVisitor.java | 108 +++++ .../blockchain/contract/maven/rule/BlackList.java | 154 +++++++ .../contract/maven/rule/DependencyExclude.java | 93 +++++ .../blockchain/contract/maven/rule/WhiteList.java | 30 ++ .../contract/maven/verify/ResolveEngine.java | 133 ++++++ .../contract/maven/verify/VerifyEngine.java | 216 ++++++++++ .../src/main/resources/blacks.conf | 11 + .../src/main/resources/config.properties | 8 - .../src/main/resources/provideds.conf | 22 + .../src/main/resources/whites.conf | 1 + .../jd/blockchain/contract/ContractJarUtils.java | 89 +++- 23 files changed, 1321 insertions(+), 994 deletions(-) create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/AbstractContract.java delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractClass.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractConstant.java delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractField.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractMethod.java delete mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMClassVisitor.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMMethodVisitor.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/WhiteList.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java create mode 100644 source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java create mode 100644 source/contract/contract-maven-plugin/src/main/resources/blacks.conf delete mode 100644 source/contract/contract-maven-plugin/src/main/resources/config.properties create mode 100644 source/contract/contract-maven-plugin/src/main/resources/provideds.conf create mode 100644 source/contract/contract-maven-plugin/src/main/resources/whites.conf diff --git a/source/contract/contract-maven-plugin/pom.xml b/source/contract/contract-maven-plugin/pom.xml index 6cca1389..9596695e 100644 --- a/source/contract/contract-maven-plugin/pom.xml +++ b/source/contract/contract-maven-plugin/pom.xml @@ -35,10 +35,6 @@ ${project.version} - - com.github.javaparser - javaparser-core - org.apache.maven @@ -58,6 +54,12 @@ 2.6 + + org.ow2.asm + asm + 5.0.4 + + diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/AbstractContract.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/AbstractContract.java new file mode 100644 index 00000000..389dd33a --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/AbstractContract.java @@ -0,0 +1,98 @@ +package com.jd.blockchain.contract.maven; + +import com.jd.blockchain.contract.maven.rule.BlackList; +import com.jd.blockchain.contract.maven.rule.WhiteList; + +import java.util.List; + +public abstract class AbstractContract { + + protected String className; + + public String getClassName() { + return className; + } + + public String getDotClassName() { + return className.replaceAll("/", "."); + } + + protected String format(final String inputFormat) { + String formatResult; + + String outputFormat = inputFormat; + if (inputFormat.endsWith(";")) { + outputFormat = inputFormat.substring(0, inputFormat.length() - 1); + } + if (outputFormat.startsWith("[L") && outputFormat.length() > 2) { + // 说明是数组,但不显示 + formatResult = outputFormat.substring(2); + } else if (outputFormat.startsWith("[") && outputFormat.length() > 1) { + // 说明是数组 + formatResult = outputFormat.substring(1); + } else if (outputFormat.startsWith("L") && outputFormat.length() > 1) { + // 说明是非基础类型 + formatResult = outputFormat.substring(1); + } else { + formatResult = outputFormat; + } + + return formatResult; + } + + public static BlackList initBlack(List blackList) { + BlackList contractBlack = new BlackList(); + if (blackList != null && !blackList.isEmpty()) { + for (String black : blackList) { + // 首先判断该black是package还是 + String packageName = isPackageAndReturn(black); + if (packageName != null) { + // 说明是包 + contractBlack.addBlackPackage(packageName); + } else { + String[] classAndMethod = black.split("-"); + if (classAndMethod.length == 1) { + // 说明只有ClassName + contractBlack.addBlack(classAndMethod[0], BlackList.COMMON_METHOD); + } else { + contractBlack.addBlack(classAndMethod[0], classAndMethod[1]); + } + } + } + } + + return contractBlack; + } + + public static WhiteList initWhite(List whiteList) { + WhiteList contractWhite = new WhiteList(); + + if (whiteList != null && !whiteList.isEmpty()) { + for (String white : whiteList) { + String packageName = isPackageAndReturn(white); + if (packageName != null) { + // 说明是包 + contractWhite.addWhite(packageName); + } else { + contractWhite.addWhite(white); + } + } + } + + return contractWhite; + } + + /** + * 获取配置的packageName + * + * @param config + * @return + * 假设为包,则返回其包名,否则返回NULL + */ + public static String isPackageAndReturn(String config) { + if (config.endsWith("*")) { + return config.substring(0, config.length() - 2); + } + return null; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java deleted file mode 100644 index 81e5f272..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java +++ /dev/null @@ -1,216 +0,0 @@ -//package com.jd.blockchain.contract.maven; -// -//import org.apache.commons.io.FileUtils; -//import org.apache.maven.model.Model; -//import org.apache.maven.model.Plugin; -//import org.apache.maven.model.PluginExecution; -//import org.apache.maven.model.io.xpp3.MavenXpp3Reader; -//import org.apache.maven.model.io.xpp3.MavenXpp3Writer; -//import org.apache.maven.plugin.AbstractMojo; -//import org.apache.maven.plugin.MojoFailureException; -//import org.apache.maven.plugins.annotations.Mojo; -//import org.apache.maven.plugins.annotations.Parameter; -//import org.apache.maven.project.MavenProject; -//import org.apache.maven.shared.invoker.*; -//import org.codehaus.plexus.util.xml.Xpp3Dom; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.io.ByteArrayOutputStream; -//import java.io.File; -//import java.io.FileInputStream; -//import java.io.IOException; -//import java.util.ArrayList; -//import java.util.Collections; -//import java.util.List; -// -// -//@Mojo(name = "contractCheck") -//public class ContractCheckMojo extends AbstractMojo { -// -// Logger LOG = LoggerFactory.getLogger(ContractCheckMojo.class); -// -// public static final String CONTRACT_VERIFY = "contractVerify"; -// -// private static final String CONTRACT_MAVEN_PLUGIN = "contract-maven-plugin"; -// -// private static final String MAVEN_ASSEMBLY_PLUGIN = "maven-assembly-plugin"; -// -// private static final String JDCHAIN_PACKAGE = "com.jd.blockchain"; -// -// private static final String APACHE_MAVEN_PLUGINS = "org.apache.maven.plugins"; -// -// private static final String GOALS_VERIFY = "package"; -// -// private static final String GOALS_PACKAGE = "package"; -// -// private static final String OUT_POM_XML = "ContractPom.xml"; -// -// @Parameter(defaultValue = "${project}", required = true, readonly = true) -// private MavenProject project; -// -// /** -// * jar's name; -// */ -// @Parameter -// private String finalName; -// -// /** -// * mainClass; -// */ -// @Parameter -// private String mainClass; -// /** -// * ledgerVersion; -// */ -// @Parameter -// private String ledgerVersion; -// -// /** -// * mvnHome; -// */ -// @Parameter -// private String mvnHome; -// -// /** -// * first compile the class, then parse it; -// */ -// @Override -// public void execute() throws MojoFailureException { -// compileFiles(); -// } -// -// private void compileFiles() throws MojoFailureException { -// try (FileInputStream fis = new FileInputStream(project.getFile())) { -// -// MavenXpp3Reader reader = new MavenXpp3Reader(); -// Model model = reader.read(fis); -// -// //delete this plugin(contractCheck) from destination pom.xml;then add the proper plugins; -// Plugin plugin = model.getBuild().getPluginsAsMap() -// .get(JDCHAIN_PACKAGE + ":" + CONTRACT_MAVEN_PLUGIN); -// if(plugin == null){ -// plugin = model.getBuild().getPluginsAsMap() -// .get(APACHE_MAVEN_PLUGINS + ":" + CONTRACT_MAVEN_PLUGIN); -// } -// -// if(plugin == null) { -// return; -// } -// -// model.getBuild().removePlugin(plugin); -// -// List plugins = new ArrayList<>(); -// plugins.add(createAssembly()); -// plugins.add(createContractVerify()); -// -// model.getBuild().setPlugins(plugins); -// -// handle(model); -// -// } catch (Exception e) { -// LOG.error(e.getMessage()); -// throw new MojoFailureException(e.getMessage()); -// } -// } -// -// private void invokeCompile(File file) { -// InvocationRequest request = new DefaultInvocationRequest(); -// -// Invoker invoker = new DefaultInvoker(); -// try { -// request.setPomFile(file); -// -// request.setGoals(Collections.singletonList(GOALS_VERIFY)); -// invoker.setMavenHome(new File(mvnHome)); -// invoker.execute(request); -// } catch (MavenInvocationException e) { -// LOG.error(e.getMessage()); -// throw new IllegalStateException(e); -// } -// } -// -// private Plugin createContractVerify() { -// Plugin plugin = new Plugin(); -// plugin.setGroupId(JDCHAIN_PACKAGE); -// plugin.setArtifactId(CONTRACT_MAVEN_PLUGIN); -// plugin.setVersion(ledgerVersion); -// -// Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); -// finalNameNode.setValue(finalName); -// Xpp3Dom configuration = new Xpp3Dom("configuration"); -// configuration.addChild(finalNameNode); -// -// plugin.setConfiguration(configuration); -// plugin.setExecutions(pluginExecution("make-assembly", GOALS_VERIFY, CONTRACT_VERIFY)); -// -// return plugin; -// } -// -// private Plugin createAssembly() { -// Plugin plugin = new Plugin(); -// plugin.setArtifactId(MAVEN_ASSEMBLY_PLUGIN); -// -// Xpp3Dom configuration = new Xpp3Dom("configuration"); -// -// Xpp3Dom mainClassNode = new Xpp3Dom("mainClass"); -// mainClassNode.setValue(mainClass); -// -// Xpp3Dom manifest = new Xpp3Dom("manifest"); -// manifest.addChild(mainClassNode); -// -// Xpp3Dom archive = new Xpp3Dom("archive"); -// archive.addChild(manifest); -// -// Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); -// finalNameNode.setValue(finalName); -// -// Xpp3Dom appendAssemblyId = new Xpp3Dom("appendAssemblyId"); -// appendAssemblyId.setValue("false"); -// -// Xpp3Dom descriptorRef = new Xpp3Dom("descriptorRef"); -// descriptorRef.setValue("jar-with-dependencies"); -// Xpp3Dom descriptorRefs = new Xpp3Dom("descriptorRefs"); -// descriptorRefs.addChild(descriptorRef); -// -// configuration.addChild(finalNameNode); -// configuration.addChild(appendAssemblyId); -// configuration.addChild(archive); -// configuration.addChild(descriptorRefs); -// -// plugin.setConfiguration(configuration); -// plugin.setExecutions(pluginExecution("make-assembly", GOALS_PACKAGE, "single")); -// -// return plugin; -// } -// -// private List pluginExecution(String id, String phase, String goal) { -// PluginExecution pluginExecution = new PluginExecution(); -// pluginExecution.setId(id); -// pluginExecution.setPhase(phase); -// List goals = new ArrayList<>(); -// goals.add(goal); -// pluginExecution.setGoals(goals); -// List pluginExecutions = new ArrayList<>(); -// pluginExecutions.add(pluginExecution); -// -// return pluginExecutions; -// } -// -// private void handle(Model model) throws IOException { -// -// MavenXpp3Writer mavenXpp3Writer = new MavenXpp3Writer(); -// -// ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); -// -// mavenXpp3Writer.write(outputStream, model); -// -// byte[] buffer = outputStream.toByteArray(); -// -// File outPom = new File(project.getBasedir().getPath(), OUT_POM_XML); -// -// FileUtils.writeByteArrayToFile(outPom, buffer); -// -// invokeCompile(outPom); -// } -//} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractClass.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractClass.java new file mode 100644 index 00000000..b9d3de7b --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractClass.java @@ -0,0 +1,66 @@ +package com.jd.blockchain.contract.maven; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class ContractClass extends AbstractContract { + + // 若出现同名的方法则进行合并(将两个方法中涉及到的内容合并在一起) + private Map methods = new ConcurrentHashMap<>(); + + public ContractClass(String className) { + if (className.contains(".")) { + this.className = className.replaceAll("\\.", "/"); + } else { + this.className = className; + } + } + + /** + * 返回构造方法 + * + * @return + */ + public ContractMethod constructor() { + return methods.get(ContractConstant.METHOD_INIT); + } + + /** + * 返回该类的所有变量 + * + * @return + */ + public List fields() { + + List fields = new ArrayList<>(); + + // 构造方法 + ContractMethod initMethod = constructor(); + if (initMethod != null) { + fields.addAll(initMethod.getClassFieldList(className)); + } + // CLINIT方法 + ContractMethod clInitMethod = methods.get(ContractConstant.METHOD_CLINIT); + if (clInitMethod != null) { + fields.addAll(clInitMethod.getClassFieldList(className)); + } + return fields; + } + + public synchronized ContractMethod method(String methodName) { + if (methods.containsKey(methodName)) { + return methods.get(methodName); + } + ContractMethod method = new ContractMethod(this.className, methodName); + + methods.put(methodName, method); + + return method; + } + + public Map getMethods() { + return methods; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java index 61a3dfff..3ddf15a0 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCompileMojo.java @@ -1,20 +1,94 @@ package com.jd.blockchain.contract.maven; +import com.jd.blockchain.contract.ContractJarUtils; +import com.jd.blockchain.contract.maven.rule.BlackList; +import com.jd.blockchain.contract.maven.rule.WhiteList; +import com.jd.blockchain.contract.maven.rule.DependencyExclude; +import com.jd.blockchain.contract.maven.verify.ResolveEngine; +import com.jd.blockchain.contract.maven.verify.VerifyEngine; +import org.apache.commons.io.FileUtils; +import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.assembly.mojos.SingleAssemblyMojo; import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.project.MavenProject; + +import java.io.File; +import java.io.IOException; +import java.util.Set; @Mojo(name = "compile") public class ContractCompileMojo extends SingleAssemblyMojo { public static final String JAR_DEPENDENCE = "jar-with-dependencies"; + public static final String SCOPE_PROVIDED = "provided"; + + public static final String SCOPE_COMPILE = "compile"; + + private DependencyExclude dependencyExclude = new DependencyExclude(); + + private static BlackList black; + + private static WhiteList white; + + static { + init(); + } + @Override public void execute() throws MojoExecutionException, MojoFailureException { + // 首先对MainClass进行校验,要求必须有MainClass + String mainClass = mainClassVerify(); + + // 将JDChain本身代码之外的代码移除(不打包进整个Jar) + handleArtifactExclude(super.getProject().getDependencyArtifacts()); + + // 此参数用于设置将所有第三方依赖的Jar包打散为.class,与主代码打包在一起,生成一个jar包 + super.setDescriptorRefs(new String[]{JAR_DEPENDENCE}); + + // 执行打包命令 + super.execute(); + + // 将本次打包好的文件重新命名,以便于后续重新打包需要 + // 把文件改名,然后重新再生成一个文件 + File dstFile; + try { + dstFile = rename(getProject(), getFinalName()); + } catch (IOException e) { + getLog().error(e); + throw new MojoFailureException(e.getMessage()); + } + + // 首先校验该类的Jar包中是否包含不符合规范的命名,以及该类的代码中的部分解析 + File finalJarFile = verify(dstFile, mainClass); + + // 将所有的依赖的jar包全部打包进一个包中,以便于进行ASM检查 + handleArtifactCompile(super.getProject().getDependencyArtifacts()); + + // 然后再打包一次,本次打包完成后,其中的代码包含所有的class(JDK自身的除外) + super.execute(); + + // 对代码中的一些规则进行校验,主要是校验其是否包含一些不允许使用的类、包、方法等 + verify(mainClass); + + // 删除中间的一些文件 + try { + FileUtils.forceDelete(dstFile); + } catch (IOException e) { + throw new MojoFailureException(e.getMessage()); + } + + // 若执行到此处没有异常则表明打包成功,打印打包成功消息 + getLog().info(String.format("JDChain's Contract compile success, path = %s !", finalJarFile.getPath())); + } + + private String mainClassVerify() throws MojoFailureException { // 要求必须有MainClass + String mainClass; try { - String mainClass = super.getJarArchiveConfiguration().getManifest().getMainClass(); + mainClass = super.getJarArchiveConfiguration().getManifest().getMainClass(); // 校验MainClass,要求MainClass必须不能为空 if (mainClass == null || mainClass.length() == 0) { throw new MojoFailureException("MainClass is NULL !!!"); @@ -23,16 +97,76 @@ public class ContractCompileMojo extends SingleAssemblyMojo { } catch (Exception e) { throw new MojoFailureException("MainClass is null: " + e.getMessage(), e ); } + return mainClass; + } - // 此参数用于设置将所有第三方依赖的Jar包打散为.class,与主代码打包在一起,生成一个jar包 - super.setDescriptorRefs(new String[]{JAR_DEPENDENCE}); + private void handleArtifactExclude(Set artifacts) { + for (Artifact artifact : artifacts) { + String groupId = artifact.getGroupId(), artifactId = artifact.getArtifactId(); + if (dependencyExclude.isExclude(groupId, artifactId)) { + getLog().info(String.format("GroupId[%s] ArtifactId[%s] belongs to DependencyExclude !!!", groupId, artifactId)); + // 属于排除的名单之中 + artifact.setScope(SCOPE_PROVIDED); + } + } + } - // 执行打包命令 - super.execute(); + private void handleArtifactCompile(Set artifacts) { + for (Artifact artifact : artifacts) { + if (artifact.getScope().equals(SCOPE_PROVIDED)) { + // 将所有的provided设置为compile,以便于后续编译 + artifact.setScope(SCOPE_COMPILE); + } + } + } + + private File rename(MavenProject project, String finalName) throws IOException { + String srcJarPath = jarPath(project, finalName); + String dstJarPath = project.getBuild().getDirectory() + + File.separator + finalName + ".jar"; + File dstFile = new File(dstJarPath); + FileUtils.copyFile(new File(srcJarPath), dstFile); + FileUtils.forceDelete(new File(srcJarPath)); + return dstFile; + } + + private String jarPath(MavenProject project, String finalName) { + return project.getBuild().getDirectory() + + File.separator + finalName + "-" + JAR_DEPENDENCE + ".jar"; + } + + private void verify(String mainClass) throws MojoFailureException { + try { - ContractResolveEngine engine = new ContractResolveEngine(getLog(), getProject(), getFinalName()); + File jarFile = new File(jarPath(getProject(), getFinalName())); - // 打包并进行校验 - engine.compileAndVerify(); + VerifyEngine verifyEngine = new VerifyEngine(getLog(), jarFile, mainClass, black, white); + + verifyEngine.verify(); + + // 校验完成后将该jar包删除 + FileUtils.forceDelete(jarFile); + + } catch (Exception e) { + getLog().error(e); + throw new MojoFailureException(e.getMessage()); + } + } + + private File verify(File jarFile, String mainClass) throws MojoFailureException { + + ResolveEngine resolveEngine = new ResolveEngine(getLog(), jarFile, mainClass); + + return resolveEngine.verify(); + + } + + private static void init() { + try { + black = AbstractContract.initBlack(ContractJarUtils.loadBlackConf()); + white = AbstractContract.initWhite(ContractJarUtils.loadWhiteConf()); + } catch (Exception e) { + throw new IllegalStateException(e); + } } } diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractConstant.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractConstant.java new file mode 100644 index 00000000..a94fb624 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractConstant.java @@ -0,0 +1,9 @@ +package com.jd.blockchain.contract.maven; + +public class ContractConstant { + + public static final String METHOD_INIT = ""; + + public static final String METHOD_CLINIT = ""; + +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java deleted file mode 100644 index bd6a1b23..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java +++ /dev/null @@ -1,176 +0,0 @@ -//package com.jd.blockchain.contract.maven; -// -//import com.jd.blockchain.binaryproto.DataContractRegistry; -//import com.jd.blockchain.crypto.HashDigest; -//import com.jd.blockchain.crypto.PrivKey; -//import com.jd.blockchain.crypto.PubKey; -//import com.jd.blockchain.ledger.*; -//import com.jd.blockchain.sdk.BlockchainService; -//import com.jd.blockchain.sdk.client.GatewayServiceFactory; -//import com.jd.blockchain.tools.keygen.KeyGenCommand; -//import com.jd.blockchain.utils.Bytes; -//import com.jd.blockchain.utils.codec.Base58Utils; -//import com.jd.blockchain.utils.net.NetworkAddress; -// -//import java.io.File; -//import java.io.FileInputStream; -//import java.io.IOException; -//import java.io.InputStream; -// -///** -// * @Author zhaogw -// * @Date 2018/11/2 10:18 -// */ -//public enum ContractDeployExeUtil { -// instance; -// private BlockchainService bcsrv; -// private Bytes contractAddress; -// -// public BlockchainKeypair getKeyPair(String pubPath, String prvPath, String rawPassword){ -// PubKey pub = null; -// PrivKey prv = null; -// try { -// prv = KeyGenCommand.readPrivKey(prvPath, KeyGenCommand.encodePassword(rawPassword)); -// pub = KeyGenCommand.readPubKey(pubPath); -// -// } catch (Exception e) { -// e.printStackTrace(); -// } -// -// return new BlockchainKeypair(pub, prv); -// } -// -// public PubKey getPubKey(String pubPath){ -// PubKey pub = null; -// try { -// if(pubPath == null){ -// BlockchainKeypair contractKeyPair = BlockchainKeyGenerator.getInstance().generate(); -// pub = contractKeyPair.getPubKey(); -// }else { -// pub = KeyGenCommand.readPubKey(pubPath); -// } -// -// } catch (Exception e) { -// e.printStackTrace(); -// } -// -// return pub; -// } -// public byte[] getChainCode(String path){ -// byte[] chainCode = null; -// File file = null; -// InputStream input = null; -// try { -// file = new File(path); -// input = new FileInputStream(file); -// chainCode = new byte[input.available()]; -// input.read(chainCode); -// -// } catch (IOException e) { -// e.printStackTrace(); -// } finally { -// try { -// if(input!=null){ -// input.close(); -// } -// } catch (IOException e) { -// e.printStackTrace(); -// } -// } -// return chainCode; -// } -// -// private void register(){ -// DataContractRegistry.register(TransactionContent.class); -// DataContractRegistry.register(TransactionContentBody.class); -// DataContractRegistry.register(TransactionRequest.class); -// DataContractRegistry.register(NodeRequest.class); -// DataContractRegistry.register(EndpointRequest.class); -// DataContractRegistry.register(TransactionResponse.class); -// DataContractRegistry.register(DataAccountKVSetOperation.class); -// DataContractRegistry.register(DataAccountKVSetOperation.KVWriteEntry.class); -// DataContractRegistry.register(Operation.class); -// DataContractRegistry.register(ContractCodeDeployOperation.class); -// DataContractRegistry.register(ContractEventSendOperation.class); -// DataContractRegistry.register(DataAccountRegisterOperation.class); -// DataContractRegistry.register(UserRegisterOperation.class); -// } -// -// public BlockchainService initBcsrv(String host, int port) { -// if(bcsrv!=null){ -// return bcsrv; -// } -// NetworkAddress addr = new NetworkAddress(host, port); -// GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(addr); -// bcsrv = gwsrvFact.getBlockchainService(); -// return bcsrv; -// } -// -// public boolean deploy(HashDigest ledgerHash, BlockchainIdentity contractIdentity, BlockchainKeypair ownerKey, byte[] chainCode){ -// register(); -// -// TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); -// txTpl.contracts().deploy(contractIdentity, chainCode); -// PreparedTransaction ptx = txTpl.prepare(); -// ptx.sign(ownerKey); -// // 提交并等待共识返回; -// TransactionResponse txResp = ptx.commit(); -// -// // 验证结果; -// contractAddress = contractIdentity.getAddress(); -// this.setContractAddress(contractAddress); -// System.out.println("contract's address="+contractAddress); -// return txResp.isSuccess(); -// } -// public boolean deploy(String host, int port, HashDigest ledgerHash, BlockchainKeypair ownerKey, byte[] chainCode){ -// register(); -// -// BlockchainIdentity contractIdentity = BlockchainKeyGenerator.getInstance().generate().getIdentity(); -// initBcsrv(host,port); -// return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); -// } -// -// // 根据用户指定的公钥生成合约地址 -// public boolean deploy(String host, int port, String ledger,String ownerPubPath, String ownerPrvPath, -// String ownerPassword, String chainCodePath,String pubPath){ -// PubKey pubKey = getPubKey(pubPath); -// BlockchainIdentity contractIdentity = new BlockchainIdentityData(pubKey); -// byte[] chainCode = getChainCode(chainCodePath); -// -// BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); -// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); -// initBcsrv(host,port); -// return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); -// } -// -// -//// 暂不支持从插件执行合约;此外,由于合约参数调用的格式发生变化,故此方法被废弃;by: huanghaiquan at 2019-04-30; -// -//// public boolean exeContract(String ledger,String ownerPubPath, String ownerPrvPath, -//// String ownerPassword,String event,String contractArgs){ -//// BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); -//// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); -//// -//// // 定义交易,传输最简单的数字、字符串、提取合约中的地址; -//// TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); -//// txTpl.contractEvents().send(getContractAddress(),event,contractArgs.getBytes()); -//// -//// // 签名; -//// PreparedTransaction ptx = txTpl.prepare(); -//// ptx.sign(ownerKey); -//// -//// // 提交并等待共识返回; -//// TransactionResponse txResp = ptx.commit(); -//// -//// // 验证结果; -//// return txResp.isSuccess(); -//// } -// -// public Bytes getContractAddress() { -// return contractAddress; -// } -// -// public void setContractAddress(Bytes contractAddress) { -// this.contractAddress = contractAddress; -// } -//} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java deleted file mode 100644 index 89a82a36..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java +++ /dev/null @@ -1,120 +0,0 @@ -//package com.jd.blockchain.contract.maven; -// -//import com.jd.blockchain.crypto.HashDigest; -//import com.jd.blockchain.crypto.PrivKey; -//import com.jd.blockchain.crypto.PubKey; -//import com.jd.blockchain.ledger.BlockchainKeypair; -//import com.jd.blockchain.tools.keygen.KeyGenCommand; -//import com.jd.blockchain.utils.StringUtils; -//import com.jd.blockchain.utils.codec.Base58Utils; -//import com.jd.blockchain.utils.io.FileUtils; -//import org.apache.maven.plugin.AbstractMojo; -//import org.apache.maven.plugin.MojoFailureException; -//import org.apache.maven.plugins.annotations.Mojo; -//import org.apache.maven.plugins.annotations.Parameter; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.io.File; -//import java.io.FileInputStream; -//import java.io.IOException; -//import java.io.InputStream; -//import java.util.Properties; -// -///** -// * for contract remote deploy; -// * @goal contractDeploy -// * @phase process-sources -// * @Author zhaogw -// * @Date 2018/10/18 10:12 -// */ -// -//@Mojo(name = "deploy") -//public class ContractDeployMojo extends AbstractMojo { -// Logger logger = LoggerFactory.getLogger(ContractDeployMojo.class); -// -// @Parameter -// private File config; -// -// @Override -// public void execute()throws MojoFailureException { -// -// Properties prop = new Properties(); -// InputStream input = null; -// -// try { -// input = new FileInputStream(config); -// prop.load(input); -// -// } catch (IOException ex) { -// logger.error(ex.getMessage()); -// throw new MojoFailureException("io error"); -// } finally { -// if (input != null) { -// try { -// input.close(); -// } catch (IOException e) { -// logger.error(e.getMessage()); -// } -// } -// } -// int port; -// try { -// port = Integer.parseInt(prop.getProperty("port")); -// }catch (NumberFormatException e){ -// logger.error(e.getMessage()); -// throw new MojoFailureException("invalid port"); -// } -// String host = prop.getProperty("host"); -// String ledger = prop.getProperty("ledger"); -// String pubKey = prop.getProperty("pubKey"); -// String prvKey = prop.getProperty("prvKey"); -// String password = prop.getProperty("password"); -// String contractPath = prop.getProperty("contractPath"); -// -// -// if(StringUtils.isEmpty(host)){ -// logger.info("host不能为空"); -// return; -// } -// -// if(StringUtils.isEmpty(ledger)){ -// logger.info("ledger不能为空."); -// return; -// } -// if(StringUtils.isEmpty(pubKey)){ -// logger.info("pubKey不能为空."); -// return; -// } -// if(StringUtils.isEmpty(prvKey)){ -// logger.info("prvKey不能为空."); -// return; -// } -// if(StringUtils.isEmpty(contractPath)){ -// logger.info("contractPath不能为空."); -// return; -// } -// -// File contract = new File(contractPath); -// if (!contract.isFile()){ -// logger.info("文件"+contractPath+"不存在"); -// return; -// } -// byte[] contractBytes = FileUtils.readBytes(contractPath); -// -// -// PrivKey prv = KeyGenCommand.decodePrivKeyWithRawPassword(prvKey, password); -// PubKey pub = KeyGenCommand.decodePubKey(pubKey); -// BlockchainKeypair blockchainKeyPair = new BlockchainKeypair(pub, prv); -// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); -// -// StringBuffer sb = new StringBuffer(); -// sb.append("host:"+ host).append(",port:"+port).append(",ledgerHash:"+ledgerHash.toBase58()). -// append(",pubKey:"+pubKey).append(",prvKey:"+prv).append(",contractPath:"+contractPath); -// logger.info(sb.toString()); -// ContractDeployExeUtil.instance.deploy(host,port,ledgerHash, blockchainKeyPair, contractBytes); -// } -// -//} -// -// diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractField.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractField.java new file mode 100644 index 00000000..f4b0f396 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractField.java @@ -0,0 +1,43 @@ +package com.jd.blockchain.contract.maven; + +public class ContractField extends AbstractContract { + + private String fieldName; + + private String fieldType; + + private boolean isStatic; + + public ContractField(String className, String fieldName, String fieldType) { + this(className, fieldName, fieldType, false); + } + + public ContractField(String className, String fieldName, String fieldType, boolean isStatic) { + this.className = format(className); + this.fieldName = fieldName; + this.fieldType = format(fieldType); + this.isStatic = isStatic; + } + + public String getFieldName() { + return fieldName; + } + + public String getFieldType() { + return fieldType; + } + + public boolean isStatic() { + return isStatic; + } + + @Override + public String toString() { + return "ContractField{" + + "className='" + className + '\'' + + ", fieldName='" + fieldName + '\'' + + ", fieldType='" + fieldType + '\'' + + ", isStatic=" + isStatic + + '}'; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractMethod.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractMethod.java new file mode 100644 index 00000000..6dc2e3a7 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractMethod.java @@ -0,0 +1,81 @@ +package com.jd.blockchain.contract.maven; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ContractMethod extends AbstractContract { + + private String methodName; + + private String[] paramTypes; + + private String[] returnTypes; + + private List fieldList = new ArrayList<>(); + + private List methodList = new ArrayList<>(); + + public ContractMethod(String className, String methodName) { + this(className, methodName, null, null); + } + + public ContractMethod(String className, String methodName, String[] paramTypes, String[] returnTypes) { + this.className = format(className); + this.methodName = methodName; + this.paramTypes = paramTypes; + this.returnTypes = returnTypes; + } + + public void addMethod(String className, String methodName, String[] paramTypes, String[] returnTypes) { + methodList.add(new ContractMethod(className, methodName, paramTypes, returnTypes)); + } + + public void addField(String className, String fieldName, String fieldType) { + this.fieldList.add(new ContractField(className, fieldName, fieldType)); + } + + public void addStaticField(String className, String fieldName, String fieldType) { + this.fieldList.add(new ContractField(className, fieldName, fieldType, true)); + } + + public String getMethodName() { + return methodName; + } + + public String[] getParamTypes() { + return paramTypes; + } + + public List getAllFieldList() { + return fieldList; + } + + public List getClassFieldList(String cName) { + List classFieldList = new ArrayList<>(); + if (!fieldList.isEmpty()) { + for (ContractField field : fieldList) { + if (field.getClassName().equals(cName)) { + classFieldList.add(field); + } + } + } + return classFieldList; + } + + public List getMethodList() { + return methodList; + } + + @Override + public String toString() { + return "ContractMethod{" + + "className='" + className + '\'' + + ", methodName='" + methodName + '\'' + + ", paramTypes=" + Arrays.toString(paramTypes) + + ", returnTypes=" + Arrays.toString(returnTypes) + + ", fieldList=" + fieldList + + ", methodList=" + methodList + + '}'; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java deleted file mode 100644 index 3fb2404d..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractResolveEngine.java +++ /dev/null @@ -1,459 +0,0 @@ -package com.jd.blockchain.contract.maven; - -import com.github.javaparser.JavaParser; -import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.ImportDeclaration; -import com.github.javaparser.ast.visitor.VoidVisitorAdapter; -import com.jd.blockchain.contract.ContractType; -import org.apache.commons.io.FileUtils; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugin.logging.Log; -import org.apache.maven.project.MavenProject; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.jar.Attributes; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import static com.jd.blockchain.contract.ContractJarUtils.*; -import static com.jd.blockchain.contract.maven.ContractCompileMojo.JAR_DEPENDENCE; -import static com.jd.blockchain.utils.decompiler.utils.DecompilerUtils.decompileJarFile; - -public class ContractResolveEngine { - - private static final String JAVA_SUFFIX = ".java"; - - private static final String PATH_DIRECT = - "src" + File.separator + - "main" + File.separator + - "java" + File.separator; - - private static final String CONFIG = "config.properties"; - - private static final String BLACK_PACKAGE_LIST = "black.package.list"; - - private static final String BLACK_CLASS_LIST = "black.class.list"; - - private static final String BLACK_NAME_LIST = "black.name.list"; - - private static List blackNameList; - - private static List blackPackageList; - - private static Set blackClassSet; - - static { - try { - configInit(); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - private Log LOGGER; - - private MavenProject project; - - private String finalName; - - public ContractResolveEngine(Log LOGGER, MavenProject project, String finalName) { - this.LOGGER = LOGGER; - this.project = project; - this.finalName = finalName; - } - - public void compileAndVerify() throws MojoFailureException { - try { - jarCopy(); - verify(compileCustomJar()); - } catch (IOException e) { - throw new MojoFailureException("IO Error : " + e.getMessage(), e); - } catch (MojoFailureException ex) { - throw ex; - } - } - - private void jarCopy() throws IOException { - String srcJarPath = project.getBuild().getDirectory() + - File.separator + finalName + "-" + JAR_DEPENDENCE + ".jar"; - String dstJarPath = project.getBuild().getDirectory() + - File.separator + finalName + ".jar"; - FileUtils.copyFile(new File(srcJarPath), new File(dstJarPath)); - } - - private File compileCustomJar() throws IOException { - return copyAndManage(project, finalName); - } - - private void verify(File jarFile) throws MojoFailureException { - try { - // 首先校验MainClass - try { - verifyMainClass(jarFile); - } catch (Exception e) { - jarFile.delete(); - LOGGER.error(e.getMessage()); - throw e; - } - - LinkedList totalClassList = loadAllClass(jarFile); - // 该项目路径 - String projectDir = project.getBasedir().getPath(); - // 代码路径 - String codeBaseDir = projectDir + File.separator + PATH_DIRECT; - - if (!totalClassList.isEmpty()) { - - boolean isOK = true; - - for (String clazz : totalClassList) { - - LOGGER.debug(String.format("Verify Class[%s] start......", clazz)); - // 获取其包名 - String packageName = packageName(clazz); - - LOGGER.debug(String.format("Class[%s] 's package name = %s", clazz, packageName)); - - // 包的名字黑名单,不能打包该类进入Jar包中,或者合约不能命名这样的名字 - boolean isNameBlack = false; - for (ContractPackage blackName : blackNameList) { - isNameBlack = verifyPackage(packageName, blackName); - if (isNameBlack) { - break; - } - } - - // 假设是黑名单则打印日志 - if (isNameBlack) { - // 打印信息供检查 - LOGGER.error(String.format("Class[%s]'s Package-Name belong to BlackNameList !!!", clazz)); - isOK = false; - continue; - } - - // 获取该Class对应的Java文件 - File javaFile = new File(codeBaseDir + clazz + JAVA_SUFFIX); - - boolean isNeedDelete = false; - if (!javaFile.exists()) { - LOGGER.debug(String.format("Class[%s] -> Java[%s] is not exist, start decompile ...", clazz, jarFile.getPath())); - // 表明不是项目中的内容,需要通过反编译获取该文件 - String source = null; - try { - source = decompileJarFile(jarFile.getPath(), clazz, true, StandardCharsets.UTF_8.name()); - if (source == null || source.length() == 0) { - throw new IllegalStateException(); - } - } catch (Exception e) { - LOGGER.warn(String.format("Decompile Jar[%s]->Class[%s] Fail !!!", jarFile.getPath(), clazz)); - } - // 将source写入Java文件 - File sourceTempJavaFile = new File(tempPath(codeBaseDir, clazz)); - FileUtils.writeStringToFile(sourceTempJavaFile, source == null ? "" : source); - javaFile = sourceTempJavaFile; - isNeedDelete = true; - } else { - LOGGER.debug(String.format("Class[%s] -> Java[%s] is exist", clazz, jarFile.getPath())); - } - - LOGGER.info(String.format("Parse Java File [%s] start......", javaFile.getPath())); - // 解析文件中的内容 - CompilationUnit compilationUnit = JavaParser.parse(javaFile); - - MethodVisitor methodVisitor = new MethodVisitor(); - - compilationUnit.accept(methodVisitor, null); - - List imports = methodVisitor.importClasses; - - if (!imports.isEmpty()) { - for (String importClass : imports) { - LOGGER.debug(String.format("Class[%s] read import -> [%s]", clazz, importClass)); - if (importClass.endsWith("*")) { - // 导入的是包 - for (ContractPackage blackPackage : blackPackageList) { - String importPackageName = importClass.substring(0, importClass.length() - 2); - if (verifyPackage(importPackageName, blackPackage)) { - // 打印信息供检查 - LOGGER.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); - isOK = false; - break; - } - } - } else { - // 导入的是具体的类,则判断类黑名单 + 包黑名单 - if (blackClassSet.contains(importClass)) { - // 包含导入类,该方式无法通过验证 - LOGGER.error(String.format("Class[%s]'s import class [%s] belong to BlackClassList !!!", clazz, importClass)); - isOK = false; - } else { - // 判断导入的该类与黑名单导入包的对应关系 - for (ContractPackage blackPackage : blackPackageList) { - if (verifyClass(importClass, blackPackage)) { - LOGGER.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); - isOK = false; - break; - } - } - } - } - } - } - if (isNeedDelete) { - javaFile.delete(); - } - LOGGER.debug(String.format("Verify Class[%s] end......", clazz)); - } - if (!isOK) { - // 需要将该Jar删除 - jarFile.delete(); - throw new IllegalStateException("There are many Illegal information, please check !!!"); - } - } else { - jarFile.delete(); - throw new IllegalStateException("There is none class !!!"); - } - } catch (Exception e) { - LOGGER.error(e.getMessage()); - throw new MojoFailureException(e.getMessage(), e); - } - } - - private void verifyMainClass(File jarFile) throws Exception { - // 加载main-class,开始校验类型 - LOGGER.debug(String.format("Verify Jar [%s] 's MainClass start...", jarFile.getName())); - URL jarURL = jarFile.toURI().toURL(); - ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL}, this.getClass().getClassLoader()); - Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); - String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); - Class mainClass = classLoader.loadClass(contractMainClass); - ContractType.resolve(mainClass); - LOGGER.debug(String.format("Verify Jar [%s] 's MainClass end...", jarFile.getName())); - } - - private static List blackNameList(Properties config) { - return blackList(config, BLACK_NAME_LIST); - } - - private static Set blackClassSet(Properties config) { - Set blackClassSet = new HashSet<>(); - String attrProp = config.getProperty(BLACK_CLASS_LIST); - if (attrProp != null && attrProp.length() > 0) { - String[] attrPropArray = attrProp.split(","); - for (String attr : attrPropArray) { - blackClassSet.add(attr.trim()); - } - } - return blackClassSet; - } - - private static List blackPackageList(Properties config) { - return blackList(config, BLACK_PACKAGE_LIST); - } - - private static List blackList(Properties config, String attrName) { - List list = new ArrayList<>(); - String attrProp = config.getProperty(attrName); - if (attrProp != null || attrProp.length() > 0) { - String[] attrPropArray = attrProp.split(","); - for (String attr : attrPropArray) { - list.add(new ContractPackage(attr)); - } - } - return list; - } - - private boolean verifyPackage(String packageName, ContractPackage contractPackage) { - boolean verify = false; - if (packageName.equals(contractPackage.packageName)) { - // 完全相同 - verify = true; - } else if (packageName.startsWith(contractPackage.packageName) && - contractPackage.isTotal) { - // 以某个包开头 - verify = true; - } - return verify; - } - - private boolean verifyClass(String className, ContractPackage contractPackage) { - boolean verify = false; - - if (contractPackage.isTotal) { - // 表示该包下面的其他所有包都会受限制,此处需要判断起始 - if (className.startsWith(contractPackage.packageName)) { - verify = true; - } - } else { - // 表示该包必须完整匹配ClassName所在包 - // 获取ClassName所在包 - String packageName = packageNameByDot(className); - if (packageName.equals(contractPackage.packageName)) { - verify = true; - } - } - return verify; - } - - private String packageNameByDot(String className) { - String[] array = className.split("."); - if (Character.isLowerCase(array[array.length - 2].charAt(0))) { - // 如果是小写,表示非内部类 - // 获取完整包名 - return className.substring(0, className.lastIndexOf(".")); - } - // 表示为内部类,该包拼装组成 - StringBuilder buffer = new StringBuilder(); - for (String s : array) { - if (buffer.length() > 0) { - buffer.append("."); - } - if (Character.isUpperCase(s.charAt(0))) { - // 表明已经到具体类 - break; - } - buffer.append(s); - } - - if (buffer.length() == 0) { - throw new IllegalStateException(String.format("Import Class [%s] Illegal !!!", className)); - } - - return buffer.toString(); - } - - private String packageName(String clazz) { - int index = clazz.lastIndexOf("/"); - String packageName = clazz.substring(0, index); - return packageName.replaceAll("/", "."); - } - - private File copyAndManage(MavenProject project, String finalName) throws IOException { - // 首先将Jar包转换为指定的格式 - String srcJarPath = project.getBuild().getDirectory() + - File.separator + finalName + ".jar"; - - String dstJarPath = project.getBuild().getDirectory() + - File.separator + finalName + "-temp-" + System.currentTimeMillis() + ".jar"; - - File srcJar = new File(srcJarPath), dstJar = new File(dstJarPath); - - LOGGER.debug(String.format("Jar from [%s] to [%s] Copying", srcJarPath, dstJarPath)); - // 首先进行Copy处理 - copy(srcJar, dstJar); - LOGGER.debug(String.format("Jar from [%s] to [%s] Copied", srcJarPath, dstJarPath)); - - byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); - - String finalJarPath = project.getBuild().getDirectory() + - File.separator + finalName + "-jdchain.jar"; - - File finalJar = new File(finalJarPath); - - copy(dstJar, finalJar, contractMFJarEntry(), txtBytes, null); - - // 删除临时文件 - FileUtils.forceDelete(dstJar); - - return finalJar; - } - - private static void configInit() throws Exception { - Properties config = loadConfig(); - - blackNameList = blackNameList(config); - - blackPackageList = blackPackageList(config); - - blackClassSet = blackClassSet(config); - } - - private static Properties loadConfig() throws Exception { - - Properties properties = new Properties(); - - properties.load(ContractResolveEngine.class.getResourceAsStream(File.separator + CONFIG)); - - return properties; - } - - private LinkedList loadAllClass(File file) throws Exception { - JarFile jarFile = new JarFile(file); - LinkedList allClass = new LinkedList<>(); - Enumeration jarEntries = jarFile.entries(); - while (jarEntries.hasMoreElements()) { - JarEntry jarEntry = jarEntries.nextElement(); - String entryName = jarEntry.getName(); - if (entryName.endsWith(".class")) { - // 内部类,不需要处理 - if (!entryName.contains("$")) { - allClass.addLast(entryName.substring(0, entryName.length() - 6)); - } - } - } - return allClass; - } - - private String tempPath(String codeBaseDir, String clazz) { - // 获取最后的名称 - String[] classArray = clazz.split("/"); - String tempPath = codeBaseDir + classArray[classArray.length - 1] + "_" + - System.currentTimeMillis() + "_" + System.nanoTime() + JAVA_SUFFIX; - return tempPath; - } - - private static class MethodVisitor extends VoidVisitorAdapter { - - private List importClasses = new ArrayList<>(); - - @Override - public void visit(ImportDeclaration n, Void arg) { - importClasses.add(parseClass(n.toString())); - super.visit(n, arg); - } - - private String parseClass(String importInfo) { - String className = importInfo.substring(7, importInfo.length() - 2); - if (importInfo.startsWith("import static ")) { - // 获取静态方法的类信息 - className = importInfo.substring(14, importInfo.lastIndexOf(".")); - } - if (!className.contains(".")) { - throw new IllegalStateException(String.format("Import Class [%s] is Illegal !!", className)); - } - return className; - } - } - - private static class ContractPackage { - - private String packageName; - - private boolean isTotal = false; - - public ContractPackage() { - } - - public ContractPackage(String totalPackage) { - if (totalPackage.endsWith("*")) { - this.packageName = totalPackage.substring(0, totalPackage.length() - 2).trim(); - this.isTotal = true; - } else { - this.packageName = totalPackage; - } - } - - public String getPackageName() { - return packageName; - } - - public boolean isTotal() { - return isTotal; - } - } -} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMClassVisitor.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMClassVisitor.java new file mode 100644 index 00000000..27bfeee4 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMClassVisitor.java @@ -0,0 +1,22 @@ +package com.jd.blockchain.contract.maven.asm; + +import com.jd.blockchain.contract.maven.ContractClass; +import com.jd.blockchain.contract.maven.ContractMethod; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class ASMClassVisitor extends ClassVisitor { + + private ContractClass contractClass; + + public ASMClassVisitor(ContractClass contractClass) { + super(Opcodes.ASM5); + this.contractClass = contractClass; + } + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor superMV = super.visitMethod(access, name, desc, signature, exceptions); + ContractMethod method = this.contractClass.method(name); + return new ASMMethodVisitor(superMV, method); + } +} \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMMethodVisitor.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMMethodVisitor.java new file mode 100644 index 00000000..03488203 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/asm/ASMMethodVisitor.java @@ -0,0 +1,108 @@ +package com.jd.blockchain.contract.maven.asm; + +import com.jd.blockchain.contract.maven.ContractMethod; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.TypePath; + +import java.util.ArrayList; +import java.util.List; + +public class ASMMethodVisitor extends MethodVisitor { + + private ContractMethod method; + + public ASMMethodVisitor(MethodVisitor mv, ContractMethod method) { + super(Opcodes.ASM5, mv); + this.method = method; + } + + @Override + public void visitFieldInsn(int type, String cName, String fName, String fType) { + if (type == 178 || type == 179) { + this.method.addStaticField(cName, fName, fType); + } else { + this.method.addField(cName, fName, fType); + } + super.visitFieldInsn(type, cName, fName, fType); + } + + @Override + public void visitMethodInsn(int type, String cName, String mName, String params, boolean b) { + ParamsAndReturn paramsAndReturn = resolveParamsAndReturn(params); + this.method.addMethod(cName, mName, paramsAndReturn.paramTypes, paramsAndReturn.returnTypes); + super.visitMethodInsn(type, cName, mName, params, b); + } + + private ParamsAndReturn resolveParamsAndReturn(String params) { + // 格式: + // 1、(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + // 2、()I + // 3、(Ljava/lang/String;)V + // 4、()V + // 5、([Ljava/lang/Object;)Ljava/util/List; false + // 从上面分析可以得出:括号内的是入参,右括号后面的是返回值,其中V表示Void,即空; + String[] paramArray = params.split("\\)"); + String paramTypeChars = ""; + if (!paramArray[0].equals("(")) { + // 表明入参不为空 + paramTypeChars = paramArray[0].split("\\(")[1]; + } + String returnTypeChars = paramArray[1]; + return new ParamsAndReturn(paramTypeChars, returnTypeChars); + } + + static class ParamsAndReturn { + + String[] paramTypes; + + String[] returnTypes; + + public ParamsAndReturn(String paramsTypeChars, String returnTypeChars) { + initParamsType(paramsTypeChars); + initReturnType(returnTypeChars); + } + + private void initParamsType(String paramsTypeChars) { + List paramList = handleTypes(paramsTypeChars); + if (!paramList.isEmpty()) { + this.paramTypes = new String[paramList.size()]; + paramList.toArray(this.paramTypes); + } + } + + private void initReturnType(String returnTypeChar) { + // 按照分号分隔 + List returnList = handleTypes(returnTypeChar); + if (!returnList.isEmpty()) { + this.returnTypes = new String[returnList.size()]; + returnList.toArray(this.returnTypes); + } + } + + private List handleTypes(String typeChars) { + String[] types = typeChars.split(";"); + List typeList = new ArrayList<>(); + if (types.length > 0) { + for (String type : types) { + if (type.length() > 0) { + if (type.startsWith("[L") && type.length() > 2) { + // 说明是数组 + typeList.add(type.substring(2) + "[]"); + } else if (type.startsWith("[") && type.length() > 1) { + // 说明是数组 + typeList.add(type.substring(1)); + } else if (type.startsWith("L") && type.length() > 1) { + // 说明是非基础类型 + typeList.add(type.substring(1)); + } else { + typeList.add(type); + } + } + } + } + return typeList; + } + } +} \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java new file mode 100644 index 00000000..97044df6 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/BlackList.java @@ -0,0 +1,154 @@ +package com.jd.blockchain.contract.maven.rule; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class BlackList { + + public static final String COMMON_METHOD = "*"; + + public static final String INIT_METHOD = "init"; + + // 合约黑名单 + private final Map blackClassMap = new ConcurrentHashMap<>(); + + private final List blackPackages = new ArrayList<>(); + + public synchronized BlackList addBlack(String className, String methodName) { + String trimClassName = className.trim(); + BlackClass blackClass = blackClassMap.get(trimClassName); + if (blackClass != null) { + blackClass.addMethod(methodName); + } else { + blackClass = new BlackClass(trimClassName); + blackClass.addMethod(methodName); + blackClassMap.put(trimClassName, blackClass); + } + return this; + } + + public synchronized BlackList addBlack(Class clazz, String methodName) { + return addBlack(clazz.getName(), methodName); + } + + public synchronized BlackList addBlack(Class clazz) { + return addBlack(clazz.getName(), COMMON_METHOD); + } + + public synchronized BlackList addBlackPackage(String packageName) { + blackPackages.add(packageName.trim() + "."); // 末尾增加一个点,防止后续判断是拼凑 + return this; + } + + public boolean isBlackClass(String className) { + if (isContainsPackage(className)) { + return true; + } + BlackClass blackClass = blackClassMap.get(className); + if (blackClass == null) { + return false; + } + return blackClass.isBlack(); + } + + public boolean isBlack(Class clazz, String methodName) { + // 判断该Class是否属于黑名单 + if (isCurrentClassBlack(clazz, methodName)) { + return true; + } + // 当前Class不是黑名单的情况下,处理其对应的父类和接口 + // 获取该Class对应的接口和父类列表 + Set> superClassAndAllInterfaces = new HashSet<>(); + + loadSuperClassAndAllInterfaces(clazz, superClassAndAllInterfaces); + + // 循环判断每个父类和接口 + for (Class currClass : superClassAndAllInterfaces) { + if (isCurrentClassBlack(currClass, methodName)) { + return true; + } + } + return false; + } + + public boolean isCurrentClassBlack(Class clazz, String methodName) { + + String packageName = clazz.getPackage().getName(); + for (String bp : blackPackages) { + if (packageName.equals(bp) || packageName.startsWith(bp)) { + return true; + } + } + // 判断该类本身是否属于黑名单 + String className = clazz.getName(); + BlackClass blackClass = blackClassMap.get(className); + if (blackClass != null) { + // 判断其方法 + return blackClass.isBlack(methodName); + } + return false; + } + + public boolean isBlackField(Class clazz) { + return isBlack(clazz, INIT_METHOD); + } + + private boolean isContainsPackage(String className) { + for (String bp : blackPackages) { + if (className.equals(bp) || className.startsWith(bp)) { + return true; + } + } + return false; + } + + private void loadSuperClassAndAllInterfaces(Class currentClass, Set> allClassList) { + if (currentClass == null) { + return; + } + + if (!allClassList.contains(currentClass)) { + allClassList.add(currentClass); + // 处理其父类 + Class superClass = currentClass.getSuperclass(); + loadSuperClassAndAllInterfaces(superClass, allClassList); + + // 处理其所有接口 + Class[] allInterfaces = currentClass.getInterfaces(); + if (allInterfaces != null && allInterfaces.length > 0) { + for (Class intf : allInterfaces) { + loadSuperClassAndAllInterfaces(intf, allClassList); + } + } + } + } + + private static class BlackClass { + + String className; + + Set methods = new HashSet<>(); + + BlackClass(String className) { + this.className = className; + } + + void addMethod(String methodName) { + methods.add(methodName); + } + + boolean isBlack(String methodName) { + // 假设method为*则表示所有的方法 + if (methods.contains(COMMON_METHOD)) { + return true; + } + return methods.contains(methodName); + } + + boolean isBlack() { + return isBlack(COMMON_METHOD); + } + } +} + + diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java new file mode 100644 index 00000000..6c3dca44 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/DependencyExclude.java @@ -0,0 +1,93 @@ +package com.jd.blockchain.contract.maven.rule; + +import com.jd.blockchain.contract.ContractJarUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class DependencyExclude { + + private static final String COMMON_ARTIFACTID = "*"; + + private static final String CONFIG = "provided.conf"; + + private static final Map> DEPENDENCYS = new ConcurrentHashMap<>(); + + static { + try { + init(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private static void init() throws Exception { + List readLines = ContractJarUtils.loadConfig(CONFIG); + if (!readLines.isEmpty()) { + for (String line : readLines) { + // groupId/artifactId + String[] lines = line.split(","); + if (lines.length > 0) { + for (String depend : lines) { + String[] depends = depend.split("/"); + if (depends.length == 2) { + String groupId = depends[0], artifactId = depends[1]; + Dependency dependency = new Dependency(groupId, artifactId); + addDependency(dependency); + } + } + } + } + } + } + + private synchronized static void addDependency(Dependency dependency) { + String groupId = dependency.groupId; + if (!DEPENDENCYS.containsKey(groupId)) { + List dependencies = new ArrayList<>(); + dependencies.add(dependency); + DEPENDENCYS.put(groupId, dependencies); + } else { + List dependencies = DEPENDENCYS.get(groupId); + dependencies.add(dependency); + } + } + + public boolean isExclude(String groupId, String artifactId) { + List dependencies = DEPENDENCYS.get(groupId); + + if (dependencies == null || dependencies.isEmpty()) { + return false; + } + + for (Dependency dependency : dependencies) { + if (dependency.isEqualArtifactId(artifactId)) { + return true; + } + } + + return false; + } + + + static class Dependency { + + String groupId; + + String artifactId; + + public Dependency(String groupId, String artifactId) { + this.groupId = groupId; + this.artifactId = artifactId; + } + + public boolean isEqualArtifactId(String artiId) { + if (artifactId.equals(COMMON_ARTIFACTID)) { + return true; + } + return artifactId.equals(artiId); + } + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/WhiteList.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/WhiteList.java new file mode 100644 index 00000000..eeb1e250 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/rule/WhiteList.java @@ -0,0 +1,30 @@ +package com.jd.blockchain.contract.maven.rule; + +import java.util.ArrayList; +import java.util.List; + +public class WhiteList { + + // 合约白名单(白名单通常数量较少,主要是JDChain内部包) + private final List whiteClasses = new ArrayList<>(); + + public void addWhite(String className) { + whiteClasses.add(className.trim()); + } + + public boolean isWhite(Class clazz) { + String className = clazz.getName(); + return isWhite(className); + } + + public boolean isWhite(String className) { + for (String white : whiteClasses) { + if (white.equals(className) || className.startsWith(white)) { + return true; + } + } + return false; + } +} + + diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java new file mode 100644 index 00000000..059f7337 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/ResolveEngine.java @@ -0,0 +1,133 @@ +package com.jd.blockchain.contract.maven.verify; + +import com.jd.blockchain.contract.ContractJarUtils; +import com.jd.blockchain.contract.ContractType; +import org.apache.commons.io.FileUtils; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import static com.jd.blockchain.contract.ContractJarUtils.*; + +public class ResolveEngine { + + private Log LOGGER; + + private File jarFile; + + private String mainClass; + + public ResolveEngine(Log LOGGER, File jarFile, String mainClass) { + this.LOGGER = LOGGER; + this.jarFile = jarFile; + this.mainClass = mainClass; + } + + public File verify() throws MojoFailureException { + try { + // 首先校验MainClass + ClassLoader classLoader = verifyMainClass(jarFile); + + // 检查jar包中所有的class的命名,要求其包名不能为com.jd.blockchain.* + LinkedList totalClasses = loadAllClass(jarFile); + + if (!totalClasses.isEmpty()) { + + for (String clazz : totalClasses) { + + String dotClassName = dotClassName(clazz); + + LOGGER.debug(String.format("Verify Dependency Class[%s] start......", dotClassName)); + // 获取其包名 + Class currentClass = classLoader.loadClass(dotClassName); + + String packageName = currentClass.getPackage().getName(); + + if (ContractJarUtils.isJDChainPackage(packageName)) { + throw new IllegalStateException(String.format("Class[%s]'s package[%s] cannot start with %s !", + dotClassName, packageName, ContractJarUtils.JDCHAIN_PACKAGE)); + } + + LOGGER.debug(String.format("Verify Class[%s] end......", clazz)); + } + } + + // 处理完成之后,生成finalName-JDChain-Contract.jar + return compileCustomJar(); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + throw new MojoFailureException(e.getMessage()); + } + } + + private File compileCustomJar() throws IOException { + + String fileParentPath = jarFile.getParentFile().getPath(); + + String jarFileName = jarFile.getName(); + + String fileName = jarFileName.substring(0, jarFileName.lastIndexOf(".")); + + // 首先将Jar包转换为指定的格式 + String dstJarPath = fileParentPath + File.separator + + fileName + "-temp-" + System.currentTimeMillis() + ".jar"; + + File srcJar = jarFile, dstJar = new File(dstJarPath); + + LOGGER.debug(String.format("Jar from [%s] to [%s] Copying", jarFile.getPath(), dstJarPath)); + // 首先进行Copy处理 + copy(srcJar, dstJar); + + LOGGER.debug(String.format("Jar from [%s] to [%s] Copied", jarFile.getPath(), dstJarPath)); + + byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); + + String finalJarPath = fileParentPath + File.separator + fileName + "-JDChain-Contract.jar"; + + File finalJar = new File(finalJarPath); + + // 生成最终的Jar文件 + copy(dstJar, finalJar, contractMFJarEntry(), txtBytes, null); + + // 删除临时文件 + FileUtils.forceDelete(dstJar); + + return finalJar; + } + + private ClassLoader verifyMainClass(File jarFile) throws Exception { + // 加载main-class,开始校验类型 + LOGGER.debug(String.format("Verify Jar [%s] 's MainClass start...", jarFile.getName())); + URL jarURL = jarFile.toURI().toURL(); + ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL}); + Class mClass = classLoader.loadClass(mainClass); + ContractType.resolve(mClass); + LOGGER.debug(String.format("Verify Jar [%s] 's MainClass end...", jarFile.getName())); + return classLoader; + } + + private LinkedList loadAllClass(File file) throws Exception { + JarFile jarFile = new JarFile(file); + LinkedList allClass = new LinkedList<>(); + Enumeration jarEntries = jarFile.entries(); + while (jarEntries.hasMoreElements()) { + JarEntry jarEntry = jarEntries.nextElement(); + String entryName = jarEntry.getName(); + if (entryName.endsWith(".class")) { + // 内部类,不需要处理 + if (!entryName.contains("$")) { + allClass.addLast(entryName.substring(0, entryName.length() - 6)); + } + } + } + return allClass; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java new file mode 100644 index 00000000..ea3d723c --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/verify/VerifyEngine.java @@ -0,0 +1,216 @@ +package com.jd.blockchain.contract.maven.verify; + +import com.jd.blockchain.contract.ContractJarUtils; +import com.jd.blockchain.contract.maven.ContractClass; +import com.jd.blockchain.contract.maven.ContractField; +import com.jd.blockchain.contract.maven.ContractMethod; +import com.jd.blockchain.contract.maven.asm.ASMClassVisitor; +import com.jd.blockchain.contract.maven.rule.BlackList; +import com.jd.blockchain.contract.maven.rule.WhiteList; +import org.apache.maven.plugin.logging.Log; +import org.objectweb.asm.ClassReader; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static com.jd.blockchain.contract.ContractJarUtils.loadAllClasses; + + +public class VerifyEngine { + + private Log LOGGER; + + private File jarFile; + + private String mainClass; + + private BlackList black; + + private WhiteList white; + + // 代表的只是当前方法,不代表该方法中的内部方法 + private Set haveManagedMethods = new HashSet<>(); + + // 代表的是处理的参数 + private Set haveManagedFields = new HashSet<>(); + + public VerifyEngine(Log LOGGER, File jarFile, String mainClass, BlackList black, WhiteList white) { + this.LOGGER = LOGGER; + this.jarFile = jarFile; + this.mainClass = mainClass; + this.black = black; + this.white = white; + } + + public void verify() throws Exception { + // 加载所有的jar,然后ASM获取MAP + URL jarURL = jarFile.toURI().toURL(); + + URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{jarURL}); + + // 解析Jar包中所有的Class + Map allContractClasses = resolveClasses(jarClasses()); + + // 开始处理MainClass + verify(urlClassLoader, allContractClasses); + + } + + public void verify(URLClassLoader urlClassLoader, Map allContractClasses) throws Exception { + // 获取MainClass + String mainClassKey = convertClassKey(mainClass); + ContractClass mainContractClass = allContractClasses.get(mainClassKey); + if (mainContractClass == null) { + LOGGER.error(String.format("Load Main Class = [%s] NULL !!!", mainClass)); + throw new IllegalStateException("MainClass is NULL !!!"); + } + // 校验该Class中所有方法 + Map methods = mainContractClass.getMethods(); + if (!methods.isEmpty()) { + for (Map.Entry entry : methods.entrySet()) { + ContractMethod method = entry.getValue(); + verify(urlClassLoader, allContractClasses, method); + } + } + } + + public void verify(URLClassLoader urlClassLoader, Map allContractClasses, ContractMethod method) throws Exception { + // 获取方法中涉及到的所有的Class及Method + // 首先判断该方法对应的Class是否由urlClassLoader加载 + // 首先判断该ClassName对应方法是否处理过 + String managedKey = managedKey(method); + if (haveManagedMethods.contains(managedKey)) { + return; + } + // 将该方法设置为已处理 + haveManagedMethods.add(managedKey); + String dotClassName = method.getDotClassName(); + Class dotClass = urlClassLoader.loadClass(dotClassName); + + if (dotClass == null) { + return; + } + String dotClassLoader = null; + ClassLoader classLoader = dotClass.getClassLoader(); + + if (classLoader != null) { + dotClassLoader = dotClass.getClassLoader().toString(); + } + + if (dotClassLoader != null && dotClassLoader.contains("URLClassLoader")) { + + // 说明是URLClassLoader,这个需要先从黑名单和白名单列表中操作 + // 首先判断是否是黑名单,黑名单优先级最高 + if (black.isBlack(dotClass, method.getMethodName())) { + throw new IllegalStateException(String.format("Class [%s] Method [%s] is Black !!!", dotClassName, method.getMethodName())); + } else { + // 不是黑名单的情况下,判断是否为白名单 + if (white.isWhite(dotClass)) { + return; + } + // 如果不属于白名单,则需要判断其子方法 + List innerMethods = method.getMethodList(); + if (!innerMethods.isEmpty()) { + for (ContractMethod innerMethod : innerMethods) { + // 需要重新从AllMap中获取,因为生成时并未处理其关联关系 + ContractClass innerClass = allContractClasses.get(innerMethod.getClassName()); + if (innerClass != null) { + ContractMethod verifyMethod = innerClass.method(innerMethod.getMethodName()); + verify(urlClassLoader, allContractClasses, verifyMethod); + } else { + verify(urlClassLoader, allContractClasses, innerMethod); + } + } + } + List innerFields = method.getAllFieldList(); + if (!innerFields.isEmpty()) { + for (ContractField innerField : innerFields) { + verify(urlClassLoader, innerField); + } + } + } + } else { + // 非URLClassLoader加载的类,只需要做判断即可 + // 1、不再需要获取其方法; + // 2、只需要判断黑名单不需要判断白名单 + if (black.isBlack(dotClass, method.getMethodName())) { + throw new IllegalStateException(String.format("Class [%s] Method [%s] is Black !!!", dotClassName, method.getMethodName())); + } + } + } + + public void verify(URLClassLoader urlClassLoader, ContractField field) throws Exception { + // 获取方法中涉及到的所有的Class及Method + // 首先判断该方法对应的Class是否由urlClassLoader加载 + // 首先判断该ClassName对应方法是否处理过 + String managedKey = managedKey(field); + if (haveManagedFields.contains(managedKey)) { + return; + } + // 将该字段设置为已读 + haveManagedFields.add(managedKey); + + Class dotClass = urlClassLoader.loadClass(field.getDotClassName()); + + if (dotClass == null) { + return; + } + + if (black.isBlackField(dotClass)) { + throw new IllegalStateException(String.format("Class [%s] Field [%s] is Black !!!", field.getDotClassName(), field.getFieldName())); + } + } + + private Map jarClasses() throws Exception { + return loadAllClasses(jarFile); + } + + private Map resolveClasses(Map allClasses) { + + Map allContractClasses = new ConcurrentHashMap<>(); + + for (Map.Entry entry : allClasses.entrySet()) { + byte[] classContent = entry.getValue(); + if (classContent == null || classContent.length == 0) { + continue; + } + String className = entry.getKey().substring(0, entry.getKey().length() - 6); + + String dotClassName = ContractJarUtils.dotClassName(className); + if (white.isWhite(dotClassName) || black.isBlackClass(dotClassName)) { + continue; + } + + LOGGER.info(String.format("Resolve Class [%s] ...", className)); + + ContractClass contractClass = new ContractClass(className); + ClassReader cr = new ClassReader(classContent); + cr.accept(new ASMClassVisitor(contractClass), ClassReader.SKIP_DEBUG); + allContractClasses.put(className, contractClass); + } + return allContractClasses; + } + + private String convertClassKey(final String classKey) { + String newClassKey = classKey; + if (classKey.endsWith(".class")) { + newClassKey = classKey.substring(0, classKey.length() - 6); + } + newClassKey = newClassKey.replaceAll("\\.", "/"); + return newClassKey; + } + + private String managedKey(ContractMethod method) { + return method.getDotClassName() + "-" + method.getMethodName(); + } + + private String managedKey(ContractField field) { + return field.getDotClassName() + "--" + field.getFieldName(); + } +} diff --git a/source/contract/contract-maven-plugin/src/main/resources/blacks.conf b/source/contract/contract-maven-plugin/src/main/resources/blacks.conf new file mode 100644 index 00000000..533c4850 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/resources/blacks.conf @@ -0,0 +1,11 @@ +java.io.* +java.nio.* +java.net.* +java.sql.* +java.lang.reflect.* +java.lang.Class +java.lang.ClassLoader +java.util.Random +java.lang.System-currentTimeMillis +java.lang.System-nanoTime +com.jd.blockchain.ledger.BlockchainKeyGenerator-generate \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/main/resources/config.properties b/source/contract/contract-maven-plugin/src/main/resources/config.properties deleted file mode 100644 index b54de2fb..00000000 --- a/source/contract/contract-maven-plugin/src/main/resources/config.properties +++ /dev/null @@ -1,8 +0,0 @@ -#black.name.list:打包为合约Jar后,每个Class文件不允许使用或包含的名称,默认不允许使用com.jd.blockchain.*及一些内部已经引用的包 -black.name.list=com.jd.blockchain.*, com.alibaba.fastjson.*, org.apache.commons.io.*, org.apache.commons.codec.*, io.netty.* - -#black.package.list:打包为合约中的每个Class都不允许使用的包列表,某个包下面的所有包通过.*表示 -black.package.list=java.io.*, java.nio.*, java.net.*, org.apache.commons.io.* - -#black.class.list:打包为合约中的每个Class都不允许使用的类列表 -black.class.list=java.util.Random, com.jd.blockchain.ledger.BlockchainKeyGenerator \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/main/resources/provideds.conf b/source/contract/contract-maven-plugin/src/main/resources/provideds.conf new file mode 100644 index 00000000..b2df66eb --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/resources/provideds.conf @@ -0,0 +1,22 @@ +com.jd.blockchain/* +com.alibaba/fastjson +org.slf4j/* +org.apache.logging.log4j/* +org.aspectj/* +redis.clients/* +org.rocksdb/* +io.grpc/* +org.apache.commons/* +org.apache.httpcomponents/* +org.apache.logging.log4j/* +org.reflections/reflections +com.google.guava/guava +commons-cli/commons-cli +commons-codec/commons-codec +commons-httpclient/commons-httpclient +commons-io/commons-io +io.netty/* +org.slf4j/* +org.springframework.boot/* +org.springframework.security/* +org.springframework/* \ No newline at end of file diff --git a/source/contract/contract-maven-plugin/src/main/resources/whites.conf b/source/contract/contract-maven-plugin/src/main/resources/whites.conf new file mode 100644 index 00000000..a495db60 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/resources/whites.conf @@ -0,0 +1 @@ +com.jd.blockchain.* \ No newline at end of file diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java index 5a183006..d27323f2 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/contract/ContractJarUtils.java @@ -4,21 +4,23 @@ import com.jd.blockchain.crypto.Crypto; import com.jd.blockchain.crypto.HashDigest; import com.jd.blockchain.crypto.HashFunction; import com.jd.blockchain.utils.io.BytesUtils; -import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import java.io.*; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.Enumeration; -import java.util.Random; +import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; public class ContractJarUtils { + public static final String BLACK_CONF = "black.conf"; + + public static final String WHITE_CONF = "white.conf"; + private static final String CONTRACT_MF = "META-INF/CONTRACT.MF"; private static final HashFunction HASH_FUNCTION = Crypto.getHashFunction("SHA256"); @@ -27,6 +29,87 @@ public class ContractJarUtils { private static final byte[] JDCHAIN_MARK = "JDChain".getBytes(StandardCharsets.UTF_8); + public static final String JDCHAIN_PACKAGE = "com.jd.blockchain"; + + public static boolean isJDChainPackage(String packageName) { + if (packageName.equals(JDCHAIN_PACKAGE)) { + return true; + } + return packageName.startsWith(JDCHAIN_PACKAGE + "."); + } + + public static List loadWhiteConf() { + + return resolveConfig(WHITE_CONF); + } + + public static List loadBlackConf() { + return resolveConfig(BLACK_CONF); + } + + public static List resolveConfig(String fileName) { + List configs = new ArrayList<>(); + + try { + List readLines = loadConfig(fileName); + if (!readLines.isEmpty()) { + for (String readLine : readLines) { + String[] lines = readLine.split(","); + configs.addAll(Arrays.asList(lines)); + } + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + + return configs; + } + + public static List loadConfig(String fileName) throws Exception { + + return IOUtils.readLines( + ContractJarUtils.class.getResourceAsStream(File.separator + fileName)); + } + + public static Map loadAllClasses(final File jar) throws Exception { + Map allClasses = new HashMap<>(); + JarFile jarFile = new JarFile(jar); + Enumeration jarEntries = jarFile.entries(); + while(jarEntries.hasMoreElements()){ + JarEntry jarEntry = jarEntries.nextElement(); + String entryName = jarEntry.getName(); + if (verify(entryName)) { + byte[] classContent = readStream(jarFile.getInputStream(jarEntry)); + if (classContent != null && classContent.length > 0) { + allClasses.put(entryName, classContent); + } + } + } + jarFile.close(); + + return allClasses; + } + + private static boolean verify(String entryName) { + + if (entryName.endsWith(".class") + && !entryName.startsWith("META-INF") + && !entryName.contains("-") + && entryName.contains("/")) { + return true; + } + return false; + } + + public static String dotClassName(String className) { + String dotClassName = className; + if (className.endsWith(".class")) { + dotClassName = className.substring(0, className.length() - 6); + } + dotClassName = dotClassName.replaceAll("/", "."); + return dotClassName; + } + public static void verify(byte[] chainCode) { if (chainCode == null || chainCode.length == 0) { throw new IllegalStateException("Contract's chaincode is empty !!!");