@@ -36,6 +36,8 @@ import java.util.jar.JarFile; | |||||
import java.util.jar.JarOutputStream; | import java.util.jar.JarOutputStream; | ||||
import java.util.stream.Collectors; | 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. | * 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". | * This is a try of "from Initail to Abandoned". | ||||
@@ -50,8 +52,6 @@ public class ContractVerifyMojo extends AbstractMojo { | |||||
Logger logger = LoggerFactory.getLogger(ContractVerifyMojo.class); | Logger logger = LoggerFactory.getLogger(ContractVerifyMojo.class); | ||||
private static final String JDCHAIN_META = "META-INF/JDCHAIN.TXT"; | |||||
@Parameter(defaultValue = "${project}", required = true, readonly = true) | @Parameter(defaultValue = "${project}", required = true, readonly = true) | ||||
private MavenProject project; | private MavenProject project; | ||||
@@ -130,75 +130,15 @@ public class ContractVerifyMojo extends AbstractMojo { | |||||
File finalJar = new File(finalJarPath); | File finalJar = new File(finalJarPath); | ||||
copy(dstJar, finalJar, new JarEntry(JDCHAIN_META), txtBytes, null); | |||||
copy(dstJar, finalJar, jdChainMetaTxtJarEntry(), txtBytes, null); | |||||
// 删除临时文件 | // 删除临时文件 | ||||
FileUtils.forceDelete(dstJar); | FileUtils.forceDelete(dstJar); | ||||
return finalJar; | 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> { | private class MethodVisitor extends VoidVisitorAdapter<Void> { | ||||
@Override | @Override | ||||
@@ -1,8 +1,15 @@ | |||||
package com.jd.blockchain.transaction; | package com.jd.blockchain.transaction; | ||||
import java.io.*; | |||||
import java.net.URL; | |||||
import java.nio.charset.StandardCharsets; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Enumeration; | |||||
import java.util.List; | 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.BlockchainIdentity; | ||||
import com.jd.blockchain.ledger.BytesValue; | 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.Operation; | ||||
import com.jd.blockchain.ledger.UserRegisterOperation; | import com.jd.blockchain.ledger.UserRegisterOperation; | ||||
import com.jd.blockchain.utils.Bytes; | 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 | * @author huanghaiquan | ||||
@@ -250,6 +260,9 @@ public class BlockchainOperationFactory implements ClientOperator, LedgerInitOpe | |||||
private class ContractCodeDeployOperationBuilderFilter implements ContractCodeDeployOperationBuilder { | private class ContractCodeDeployOperationBuilderFilter implements ContractCodeDeployOperationBuilder { | ||||
@Override | @Override | ||||
public ContractCodeDeployOperation deploy(BlockchainIdentity id, byte[] chainCode) { | public ContractCodeDeployOperation deploy(BlockchainIdentity id, byte[] chainCode) { | ||||
// 校验chainCode | |||||
ContractJarUtils.verify(chainCode); | |||||
// 校验成功后发布 | |||||
ContractCodeDeployOperation op = CONTRACT_CODE_DEPLOY_OP_BUILDER.deploy(id, chainCode); | ContractCodeDeployOperation op = CONTRACT_CODE_DEPLOY_OP_BUILDER.deploy(id, chainCode); | ||||
operationList.add(op); | operationList.add(op); | ||||
return op; | return op; | ||||
@@ -24,6 +24,12 @@ | |||||
<artifactId>commons-codec</artifactId> | <artifactId>commons-codec</artifactId> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>commons-io</groupId> | |||||
<artifactId>commons-io</artifactId> | |||||
<version>2.4</version> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>net.i2p.crypto</groupId> | <groupId>net.i2p.crypto</groupId> | ||||
<artifactId>eddsa</artifactId> | <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 !!"); | |||||
} | |||||
} | |||||
} |