@@ -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 !!"); | |||
} | |||
} | |||
} |