@@ -35,10 +35,6 @@ | |||||
<version>${project.version}</version> | <version>${project.version}</version> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>com.github.javaparser</groupId> | |||||
<artifactId>javaparser-core</artifactId> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>org.apache.maven</groupId> | <groupId>org.apache.maven</groupId> | ||||
@@ -58,6 +54,12 @@ | |||||
<version>2.6</version> | <version>2.6</version> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>org.ow2.asm</groupId> | |||||
<artifactId>asm</artifactId> | |||||
<version>5.0.4</version> | |||||
</dependency> | |||||
</dependencies> | </dependencies> | ||||
<build> | <build> | ||||
@@ -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<String> 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<String> 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; | |||||
} | |||||
} |
@@ -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<Plugin> 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> pluginExecution(String id, String phase, String goal) { | |||||
// PluginExecution pluginExecution = new PluginExecution(); | |||||
// pluginExecution.setId(id); | |||||
// pluginExecution.setPhase(phase); | |||||
// List<String> goals = new ArrayList<>(); | |||||
// goals.add(goal); | |||||
// pluginExecution.setGoals(goals); | |||||
// List<PluginExecution> 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); | |||||
// } | |||||
//} |
@@ -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<String, ContractMethod> 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<ContractField> fields() { | |||||
List<ContractField> 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<String, ContractMethod> getMethods() { | |||||
return methods; | |||||
} | |||||
} |
@@ -1,20 +1,94 @@ | |||||
package com.jd.blockchain.contract.maven; | 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.MojoExecutionException; | ||||
import org.apache.maven.plugin.MojoFailureException; | import org.apache.maven.plugin.MojoFailureException; | ||||
import org.apache.maven.plugin.assembly.mojos.SingleAssemblyMojo; | import org.apache.maven.plugin.assembly.mojos.SingleAssemblyMojo; | ||||
import org.apache.maven.plugins.annotations.Mojo; | 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") | @Mojo(name = "compile") | ||||
public class ContractCompileMojo extends SingleAssemblyMojo { | public class ContractCompileMojo extends SingleAssemblyMojo { | ||||
public static final String JAR_DEPENDENCE = "jar-with-dependencies"; | 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 | @Override | ||||
public void execute() throws MojoExecutionException, MojoFailureException { | 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 | // 要求必须有MainClass | ||||
String mainClass; | |||||
try { | try { | ||||
String mainClass = super.getJarArchiveConfiguration().getManifest().getMainClass(); | |||||
mainClass = super.getJarArchiveConfiguration().getManifest().getMainClass(); | |||||
// 校验MainClass,要求MainClass必须不能为空 | // 校验MainClass,要求MainClass必须不能为空 | ||||
if (mainClass == null || mainClass.length() == 0) { | if (mainClass == null || mainClass.length() == 0) { | ||||
throw new MojoFailureException("MainClass is NULL !!!"); | throw new MojoFailureException("MainClass is NULL !!!"); | ||||
@@ -23,16 +97,76 @@ public class ContractCompileMojo extends SingleAssemblyMojo { | |||||
} catch (Exception e) { | } catch (Exception e) { | ||||
throw new MojoFailureException("MainClass is null: " + e.getMessage(), 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<Artifact> 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<Artifact> 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); | |||||
} | |||||
} | } | ||||
} | } |
@@ -0,0 +1,9 @@ | |||||
package com.jd.blockchain.contract.maven; | |||||
public class ContractConstant { | |||||
public static final String METHOD_INIT = "<init>"; | |||||
public static final String METHOD_CLINIT = "<clinit>"; | |||||
} |
@@ -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; | |||||
// } | |||||
//} |
@@ -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); | |||||
// } | |||||
// | |||||
//} | |||||
// | |||||
// |
@@ -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 + | |||||
'}'; | |||||
} | |||||
} |
@@ -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<ContractField> fieldList = new ArrayList<>(); | |||||
private List<ContractMethod> 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<ContractField> getAllFieldList() { | |||||
return fieldList; | |||||
} | |||||
public List<ContractField> getClassFieldList(String cName) { | |||||
List<ContractField> classFieldList = new ArrayList<>(); | |||||
if (!fieldList.isEmpty()) { | |||||
for (ContractField field : fieldList) { | |||||
if (field.getClassName().equals(cName)) { | |||||
classFieldList.add(field); | |||||
} | |||||
} | |||||
} | |||||
return classFieldList; | |||||
} | |||||
public List<ContractMethod> 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 + | |||||
'}'; | |||||
} | |||||
} |
@@ -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<ContractPackage> blackNameList; | |||||
private static List<ContractPackage> blackPackageList; | |||||
private static Set<String> 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<String> 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<String> 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<ContractPackage> blackNameList(Properties config) { | |||||
return blackList(config, BLACK_NAME_LIST); | |||||
} | |||||
private static Set<String> blackClassSet(Properties config) { | |||||
Set<String> 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<ContractPackage> blackPackageList(Properties config) { | |||||
return blackList(config, BLACK_PACKAGE_LIST); | |||||
} | |||||
private static List<ContractPackage> blackList(Properties config, String attrName) { | |||||
List<ContractPackage> 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<String> loadAllClass(File file) throws Exception { | |||||
JarFile jarFile = new JarFile(file); | |||||
LinkedList<String> allClass = new LinkedList<>(); | |||||
Enumeration<JarEntry> 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<Void> { | |||||
private List<String> 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; | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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<String> paramList = handleTypes(paramsTypeChars); | |||||
if (!paramList.isEmpty()) { | |||||
this.paramTypes = new String[paramList.size()]; | |||||
paramList.toArray(this.paramTypes); | |||||
} | |||||
} | |||||
private void initReturnType(String returnTypeChar) { | |||||
// 按照分号分隔 | |||||
List<String> returnList = handleTypes(returnTypeChar); | |||||
if (!returnList.isEmpty()) { | |||||
this.returnTypes = new String[returnList.size()]; | |||||
returnList.toArray(this.returnTypes); | |||||
} | |||||
} | |||||
private List<String> handleTypes(String typeChars) { | |||||
String[] types = typeChars.split(";"); | |||||
List<String> 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; | |||||
} | |||||
} | |||||
} |
@@ -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<String, BlackClass> blackClassMap = new ConcurrentHashMap<>(); | |||||
private final List<String> 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<Class<?>> 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<Class<?>> 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<String> 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); | |||||
} | |||||
} | |||||
} | |||||
@@ -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<String, List<Dependency>> DEPENDENCYS = new ConcurrentHashMap<>(); | |||||
static { | |||||
try { | |||||
init(); | |||||
} catch (Exception e) { | |||||
throw new IllegalStateException(e); | |||||
} | |||||
} | |||||
private static void init() throws Exception { | |||||
List<String> 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<Dependency> dependencies = new ArrayList<>(); | |||||
dependencies.add(dependency); | |||||
DEPENDENCYS.put(groupId, dependencies); | |||||
} else { | |||||
List<Dependency> dependencies = DEPENDENCYS.get(groupId); | |||||
dependencies.add(dependency); | |||||
} | |||||
} | |||||
public boolean isExclude(String groupId, String artifactId) { | |||||
List<Dependency> 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); | |||||
} | |||||
} | |||||
} |
@@ -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<String> 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; | |||||
} | |||||
} | |||||
@@ -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<String> 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<String> loadAllClass(File file) throws Exception { | |||||
JarFile jarFile = new JarFile(file); | |||||
LinkedList<String> allClass = new LinkedList<>(); | |||||
Enumeration<JarEntry> 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; | |||||
} | |||||
} |
@@ -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<String> haveManagedMethods = new HashSet<>(); | |||||
// 代表的是处理的参数 | |||||
private Set<String> 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<String, ContractClass> allContractClasses = resolveClasses(jarClasses()); | |||||
// 开始处理MainClass | |||||
verify(urlClassLoader, allContractClasses); | |||||
} | |||||
public void verify(URLClassLoader urlClassLoader, Map<String, ContractClass> 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<String, ContractMethod> methods = mainContractClass.getMethods(); | |||||
if (!methods.isEmpty()) { | |||||
for (Map.Entry<String, ContractMethod> entry : methods.entrySet()) { | |||||
ContractMethod method = entry.getValue(); | |||||
verify(urlClassLoader, allContractClasses, method); | |||||
} | |||||
} | |||||
} | |||||
public void verify(URLClassLoader urlClassLoader, Map<String, ContractClass> 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<ContractMethod> 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<ContractField> 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<String, byte[]> jarClasses() throws Exception { | |||||
return loadAllClasses(jarFile); | |||||
} | |||||
private Map<String, ContractClass> resolveClasses(Map<String, byte[]> allClasses) { | |||||
Map<String, ContractClass> allContractClasses = new ConcurrentHashMap<>(); | |||||
for (Map.Entry<String, byte[]> 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() + "-<init>-" + field.getFieldName(); | |||||
} | |||||
} |
@@ -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 |
@@ -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 |
@@ -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/* |
@@ -0,0 +1 @@ | |||||
com.jd.blockchain.* |
@@ -4,21 +4,23 @@ import com.jd.blockchain.crypto.Crypto; | |||||
import com.jd.blockchain.crypto.HashDigest; | import com.jd.blockchain.crypto.HashDigest; | ||||
import com.jd.blockchain.crypto.HashFunction; | import com.jd.blockchain.crypto.HashFunction; | ||||
import com.jd.blockchain.utils.io.BytesUtils; | 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.FileUtils; | ||||
import org.apache.commons.io.IOUtils; | import org.apache.commons.io.IOUtils; | ||||
import java.io.*; | import java.io.*; | ||||
import java.net.URL; | import java.net.URL; | ||||
import java.nio.charset.StandardCharsets; | 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.JarEntry; | ||||
import java.util.jar.JarFile; | import java.util.jar.JarFile; | ||||
import java.util.jar.JarOutputStream; | import java.util.jar.JarOutputStream; | ||||
public class ContractJarUtils { | 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 String CONTRACT_MF = "META-INF/CONTRACT.MF"; | ||||
private static final HashFunction HASH_FUNCTION = Crypto.getHashFunction("SHA256"); | 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); | 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<String> loadWhiteConf() { | |||||
return resolveConfig(WHITE_CONF); | |||||
} | |||||
public static List<String> loadBlackConf() { | |||||
return resolveConfig(BLACK_CONF); | |||||
} | |||||
public static List<String> resolveConfig(String fileName) { | |||||
List<String> configs = new ArrayList<>(); | |||||
try { | |||||
List<String> 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<String> loadConfig(String fileName) throws Exception { | |||||
return IOUtils.readLines( | |||||
ContractJarUtils.class.getResourceAsStream(File.separator + fileName)); | |||||
} | |||||
public static Map<String, byte[]> loadAllClasses(final File jar) throws Exception { | |||||
Map<String, byte[]> allClasses = new HashMap<>(); | |||||
JarFile jarFile = new JarFile(jar); | |||||
Enumeration<JarEntry> 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) { | public static void verify(byte[] chainCode) { | ||||
if (chainCode == null || chainCode.length == 0) { | if (chainCode == null || chainCode.length == 0) { | ||||
throw new IllegalStateException("Contract's chaincode is empty !!!"); | throw new IllegalStateException("Contract's chaincode is empty !!!"); | ||||