diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java index 7dd78300..adb898b2 100644 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/ContractVerifyMojo.java @@ -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 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 { @Override diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java index ef9b138a..a8f34045 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/transaction/BlockchainOperationFactory.java @@ -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; diff --git a/source/utils/utils-common/pom.xml b/source/utils/utils-common/pom.xml index ff45bf19..d0aea4d7 100644 --- a/source/utils/utils-common/pom.xml +++ b/source/utils/utils-common/pom.xml @@ -24,6 +24,12 @@ commons-codec + + commons-io + commons-io + 2.4 + + net.i2p.crypto eddsa diff --git a/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java new file mode 100644 index 00000000..08ccdbff --- /dev/null +++ b/source/utils/utils-common/src/main/java/com/jd/blockchain/utils/jar/ContractJarUtils.java @@ -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 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"); + } +} diff --git a/source/utils/utils-common/src/main/resources/complex.jar b/source/utils/utils-common/src/main/resources/complex.jar new file mode 100644 index 00000000..6f40ca02 Binary files /dev/null and b/source/utils/utils-common/src/main/resources/complex.jar differ diff --git a/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java b/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java new file mode 100644 index 00000000..9caa6da2 --- /dev/null +++ b/source/utils/utils-common/src/test/java/test/my/utils/ContractJarUtilsTest.java @@ -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 !!"); + } + + } +} diff --git a/source/utils/utils-common/src/test/resources/complex.jar b/source/utils/utils-common/src/test/resources/complex.jar new file mode 100644 index 00000000..6f40ca02 Binary files /dev/null and b/source/utils/utils-common/src/test/resources/complex.jar differ