| @@ -36,6 +36,8 @@ import java.util.jar.JarFile; | |||
| import java.util.jar.JarOutputStream; | |||
| import java.util.stream.Collectors; | |||
| import static com.jd.blockchain.utils.jar.ContractJarUtils.*; | |||
| /** | |||
| * first step, we want to parse the source code by javaParse. But it's repeated and difficult to parse the source. | |||
| * This is a try of "from Initail to Abandoned". | |||
| @@ -50,8 +52,6 @@ public class ContractVerifyMojo extends AbstractMojo { | |||
| Logger logger = LoggerFactory.getLogger(ContractVerifyMojo.class); | |||
| private static final String JDCHAIN_META = "META-INF/JDCHAIN.TXT"; | |||
| @Parameter(defaultValue = "${project}", required = true, readonly = true) | |||
| private MavenProject project; | |||
| @@ -130,75 +130,15 @@ public class ContractVerifyMojo extends AbstractMojo { | |||
| File finalJar = new File(finalJarPath); | |||
| copy(dstJar, finalJar, new JarEntry(JDCHAIN_META), txtBytes, null); | |||
| copy(dstJar, finalJar, jdChainMetaTxtJarEntry(), txtBytes, null); | |||
| // 删除临时文件 | |||
| FileUtils.forceDelete(dstJar); | |||
| return finalJar; | |||
| // 删除srcJar | |||
| // 删除finalJar | |||
| // FileUtils.forceDelete(finalJar); | |||
| // // 删除srcJar | |||
| // srcJar.deleteOnExit(); | |||
| // | |||
| // // 修改名字 | |||
| // finalJar.renameTo(srcJar); | |||
| } | |||
| private void copy(File srcJar, File dstJar) throws IOException { | |||
| copy(srcJar, dstJar, null, null, null); | |||
| } | |||
| private void copy(File srcJar, File dstJar, JarEntry addEntry, byte[] addBytes, String filter) throws IOException { | |||
| JarFile jarFile = new JarFile(srcJar); | |||
| Enumeration<JarEntry> jarEntries = jarFile.entries(); | |||
| JarOutputStream jarOut = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(dstJar))); | |||
| while(jarEntries.hasMoreElements()){ | |||
| JarEntry jarEntry = jarEntries.nextElement(); | |||
| String entryName = jarEntry.getName(); | |||
| if (filter != null && filter.equals(entryName)) { | |||
| continue; | |||
| } | |||
| System.out.println(entryName); | |||
| jarOut.putNextEntry(jarEntry); | |||
| jarOut.write(readStream(jarFile.getInputStream(jarEntry))); | |||
| jarOut.closeEntry(); | |||
| } | |||
| if (addEntry != null) { | |||
| jarOut.putNextEntry(addEntry); | |||
| jarOut.write(addBytes); | |||
| jarOut.closeEntry(); | |||
| } | |||
| jarOut.flush(); | |||
| jarOut.finish(); | |||
| jarOut.close(); | |||
| jarFile.close(); | |||
| } | |||
| private String jdChainTxt(byte[] content) { | |||
| // hash=Hex(hash(content)) | |||
| String hashTxt = "hash:" + DigestUtils.sha256Hex(content); | |||
| System.out.println(hashTxt); | |||
| return hashTxt; | |||
| } | |||
| private byte[] readStream(InputStream inputStream) { | |||
| try (ByteArrayOutputStream outSteam = new ByteArrayOutputStream()) { | |||
| byte[] buffer = new byte[1024]; | |||
| int len; | |||
| while ((len = inputStream.read(buffer)) != -1) { | |||
| outSteam.write(buffer, 0, len); | |||
| } | |||
| inputStream.close(); | |||
| return outSteam.toByteArray(); | |||
| } catch (Exception e) { | |||
| throw new IllegalStateException(e); | |||
| } | |||
| } | |||
| private class MethodVisitor extends VoidVisitorAdapter<Void> { | |||
| @Override | |||
| @@ -1,8 +1,15 @@ | |||
| package com.jd.blockchain.transaction; | |||
| import java.io.*; | |||
| import java.net.URL; | |||
| import java.nio.charset.StandardCharsets; | |||
| import java.util.ArrayList; | |||
| import java.util.Collection; | |||
| import java.util.Enumeration; | |||
| import java.util.List; | |||
| import java.util.jar.JarEntry; | |||
| import java.util.jar.JarFile; | |||
| import java.util.jar.JarOutputStream; | |||
| import com.jd.blockchain.ledger.BlockchainIdentity; | |||
| import com.jd.blockchain.ledger.BytesValue; | |||
| @@ -16,6 +23,9 @@ import com.jd.blockchain.ledger.LedgerInitSetting; | |||
| import com.jd.blockchain.ledger.Operation; | |||
| import com.jd.blockchain.ledger.UserRegisterOperation; | |||
| import com.jd.blockchain.utils.Bytes; | |||
| import com.jd.blockchain.utils.jar.ContractJarUtils; | |||
| import org.apache.commons.codec.digest.DigestUtils; | |||
| import org.apache.commons.io.FileUtils; | |||
| /** | |||
| * @author huanghaiquan | |||
| @@ -250,6 +260,9 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe | |||
| private class ContractCodeDeployOperationBuilderFilter implements ContractCodeDeployOperationBuilder { | |||
| @Override | |||
| public ContractCodeDeployOperation deploy(BlockchainIdentity id, byte[] chainCode) { | |||
| // 校验chainCode | |||
| ContractJarUtils.verify(chainCode); | |||
| // 校验成功后发布 | |||
| ContractCodeDeployOperation op = CONTRACT_CODE_DEPLOY_OP_BUILDER.deploy(id, chainCode); | |||
| operationList.add(op); | |||
| return op; | |||
| @@ -24,6 +24,12 @@ | |||
| <artifactId>commons-codec</artifactId> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>commons-io</groupId> | |||
| <artifactId>commons-io</artifactId> | |||
| <version>2.4</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>net.i2p.crypto</groupId> | |||
| <artifactId>eddsa</artifactId> | |||
| @@ -0,0 +1,136 @@ | |||
| package com.jd.blockchain.utils.jar; | |||
| 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.jar.JarEntry; | |||
| import java.util.jar.JarFile; | |||
| import java.util.jar.JarOutputStream; | |||
| public class ContractJarUtils { | |||
| private static final String JDCHAIN_META = "META-INF/JDCHAIN.TXT"; | |||
| private static final int JDCHAIN_HASH_LENGTH = 69; | |||
| private static final Random FILE_RANDOM = new Random(); | |||
| public static void verify(byte[] chainCode) { | |||
| // 首先生成合约文件 | |||
| File jarFile = newJarFile(); | |||
| try { | |||
| FileUtils.writeByteArrayToFile(jarFile, chainCode); | |||
| // 校验合约文件 | |||
| verify(jarFile); | |||
| } catch (Exception e) { | |||
| throw new IllegalStateException(e); | |||
| } finally { | |||
| // 删除文件 | |||
| try { | |||
| FileUtils.forceDelete(jarFile); | |||
| } catch (Exception e) { | |||
| throw new IllegalStateException(e); | |||
| } | |||
| } | |||
| } | |||
| private static void verify(File jarFile) throws Exception { | |||
| // 首先判断jarFile中是否含有META-INF/JDCHAIN.TXT,并将其读出 | |||
| URL jarUrl = new URL("jar:file:" + jarFile.getPath() + "!/" + JDCHAIN_META); | |||
| InputStream inputStream = jarUrl.openStream(); | |||
| if (inputStream == null) { | |||
| throw new IllegalStateException(JDCHAIN_META + " IS NULL !!!"); | |||
| } | |||
| byte[] bytes = IOUtils.toByteArray(inputStream); | |||
| if (bytes == null || bytes.length != JDCHAIN_HASH_LENGTH) { | |||
| throw new IllegalStateException(JDCHAIN_META + " IS Illegal !!!"); | |||
| } | |||
| // 获取对应的Hash内容 | |||
| String txt = new String(bytes, StandardCharsets.UTF_8); | |||
| // 生成新的Jar包文件,该文件路径与JarFile基本一致 | |||
| File tempJar = newJarFile(); | |||
| // 复制除JDCHAIN.TXT之外的部分 | |||
| copy(jarFile, tempJar, null, null, JDCHAIN_META); | |||
| // 生成新Jar包对应的Hash内容 | |||
| String verifyTxt = jdChainTxt(FileUtils.readFileToByteArray(tempJar)); | |||
| // 删除临时文件 | |||
| FileUtils.forceDelete(tempJar); | |||
| // 校验Jar包内容 | |||
| if (!txt.equals(verifyTxt)) { | |||
| throw new IllegalStateException(String.format("Jar [%s] verify Illegal !!!", jarFile.getName())); | |||
| } | |||
| } | |||
| public static void copy(File srcJar, File dstJar) throws IOException { | |||
| copy(srcJar, dstJar, null, null, null); | |||
| } | |||
| public static void copy(File srcJar, File dstJar, JarEntry addEntry, byte[] addBytes, String filter) throws IOException { | |||
| JarFile jarFile = new JarFile(srcJar); | |||
| Enumeration<JarEntry> jarEntries = jarFile.entries(); | |||
| JarOutputStream jarOut = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(dstJar))); | |||
| while(jarEntries.hasMoreElements()){ | |||
| JarEntry jarEntry = jarEntries.nextElement(); | |||
| String entryName = jarEntry.getName(); | |||
| if (filter != null && filter.equals(entryName)) { | |||
| continue; | |||
| } | |||
| jarOut.putNextEntry(jarEntry); | |||
| jarOut.write(readStream(jarFile.getInputStream(jarEntry))); | |||
| jarOut.closeEntry(); | |||
| } | |||
| if (addEntry != null) { | |||
| jarOut.putNextEntry(addEntry); | |||
| jarOut.write(addBytes); | |||
| jarOut.closeEntry(); | |||
| } | |||
| jarOut.flush(); | |||
| jarOut.finish(); | |||
| jarOut.close(); | |||
| jarFile.close(); | |||
| } | |||
| public static String jdChainTxt(byte[] content) { | |||
| // hash=Hex(hash(content)) | |||
| return "hash:" + DigestUtils.sha256Hex(content); | |||
| } | |||
| public static JarEntry jdChainMetaTxtJarEntry() { | |||
| return new JarEntry(JDCHAIN_META); | |||
| } | |||
| private static byte[] readStream(InputStream inputStream) { | |||
| try (ByteArrayOutputStream outSteam = new ByteArrayOutputStream()) { | |||
| byte[] buffer = new byte[1024]; | |||
| int len; | |||
| while ((len = inputStream.read(buffer)) != -1) { | |||
| outSteam.write(buffer, 0, len); | |||
| } | |||
| inputStream.close(); | |||
| return outSteam.toByteArray(); | |||
| } catch (Exception e) { | |||
| throw new IllegalStateException(e); | |||
| } | |||
| } | |||
| private static File newJarFile() { | |||
| return new File("contract-" + | |||
| System.currentTimeMillis() + "-" + | |||
| System.nanoTime() + "-" + | |||
| FILE_RANDOM.nextInt(1024) + | |||
| ".jar"); | |||
| } | |||
| } | |||
| @@ -0,0 +1,64 @@ | |||
| package test.my.utils; | |||
| import org.apache.commons.io.FileUtils; | |||
| import org.junit.Test; | |||
| import org.springframework.core.io.ClassPathResource; | |||
| import java.io.File; | |||
| import java.nio.charset.StandardCharsets; | |||
| import static com.jd.blockchain.utils.jar.ContractJarUtils.*; | |||
| import static org.junit.Assert.fail; | |||
| public class ContractJarUtilsTest { | |||
| private String jarName = "complex"; | |||
| @Test | |||
| public void test() { | |||
| byte[] chainCode = null; | |||
| try { | |||
| ClassPathResource classPathResource = new ClassPathResource(jarName + ".jar"); | |||
| String classPath = classPathResource.getFile().getParentFile().getPath(); | |||
| // 首先将Jar包转换为指定的格式 | |||
| String srcJarPath = classPath + | |||
| File.separator + jarName + ".jar"; | |||
| String dstJarPath = classPath + | |||
| File.separator + jarName + "-temp-" + System.currentTimeMillis() + ".jar"; | |||
| File srcJar = new File(srcJarPath), dstJar = new File(dstJarPath); | |||
| // 首先进行Copy处理 | |||
| copy(srcJar, dstJar); | |||
| byte[] txtBytes = jdChainTxt(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); | |||
| String finalJarPath = classPath + | |||
| File.separator + jarName + "-jdchain.jar"; | |||
| File finalJar = new File(finalJarPath); | |||
| copy(dstJar, finalJar, jdChainMetaTxtJarEntry(), txtBytes, null); | |||
| // 删除临时文件 | |||
| FileUtils.forceDelete(dstJar); | |||
| // 读取finalJar中的内容 | |||
| chainCode = FileUtils.readFileToByteArray(finalJar); | |||
| FileUtils.forceDelete(finalJar); | |||
| } catch (Exception e) { | |||
| e.printStackTrace(); | |||
| } | |||
| try { | |||
| verify(chainCode); | |||
| System.out.println("Verify Success !!!"); | |||
| } catch (Exception e) { | |||
| fail("Verify Fail !!"); | |||
| } | |||
| } | |||
| } | |||