diff --git a/OAT.xml b/OAT.xml
index f35a7042d43d51fe12bb04f2c8fbe3cdc08fa708..769e4fc6d1773d79b5ea592f1af90d0ee5ac67db 100644
--- a/OAT.xml
+++ b/OAT.xml
@@ -55,6 +55,8 @@
+
+
diff --git a/codesigntool/README.md b/codesigntool/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a2f1c179c321a52a3841fb08055f3153b2cab46f
--- /dev/null
+++ b/codesigntool/README.md
@@ -0,0 +1,63 @@
+# Code Signing Tool
+
+## Introduction
+
+CodeSignTool provides the code signing tool SDK for cloud-based deployment. Generally, AppGallery keys are hosted on the Public Key Infrastructure (PKI) server, and the code signing tool does not directly use the keys. Therefore, you need to manage and use the keys by yourself and integrate the code signing tool SDK, thereby implementing code signing.
+
+## Directory Structure
+
+```
+codesigntool # Signing tool
+├── code_sign_lib # Signing tool SDK
+└── code_sign_tool_lib # Signing tool core capability
+```
+
+## Constraints
+
+The code signing tool is developed in Java and must be built and run in an environment installed with JDK 8 or later.
+
+## Usage
+
+### Building
+
+1. Ensure that Maven, on which the code signing tool is built, has been installed and configured in the environment.
+
+2. Download the code, open the file directory **developtools_hapsigner/codesigntool**, and run the following command to build the code:
+
+ ```
+ mvn package
+ ```
+
+3. Find the build products in the **developtools_hapsigner/codesigntool/outputs/** directory.
+
+ ```
+ code_sign_lib.jar
+ code_sign_tool_lib.jar
+ ```
+
+### Available APIs
+
+#### API Used for Signing
+
+Bundle name: com.ohos.codesigntool.core.signtool
+
+Class name: CodeSignTool
+
+Function: boolean signCode(CodeSignServer server, String[] params)
+
+The caller must implement the signature server.
+
+Implementation reference: codesigntool/code\_sign\_lib/src/test/java/com/ohos/test/signclient/core/api/TestCodeSignServer.java
+
+Description of params:
+
+| **Parameter Command**| **Parameter Description** |
+| ------------ | ------------------------------- |
+| -inputFile | Path of the file to sign. |
+| -outputPath | Path of the signed file. |
+| -signAlg | Signing algorithm. **SHA256withECDSA** is supported. |
+| -outTree | Whether to retain the merkle tree in the signature file|
+
+## Repositories Involved
+
+**[security_code_signature](https://gitee.com/openharmony/security_code_signature)**
diff --git a/codesigntool/README_zh.md b/codesigntool/README_zh.md
new file mode 100644
index 0000000000000000000000000000000000000000..eb70172107e0dd046dab2bed2e4a956ef18ff790
--- /dev/null
+++ b/codesigntool/README_zh.md
@@ -0,0 +1,63 @@
+# 代码签名工具
+
+## 简介
+
+CodeSignTool提供用于云化部署的代码签名工具SDK。考虑应用市场的密钥一般托管于PKI服务器的场景,代码签名工具并不直接使用密钥,需由使用方集成代码签名工具SDK,完成密钥的管理与使用,以实现代码签名功能。
+
+## 目录
+
+```
+codesigntool # 签名工具
+├── code_sign_lib # 签名工具SDK接口
+└── code_sign_tool_lib # 签名工具核心能力
+```
+
+## 约束
+
+代码签名工具基于Java语言开发,需要在JDK>=8.0的环境下编译运行
+
+## 使用
+
+### 构建指导
+
+1. 该工具基于Maven构建,请确认环境已安装配置Maven工具
+
+2. 下载代码,命令行打开文件目录至 developtools\_hapsigner/codesigntool,执行命令进行编译打包
+
+ ```
+ mvn package
+ ```
+
+3. 编译产物生成于developtools\_hapsigner/codesigntool/outputs/目录下
+
+ ```
+ code_sign_lib.jar
+ code_sign_tool_lib.jar
+ ```
+
+### 接口说明
+
+#### 签名接口
+
+包名 com.ohos.codesigntool.core.signtool
+
+类名 CodeSignTool
+
+接口函数 : boolean signCode(CodeSignServer server, String[] params)
+
+1) 调用方实现签名服务器server
+
+参考实现:codesigntool/code\_sign\_lib/src/test/java/com/ohos/test/signclient/core/api/TestCodeSignServer.java
+
+2) params参列表:
+
+| **参数命令** | **参数说明** |
+| --- | --- |
+| -inputFile | 被签名的目标文件路径 |
+| -outputPath | 输出的签名文件的路径 |
+| -signAlg | 签名算法,支持SHA256withECDSA |
+| -outTree | 是否在签名文件中保留merkle tree |
+
+## 相关仓
+
+**[security_code_signature](https://gitee.com/openharmony/security_code_signature)**
diff --git a/codesigntool/code_sign_lib/pom.xml b/codesigntool/code_sign_lib/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8bd5ba22019e14d147fb8d23a728586c6dbd2f26
--- /dev/null
+++ b/codesigntool/code_sign_lib/pom.xml
@@ -0,0 +1,80 @@
+
+
+
+
+ 4.0.0
+
+
+ codesigntool
+ com.ohos.codesigntool
+ ${revision}
+
+
+ code_sign_lib
+
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ com.ohos.codesigntool
+ code_sign_tool_lib
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+
+ code_sign_lib
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.7.0
+
+ 1.8
+ 1.8
+ UTF-8
+ true
+
+ -Xlint:all
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.0.2
+
+ UTF-8
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.6
+
+ ../outputs
+
+
+
+
+
diff --git a/codesigntool/code_sign_lib/src/main/java/com/ohos/codesigntool/core/provider/RemoteCodeSignProvider.java b/codesigntool/code_sign_lib/src/main/java/com/ohos/codesigntool/core/provider/RemoteCodeSignProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..633d02dadffadcb8f9105c01c297ef5d3cf51901
--- /dev/null
+++ b/codesigntool/code_sign_lib/src/main/java/com/ohos/codesigntool/core/provider/RemoteCodeSignProvider.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.provider;
+
+import com.ohos.codesigntool.core.config.CodeSignConfig;
+import com.ohos.codesigntool.core.config.RemoteCodeSignConfig;
+
+import java.security.InvalidKeyException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * Remote online sign provider
+ *
+ * @since 2023/06/05
+ */
+public class RemoteCodeSignProvider extends CodeSignProvider {
+ @Override
+ public X509CRL getCrl() {
+ return null;
+ }
+
+ @Override
+ public CodeSignConfig createSignConfigs(List x509CertList, X509CRL crl)
+ throws InvalidKeyException {
+ CodeSignConfig signConfig = new RemoteCodeSignConfig();
+ initSignConfigs(signConfig, x509CertList, crl);
+ signConfig.setServer(this.server);
+ return signConfig;
+ }
+}
\ No newline at end of file
diff --git a/codesigntool/code_sign_lib/src/main/java/com/ohos/codesigntool/core/signtool/CodeSignTool.java b/codesigntool/code_sign_lib/src/main/java/com/ohos/codesigntool/core/signtool/CodeSignTool.java
new file mode 100644
index 0000000000000000000000000000000000000000..f0248b44acecaf98c2b6d55adf15b38f4aa9e860
--- /dev/null
+++ b/codesigntool/code_sign_lib/src/main/java/com/ohos/codesigntool/core/signtool/CodeSignTool.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.signtool;
+
+import com.ohos.codesigntool.core.api.CodeSignServer;
+import com.ohos.codesigntool.core.provider.CodeSignProvider;
+import com.ohos.codesigntool.core.provider.RemoteCodeSignProvider;
+
+/**
+ * code signature tool, defined function to sign hap file
+ *
+ * @since 2023/06/05
+ */
+public class CodeSignTool {
+ private static final String CODE_SIGN_TOOL_VERSION = "V1.0";
+
+ /**
+ * code-signing API for hap file.
+ *
+ * @param server sign server interface provided by the caller.
+ * @param params "-inputFile", "input file path",
+ * "-outputPath", "output signature file path",
+ * "-signAlg", "SHA256withECDSA",
+ * "-outTree" , "true/false"
+ * @return true, if sign successfully.
+ */
+ public static boolean signCode(CodeSignServer server, String[] params) {
+ CodeSignProvider codeSignProvider = new RemoteCodeSignProvider();
+ codeSignProvider.setCodeSignServer(server);
+ return codeSignProvider.sign(params);
+ }
+
+ /**
+ * Get version of tool.
+ *
+ * @return version of jar.
+ */
+ public static String getVersion() {
+ return CODE_SIGN_TOOL_VERSION;
+ }
+}
diff --git a/codesigntool/code_sign_lib/src/test/java/com/ohos/test/signclient/core/api/TestCodeSignServer.java b/codesigntool/code_sign_lib/src/test/java/com/ohos/test/signclient/core/api/TestCodeSignServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..d64662cf7d2e42faaae7341803583076ec5f68d5
--- /dev/null
+++ b/codesigntool/code_sign_lib/src/test/java/com/ohos/test/signclient/core/api/TestCodeSignServer.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.test.signclient.core.api;
+
+import com.google.gson.Gson;
+import com.ohos.codesigntool.core.api.CodeSignServer;
+import com.ohos.codesigntool.core.response.DataFromAppGallaryServer;
+import com.ohos.codesigntool.core.response.DataFromSignCenterServer;
+import com.ohos.codesigntool.core.utils.CertUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Base64;
+import java.util.List;
+
+/**
+ * class implements CodeSignServer, use in test.
+ *
+ * @since 2023/06/05
+ */
+public class TestCodeSignServer implements CodeSignServer {
+ private static final Logger LOGGER = LogManager.getLogger(TestCodeSignServer.class);
+ private static final String SEPARATOR = File.separator;
+ private static final String KEYSTORE =
+ "src" + SEPARATOR + "test" + SEPARATOR + "resources" + SEPARATOR + "cs_cert" + SEPARATOR + "cstest.jks";
+ private static final String CERTPATH =
+ "src" + SEPARATOR + "test" + SEPARATOR + "resources" + SEPARATOR + "cs_cert" + SEPARATOR + "chain.pem";
+ private static final String KEYSTORE_CODE = "123456";
+ private static final String KEYALIAS = "oh_code_sign_test";
+ private static final String KEYALIAS_CODE = "123456";
+
+ private class ResponseJson extends DataFromAppGallaryServer {
+ ResponseJson(String code, String message, String signedData, String[] certchain, String crl) {
+ DataFromSignCenterServer dataFromServer = new DataFromSignCenterServer();
+ dataFromServer.setSignedData(signedData);
+ dataFromServer.setCertchain(certchain);
+ dataFromServer.setCrl(crl);
+ setCodeSignature(code);
+ setMessage(message);
+ setDataFromSignCenterServer(dataFromServer);
+ }
+ }
+
+ @Override
+ public String getSignature(byte[] data, String signatureAlg) {
+ List publicCertList = CertUtils.getCertChainsFromFile(CERTPATH);
+ if (publicCertList == null) {
+ LOGGER.error("public certs is null!");
+ return "";
+ }
+ String[] certchain;
+ try {
+ certchain = getCertchain(publicCertList);
+ } catch (CertificateEncodingException e) {
+ LOGGER.error("get certchain failed!", e);
+ return "";
+ }
+ byte[] signData = getSignature(data, signatureAlg, null);
+ String code = "success";
+ String message = "sign successfully";
+ String signedData = null;
+ if (signData == null) {
+ code = "fail";
+ message = "sign failed";
+ } else {
+ signedData = Base64.getUrlEncoder().encodeToString(signData);
+ }
+ ResponseJson ret = new ResponseJson(code, message, signedData, certchain, "");
+ String jsonObject = new Gson().toJson(ret);
+ LOGGER.info(jsonObject);
+ return jsonObject;
+ }
+
+ private String[] getCertchain(List certList)
+ throws CertificateEncodingException {
+ int certListSize = certList.size();
+ String[] certchain = new String[certListSize];
+ for (int i = 0; i < certListSize; i++) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("-----BEGIN CERTIFICATE-----")
+ .append(System.lineSeparator())
+ .append(Base64.getEncoder().encodeToString(certList.get(i).getEncoded()))
+ .append("-----END CERTIFICATE-----")
+ .append(System.lineSeparator());
+ certchain[i] = builder.toString();
+ }
+ return certchain;
+ }
+
+ private byte[] getSignature(byte[] data, String signAlgName, AlgorithmParameterSpec algParamValue) {
+ LOGGER.info("Compute signature by {}", this.getClass().getName());
+ byte[] signBytes = null;
+ try {
+ PrivateKey privateKey = getPrivateKeyFromKeyStore();
+ if (privateKey == null) {
+ return signBytes;
+ }
+ signBytes = getSignature(data, signAlgName, privateKey, algParamValue);
+ } catch (InvalidAlgorithmParameterException
+ | InvalidKeyException
+ | NoSuchAlgorithmException
+ | NoSuchProviderException
+ | SignatureException e) {
+ LOGGER.error("get Signature failed!", e);
+ }
+ return signBytes;
+ }
+
+ private PrivateKey getPrivateKeyFromKeyStore() {
+ PrivateKey privateKey = null;
+ try (FileInputStream fileStream = new FileInputStream(KEYSTORE)) {
+ KeyStore keyStore = KeyStore.getInstance("JKS");
+ keyStore.load(fileStream, KEYSTORE_CODE.toCharArray());
+ Object obj = keyStore.getKey(KEYALIAS, KEYALIAS_CODE.toCharArray());
+ if (!(obj instanceof PrivateKey)) {
+ LOGGER.error("key from keystore can not be converted to PrivateKey");
+ return null;
+ }
+ privateKey = (PrivateKey) obj;
+ } catch (CertificateException
+ | IOException
+ | KeyStoreException
+ | NoSuchAlgorithmException
+ | UnrecoverableKeyException e) {
+ LOGGER.error("get private key from keystore failed!", e);
+ }
+ return privateKey;
+ }
+
+ private byte[] getSignature(byte[] data, String signAlgName, PrivateKey privateKey,
+ AlgorithmParameterSpec algParamValue) throws NoSuchProviderException,
+ NoSuchAlgorithmException, InvalidKeyException,
+ InvalidAlgorithmParameterException, SignatureException {
+ Signature sign = Signature.getInstance(signAlgName, "BC");
+ sign.initSign(privateKey);
+ if (algParamValue != null) {
+ sign.setParameter(algParamValue);
+ }
+ sign.update(data);
+ return sign.sign();
+ }
+}
\ No newline at end of file
diff --git a/codesigntool/code_sign_lib/src/test/java/com/ohos/test/signclient/core/signtool/CodeSignToolTest.java b/codesigntool/code_sign_lib/src/test/java/com/ohos/test/signclient/core/signtool/CodeSignToolTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f25dac2302da7c3b02a4e2f19e796f450a2f9667
--- /dev/null
+++ b/codesigntool/code_sign_lib/src/test/java/com/ohos/test/signclient/core/signtool/CodeSignToolTest.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.test.signclient.core.signtool;
+
+import com.ohos.codesigntool.core.signtool.CodeSignTool;
+import com.ohos.codesigntool.core.utils.ParamConstants;
+import com.ohos.test.signclient.core.api.TestCodeSignServer;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * CodeSignTool Tester.
+ *
+ * @since 2023/06/05
+ */
+public class CodeSignToolTest {
+ private static final String SEPARATOR = File.separator;
+ private static final File TMP_DIR = new File("target");
+ private static final int MIN_DATA_CHUNK_SIZE = 4096;
+ private static final int MAX_DATA_CHUNK_SIZE = 1024 * 1024 * 2;
+ private static final int MIN_ENTRY_COUNT = 1;
+ private static final int MAX_ENTRY_COUNT = 16;
+ private static final int CONCURRENT_TASK_COUNT = 10;
+ private static final int CONCURRENT_TASK_EXECUTE_TIME = 30;
+ private static final int CONCURRENT_TASK_TYPE_HAP = 1;
+ private static final String DEFAULT_SIGN_ALG = "SHA256withECDSA";
+ private static final String TMP_UNSIGNED_FILE_PREFIX = "unsigned-";
+ private static final String TMP_SIGNED_FILE_PREFIX = "signed-";
+ private static final String TMP_HAP_FILE_SUFFIX = ".hap";
+ private static final String OUTPUT_MERKLE_TREE = "true";
+ private static final String TEST_TARGET_HAPS_BASE =
+ "src" + SEPARATOR + "test" + SEPARATOR + "resources" + SEPARATOR + "haps";
+ private static final List MODULE_TYPES = new ArrayList<>();
+ private static final String STAGE_MODULE = "STAGE";
+ private static final String FA_MODULE = "FA";
+ private static final List COMPRESS_NATIVE_LIB_OPTIONS = new ArrayList<>();
+ private static final String TRUE_OPTION = "true";
+ private static final String FALSE_OPTION = "false";
+ private static final String NONE_OPTION = "none";
+ private static final Map OPTIONS_AND_LIB_SIGNATURE_MAP = new HashMap<>();
+ private static final String SO_SUFFIX = ".so";
+ private static final String HAP_SUFFIX = ".hap";
+ private static final String SIG_SUFFIX = ".sig";
+ private static final String SINGLE_SIG_SUFFIX = ".fsv-sig";
+ private static List tmpSources;
+ private static String tmpOutputPath;
+
+ static {
+ MODULE_TYPES.add(STAGE_MODULE);
+ MODULE_TYPES.add(FA_MODULE);
+ COMPRESS_NATIVE_LIB_OPTIONS.add(TRUE_OPTION);
+ COMPRESS_NATIVE_LIB_OPTIONS.add(FALSE_OPTION);
+ COMPRESS_NATIVE_LIB_OPTIONS.add(NONE_OPTION);
+ OPTIONS_AND_LIB_SIGNATURE_MAP.put(TRUE_OPTION, true);
+ OPTIONS_AND_LIB_SIGNATURE_MAP.put(FALSE_OPTION, false);
+ OPTIONS_AND_LIB_SIGNATURE_MAP.put(NONE_OPTION, true);
+ }
+
+ /**
+ * Init template resources container.
+ */
+ @BeforeClass
+ public static void initTmpResourcesContainer() {
+ tmpSources = new CopyOnWriteArrayList<>();
+ try {
+ tmpOutputPath = Files.createTempDirectory(TMP_DIR.toPath(), TMP_SIGNED_FILE_PREFIX).toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Clean template resources.
+ */
+ @AfterClass
+ public static void cleanTmpResources() {
+ tmpSources.forEach(Cleanable::clean);
+ File dir = new File(tmpOutputPath);
+ File[] files = dir.listFiles();
+ for (File file : files) {
+ file.delete();
+ }
+ dir.delete();
+ }
+
+ /**
+ * Test sign hap.
+ *
+ * @throws Exception on error.
+ */
+ @Test
+ public void testSignHap() throws Exception {
+ File unsignedHap = File.createTempFile(TMP_UNSIGNED_FILE_PREFIX, TMP_HAP_FILE_SUFFIX, TMP_DIR);
+ tmpSources.add(new CleanableFile(unsignedHap));
+ testSignCode(unsignedHap, true);
+ }
+
+ /**
+ * Test sign file that does not exist.
+ *
+ * @throws Exception on error.
+ */
+ @Test
+ public void testInvalidInputFile() throws Exception {
+ Params param = new Params();
+ param.addParam(ParamConstants.PARAM_BASIC_INPUT_FILE, "invalid");
+ param.addParam(ParamConstants.PARAM_BASIC_OUTPUT_PATH, tmpOutputPath);
+ param.addParam(ParamConstants.PARAM_BASIC_SIGN_ALG, DEFAULT_SIGN_ALG);
+
+ String[] params = param.toArray();
+ TestCodeSignServer server = new TestCodeSignServer();
+ Assert.assertFalse("sign nonexistent file", CodeSignTool.signCode(server, params));
+ }
+
+ /**
+ * Test sign and store merkle tree in ouput file.
+ *
+ * @throws Exception on error
+ */
+ @Test
+ public void testSignHapAndStoreTree() throws Exception {
+ testSignCodeWithTree();
+ }
+
+ /**
+ * Test sign code on multi-thread.
+ *
+ * @throws Exception on error
+ */
+ @Test
+ public void testSignCodeConcurrent() throws Exception {
+ executeConcurrentTask(CONCURRENT_TASK_TYPE_HAP);
+ }
+
+ private void executeConcurrentTask(int taskType) throws Exception {
+ CountDownLatch countDownLatch = new CountDownLatch(CONCURRENT_TASK_COUNT);
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(CONCURRENT_TASK_COUNT,
+ CONCURRENT_TASK_COUNT, 30, TimeUnit.SECONDS,
+ new ArrayBlockingQueue<>(CONCURRENT_TASK_COUNT),
+ new ThreadPoolExecutor.CallerRunsPolicy());
+ List> futures = new ArrayList<>(CONCURRENT_TASK_COUNT);
+ List> tasks = new ArrayList<>(CONCURRENT_TASK_COUNT);
+ for (int i = 0; i < CONCURRENT_TASK_COUNT; i++) {
+ if (taskType == CONCURRENT_TASK_TYPE_HAP) {
+ tasks.add(generateSignCodeTask(countDownLatch));
+ }
+ }
+ for (Callable task : tasks) {
+ Future future = executor.submit(task);
+ futures.add(future);
+ }
+ executor.shutdown();
+ boolean isFinished = countDownLatch.await(CONCURRENT_TASK_EXECUTE_TIME, TimeUnit.SECONDS);
+ if (!isFinished) {
+ executor.shutdownNow();
+ }
+ Assert.assertTrue("some task not finished in " + CONCURRENT_TASK_EXECUTE_TIME + "seconds.", isFinished);
+ for (Future future : futures) {
+ Boolean isSuccess = future.get();
+ Assert.assertNotNull("task not finish or error", isSuccess);
+ Assert.assertTrue("task failed", isSuccess);
+ }
+ }
+
+ private Callable generateSignCodeTask(CountDownLatch countDownLatch) throws IOException {
+ File inputFile = File.createTempFile(TMP_UNSIGNED_FILE_PREFIX, TMP_HAP_FILE_SUFFIX, TMP_DIR);
+ tmpSources.add(new CleanableFile(inputFile));
+ fillHapFile(inputFile);
+ return new MultiSignCodeTask(inputFile, tmpOutputPath, countDownLatch);
+ }
+
+ private void testSignCode(File unsignedHap, boolean fill) throws Exception {
+ if (fill) {
+ fillHapFile(unsignedHap);
+ }
+
+ Params param = new Params();
+ param.addParam(ParamConstants.PARAM_BASIC_INPUT_FILE, unsignedHap.getPath());
+ param.addParam(ParamConstants.PARAM_BASIC_OUTPUT_PATH, tmpOutputPath);
+ param.addParam(ParamConstants.PARAM_BASIC_SIGN_ALG, DEFAULT_SIGN_ALG);
+
+ String[] params = param.toArray();
+ TestCodeSignServer server = new TestCodeSignServer();
+ Assert.assertTrue("sign code failed", CodeSignTool.signCode(server, params));
+ CodeSignVerify codeSignVerify = new CodeSignVerify();
+ Assert.assertTrue("verify code failed", codeSignVerify.verifyCode(
+ unsignedHap.getPath(), spliceFilePath(tmpOutputPath, unsignedHap.getName())));
+ }
+
+ private static String spliceFilePath(String outputDir, String hapName) {
+ String outputPath = outputDir;
+ if (!outputPath.endsWith(File.separator)) {
+ outputPath += File.separator;
+ }
+ return new String(outputPath + hapName + SIG_SUFFIX);
+ }
+
+ private void testSignCodeWithTree() throws Exception {
+ File unsignedHap = File.createTempFile(TMP_UNSIGNED_FILE_PREFIX, TMP_HAP_FILE_SUFFIX, TMP_DIR);
+ tmpSources.add(new CleanableFile(unsignedHap));
+
+ fillHapFile(unsignedHap);
+
+ Params param = new Params();
+ param.addParam(ParamConstants.PARAM_BASIC_INPUT_FILE, unsignedHap.getPath());
+ param.addParam(ParamConstants.PARAM_BASIC_OUTPUT_PATH, tmpOutputPath);
+ param.addParam(ParamConstants.PARAM_BASIC_SIGN_ALG, DEFAULT_SIGN_ALG);
+ param.addParam(ParamConstants.PARAM_OUTPUT_MEKLE_TREE, OUTPUT_MERKLE_TREE);
+
+ String[] params = param.toArray();
+ TestCodeSignServer server = new TestCodeSignServer();
+ Assert.assertTrue("sign code failed", CodeSignTool.signCode(server, params));
+ CodeSignVerify codeSignVerify = new CodeSignVerify();
+ Assert.assertTrue("verify code failed", codeSignVerify.verifyCode(
+ unsignedHap.getPath(), spliceFilePath(tmpOutputPath, unsignedHap.getName())));
+ }
+
+ /**
+ * Test sign code with option `CompressiveNativeLibs`
+ *
+ * @throws Exception an error.
+ */
+ @Test
+ public void testSignHapWithLibOptions() throws Exception {
+ for (String module : MODULE_TYPES) {
+ for (String option : COMPRESS_NATIVE_LIB_OPTIONS) {
+ String hapPath = TEST_TARGET_HAPS_BASE + SEPARATOR + module + SEPARATOR + option + HAP_SUFFIX;
+ testSignCode(new File(hapPath), false);
+ String outFile = tmpOutputPath + SEPARATOR + option + HAP_SUFFIX + SIG_SUFFIX;
+ CheckNativeLibSignature check = new CheckNativeLibSignature(outFile);
+ Assert.assertEquals("compress libs check failed",
+ check.checkLibDirInSignature(), OPTIONS_AND_LIB_SIGNATURE_MAP.get(option));
+ }
+ }
+ }
+
+ /**
+ * Test Method: getVersion()
+ *
+ * @throws Exception on error.
+ */
+ @Test
+ public void testGetVersion() throws Exception {
+ Assert.assertNotNull(CodeSignTool.getVersion());
+ }
+
+ /**
+ * Test unsupported sign algorithm
+ *
+ * @throws Exception on error.
+ */
+ @Test
+ public void testUnSupportedSignAlgorithm() throws Exception {
+ File unsignedHap = File.createTempFile(TMP_UNSIGNED_FILE_PREFIX, TMP_HAP_FILE_SUFFIX, TMP_DIR);
+ tmpSources.add(new CleanableFile(unsignedHap));
+ fillHapFile(unsignedHap);
+ Params param = new Params();
+ param.addParam(ParamConstants.PARAM_BASIC_INPUT_FILE, unsignedHap.getPath());
+ param.addParam(ParamConstants.PARAM_BASIC_OUTPUT_PATH, tmpOutputPath);
+ param.addParam(ParamConstants.PARAM_BASIC_SIGN_ALG, ParamConstants.SIG_ALGORITHM_SHA256_RSA);
+ String[] params = param.toArray();
+ TestCodeSignServer server = new TestCodeSignServer();
+ Assert.assertFalse("sign using unsupported algorithm", CodeSignTool.signCode(server, params));
+ }
+
+ private byte[] generateChunkBytes() {
+ Random random = new Random();
+ int size = Math.max(MIN_DATA_CHUNK_SIZE, random.nextInt(MAX_DATA_CHUNK_SIZE + 1));
+ byte[] bytes = new byte[size];
+ random.nextBytes(bytes);
+ return bytes;
+ }
+
+ private String generateEntryName() {
+ return new BigInteger(Long.SIZE, new Random()) + SO_SUFFIX;
+ }
+
+ private void fillHapFile(File file) throws IOException {
+ try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file))) {
+ Random random = new Random();
+ int entryCount = Math.max(MIN_ENTRY_COUNT, random.nextInt(MAX_ENTRY_COUNT + 1));
+ for (int i = 0; i < entryCount; i++) {
+ ZipEntry zipEntry = new ZipEntry(generateEntryName());
+ out.putNextEntry(zipEntry);
+ out.write(generateChunkBytes());
+ }
+ }
+ }
+
+ private interface Cleanable {
+ /**
+ * Clean template resources.
+ */
+ void clean();
+ }
+
+ private static class CheckNativeLibSignature {
+
+ private static final String LIB_DIR_NAME = "libs";
+ private static final String SO_SIG_SUFFIX = SO_SUFFIX + SINGLE_SIG_SUFFIX;
+
+ private final File inputFile;
+
+ public CheckNativeLibSignature(String filePath) {
+ inputFile = new File(filePath);
+
+ }
+
+ /**
+ * Check whether the signature file contains signature of native libs
+ *
+ * @return true if the file contains signature of native libs
+ */
+ public boolean checkLibDirInSignature() {
+ try (JarFile inputJar = new JarFile(inputFile, false)) {
+ for (Enumeration e = inputJar.entries(); e.hasMoreElements();) {
+ JarEntry entry = e.nextElement();
+ if (entry.getName().startsWith(LIB_DIR_NAME) && entry.getName().endsWith(SO_SIG_SUFFIX)) {
+ return true;
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+ }
+
+ private static class MultiSignCodeTask implements Callable {
+ private final File inputFile;
+ private final String outputPath;
+ private final CountDownLatch countDownLatch;
+
+ /**
+ * MultiSignCodeTask constructor.
+ *
+ * @param inHap input hap file
+ * @param outPath signed output file path
+ * @param countDownLatch count down latch on multi-thread
+ */
+ public MultiSignCodeTask(File inHap, String outPath, CountDownLatch countDownLatch) {
+ this.inputFile = inHap;
+ this.outputPath = outPath;
+ this.countDownLatch = countDownLatch;
+ }
+
+ @Override
+ public Boolean call() {
+ try {
+ Params params = new Params();
+ TestCodeSignServer server = new TestCodeSignServer();
+ params.addParam(ParamConstants.PARAM_BASIC_INPUT_FILE, inputFile.getPath());
+ params.addParam(ParamConstants.PARAM_BASIC_OUTPUT_PATH, outputPath);
+ params.addParam(ParamConstants.PARAM_BASIC_SIGN_ALG, DEFAULT_SIGN_ALG);
+ CodeSignVerify codeSignVerify = new CodeSignVerify();
+ return (CodeSignTool.signCode(server, params.toArray()) &
+ codeSignVerify.verifyCode(inputFile.getPath(), spliceFilePath(outputPath, inputFile.getName())));
+ } finally {
+ countDownLatch.countDown();
+ }
+ }
+ }
+
+ private static class Params {
+ private final Set params = new HashSet<>();
+
+ /**
+ * Add param.
+ *
+ * @param name param name
+ * @param value param value
+ */
+ public void addParam(String name, String value) {
+ params.add(new Param(name, value));
+ }
+
+ /**
+ * Parse to String[] params.
+ *
+ * @return String[] params
+ */
+ public String[] toArray() {
+ List paramList = new ArrayList<>(params.size());
+ for (Param param : params) {
+ paramList.add(param.getKey());
+ paramList.add(param.getValue());
+ }
+ return paramList.toArray(new String[0]);
+ }
+ }
+
+ private static class Param {
+ private static final String DEFAULT_PREFIX = "-";
+
+ private final String name;
+ private final String value;
+ private final String prefix;
+
+ public Param(String name, String value) {
+ this(name, value, DEFAULT_PREFIX);
+ }
+
+ public Param(String name, String value, String prefix) {
+ this.name = name;
+ this.value = value;
+ this.prefix = prefix;
+ }
+
+ /**
+ * Get parameter key.
+ *
+ * @return parameter key
+ */
+ public String getKey() {
+ return prefix + name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ if (other instanceof Param) {
+ Param param = (Param) other;
+ return Objects.equals(name, param.name);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+ }
+
+ private static class CleanableFile implements Cleanable {
+ private final File file;
+
+ public CleanableFile(File file) {
+ this.file = file;
+ }
+
+ @Override
+ public void clean() {
+ if (file != null && file.exists() && file.isFile()) {
+ try {
+ Files.delete(file.toPath());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+}
diff --git a/codesigntool/code_sign_lib/src/test/java/com/ohos/test/signclient/core/signtool/CodeSignVerify.java b/codesigntool/code_sign_lib/src/test/java/com/ohos/test/signclient/core/signtool/CodeSignVerify.java
new file mode 100644
index 0000000000000000000000000000000000000000..2fe646d82bbd4b1f47ce749acef491711a165095
--- /dev/null
+++ b/codesigntool/code_sign_lib/src/test/java/com/ohos/test/signclient/core/signtool/CodeSignVerify.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.test.signclient.core.signtool;
+
+import com.ohos.codesigntool.core.exception.FsVerityDigestException;
+import com.ohos.codesigntool.core.fsverity.FsVerityGenerator;
+import com.ohos.codesigntool.core.utils.CmsUtils;
+import com.ohos.codesigntool.core.utils.InputStreamUtils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.cms.CMSException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * HapSigVerify.
+ *
+ * @since 2023/07/1
+ */
+public class CodeSignVerify {
+ private static final Logger LOGGER = LogManager.getLogger(CodeSignVerify.class);
+ private static final String SO_SUFFIX = ".so";
+ private static final String AN_SUFFIX = ".an";
+ private static final String SINGLE_SIG_SUFFIX = ".fsv-sig";
+ private static final String HAP_SIGNATURE_ENTRY_NAME = "Hap";
+ private static final List EXTRACTED_NATIVE_LIB_SUFFIXS = new ArrayList<>();
+ private static final List EXTRACTED_SIG_SUFFIXS = new ArrayList<>();
+
+ static {
+ EXTRACTED_NATIVE_LIB_SUFFIXS.add(AN_SUFFIX);
+ EXTRACTED_NATIVE_LIB_SUFFIXS.add(SO_SUFFIX);
+ EXTRACTED_SIG_SUFFIXS.add(SINGLE_SIG_SUFFIX);
+ }
+
+ private Map mapHapData = new HashMap();
+ private Map mapSignData = new HashMap();
+
+ /**
+ * Verify binary files contained within the file
+ *
+ * @param hapPath input hap file path
+ * @param signPath input sign file path
+ * @return verify result
+ */
+ public boolean verifyCode(String hapPath, String signPath) {
+ boolean verifyCode = false;
+ try {
+ generateMapData(new File(hapPath), false);
+ generateMapData(new File(signPath), true);
+ verifyCode = verifyMapData();
+ } catch (CMSException | IOException | FsVerityDigestException e) {
+ LOGGER.error("verify code failed!", e);
+ }
+ return verifyCode;
+ }
+
+ private void generateMapData(File file, boolean isSignFile)throws IOException, FsVerityDigestException {
+ if (isSignFile) {
+ mapSignData.clear();
+ } else {
+ mapHapData.clear();
+ try (FileInputStream inputStream = new FileInputStream(file)) {
+ mapHapData.put(HAP_SIGNATURE_ENTRY_NAME,
+ generateFsVerityDigest(inputStream, file.length()));
+ }
+ }
+ try (JarFile inputJar = new JarFile(file, false)) {
+ List entryNames = getTargetEntries(inputJar, isSignFile);
+ if (entryNames.isEmpty()) {
+ return;
+ }
+ if (isSignFile) {
+ generateMapSignData(entryNames, inputJar);
+ } else {
+ generateMapHapData(entryNames, inputJar);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void generateMapHapData(List entryNames, JarFile hap) throws IOException, FsVerityDigestException {
+ for (String name : entryNames) {
+ JarEntry inEntry = hap.getJarEntry(name);
+ try (InputStream inputStream = hap.getInputStream(inEntry)) {
+ mapHapData.put(name, generateFsVerityDigest(inputStream, inEntry.getSize()));
+ }
+ }
+ }
+
+ private byte[] generateFsVerityDigest(InputStream inputStream, long size) throws FsVerityDigestException {
+ FsVerityGenerator fsVerityGenerator = new FsVerityGenerator();
+ fsVerityGenerator.generateFsVerityDigest(inputStream, size);
+ return fsVerityGenerator.getFsVerityDigest();
+ }
+
+ private void generateMapSignData(List entryNames, JarFile hap) throws IOException, FsVerityDigestException {
+ for (String name : entryNames) {
+ JarEntry inEntry = hap.getJarEntry(name);
+ try (InputStream data = hap.getInputStream(inEntry)) {
+ byte[] signDigest = InputStreamUtils.toByteArray(data, (int) inEntry.getSize());
+ mapSignData.put(name, signDigest);
+ }
+ }
+ }
+
+ private boolean verifyMapData() throws CMSException {
+ for (Map.Entry entry : mapSignData.entrySet()) {
+ String signKey = entry.getKey();
+ int index = signKey.indexOf(SINGLE_SIG_SUFFIX);
+ if (index == -1) {
+ throw new CMSException("Sign file name err:" + signKey);
+ }
+ String hapKey = signKey.substring(0, index);
+ boolean verifyResult = CmsUtils.verifySignDataWithUnsignedDataDigest(
+ mapHapData.get(hapKey), entry.getValue());
+ if (!verifyResult) {
+ throw new CMSException("PKCS cms data did not verify");
+ }
+ }
+ return true;
+ }
+
+ private List getTargetEntries(JarFile file, boolean isSignFile) {
+ List result = new ArrayList<>();
+ for (Enumeration e = file.entries(); e.hasMoreElements();) {
+ JarEntry entry = e.nextElement();
+ if (!entry.isDirectory()) {
+ if (!isTargetType(entry.getName(), isSignFile)) {
+ continue;
+ }
+ result.add(entry.getName());
+ }
+ }
+ return result;
+ }
+
+ private boolean isTargetType(String entryName, boolean isSignFile) {
+ List stringList;
+ if (isSignFile) {
+ stringList = EXTRACTED_SIG_SUFFIXS;
+ } else {
+ stringList = EXTRACTED_NATIVE_LIB_SUFFIXS;
+ }
+ for (String suffix : stringList) {
+ if (entryName.endsWith(suffix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/codesigntool/code_sign_lib/src/test/resources/cs_cert/chain.pem b/codesigntool/code_sign_lib/src/test/resources/cs_cert/chain.pem
new file mode 100644
index 0000000000000000000000000000000000000000..1b7875dace4f93405e14261ac80a674450e04097
--- /dev/null
+++ b/codesigntool/code_sign_lib/src/test/resources/cs_cert/chain.pem
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIC1TCCAlugAwIBAgIUQaPZEzo/Zt+vphkwQCn1Ipd3YVYwCgYIKoZIzj0EAwIw
+YzELMAkGA1UEBhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UECwwKSHVhd2Vp
+IENCRzEuMCwGA1UEAwwlSGFybW9ueU9TIENvZGUgU2lnbmF0dXJlIFJvb3QgQ0Eg
+VGVzdDAeFw0yMzA0MjMxMjU1MDhaFw0zMzA0MjAxMjU1MDhaMGMxCzAJBgNVBAYT
+AkNOMQ8wDQYDVQQKDAZIdWF3ZWkxEzARBgNVBAsMCkh1YXdlaSBDQkcxLjAsBgNV
+BAMMJUhhcm1vbnlPUyBDb2RlIFNpZ25hdHVyZSBSb290IENBIFRlc3QwdjAQBgcq
+hkjOPQIBBgUrgQQAIgNiAAQOLq0yERrODcj6ovg6vfjhnQOrbMmLZCeHycXAgUPh
+zLnGOQKd9K1BNHJQVm2EKUmL/tZqkLPuvchH3eyCzRd2t5AxHTXu8JOUQofs/FFK
+RzcCKLuGoWuxLGkULt3CzSmjgc8wgcwwgYkGA1UdIwSBgTB/oWekZTBjMQswCQYD
+VQQGEwJDTjEPMA0GA1UECgwGSHVhd2VpMRMwEQYDVQQLDApIdWF3ZWkgQ0JHMS4w
+LAYDVQQDDCVIYXJtb255T1MgQ29kZSBTaWduYXR1cmUgUm9vdCBDQSBUZXN0ghRB
+o9kTOj9m36+mGTBAKfUil3dhVjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFCei
+PbomVCZZKoOG2pIn5E3XDaDQMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwID
+aAAwZQIwDjg7eVeyeeDuhvIDh5UvKcCwCxz/BFJx7c8zq+GyhXNkh2j1uYP8pTLG
+7a28b0BdAjEAj65RKIPtjfNacJa49j/Rqu4FNSjS7oQ7akn9n7ckNGDVzuq6O4I/
+jscF0b6WoP9o
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICjzCCAhagAwIBAgIUSblP8XU4Qo9ZjSQ8lKBTKzuIIEUwCgYIKoZIzj0EAwIw
+YzELMAkGA1UEBhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UECwwKSHVhd2Vp
+IENCRzEuMCwGA1UEAwwlSGFybW9ueU9TIENvZGUgU2lnbmF0dXJlIFJvb3QgQ0Eg
+VGVzdDAeFw0yMzA0MjMxMjU1MjRaFw0zMzA0MjAxMjU1MjRaMHkxCzAJBgNVBAYT
+AkNOMRQwEgYDVQQKDAtPcGVuSGFybW9ueTEeMBwGA1UECwwVT3Blbkhhcm1vbnkg
+Q29tbXVuaXR5MTQwMgYDVQQDDCtPcGVuSGFybW9ueSBBcHBsaWNhdGlvbiBDb2Rl
+IFNpZ25hdHVyZSBUZXN0MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEg1t25S3a+z9/
+MUtBHPnt3LI62xmF1NBmX6xbpjw+q4hGqHii1nHZ8onn6lWiX3qeLjsZsn/CfPeY
+oebNXIgvkqx7fXvvB8HP6f8c85K6Dtl9PZc3qo/KRa1Qivm9I/nUo3UwczAfBgNV
+HSMEGDAWgBQnoj26JlQmWSqDhtqSJ+RN1w2g0DAdBgNVHQ4EFgQUEsl6AUobaB/e
+ouEnvRIiF4WxHXcwCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/
+BAwwCgYIKwYBBQUHAwMwCgYIKoZIzj0EAwIDZwAwZAIwRAkNKX5gg+7026bP0yJE
+cQaW0dEPnG6FozYY8PZ1WHZ/ZENZWVDnX55y2D3VaHQ6AjAFds6rnwwYjgX5zDC4
+J8SPp9ee5HwKQLy2Pgv3g0XDYWBS2nIRrCvxgBR/hbk+/ek=
+-----END CERTIFICATE-----
diff --git a/codesigntool/code_sign_lib/src/test/resources/cs_cert/cstest.jks b/codesigntool/code_sign_lib/src/test/resources/cs_cert/cstest.jks
new file mode 100644
index 0000000000000000000000000000000000000000..25ad454bc1fe531784b1065c2538b4509a498ebd
Binary files /dev/null and b/codesigntool/code_sign_lib/src/test/resources/cs_cert/cstest.jks differ
diff --git a/codesigntool/code_sign_lib/src/test/resources/haps/FA/false.hap b/codesigntool/code_sign_lib/src/test/resources/haps/FA/false.hap
new file mode 100644
index 0000000000000000000000000000000000000000..60eb3305f4a59cdb12c9eaec0effee3103ef98fa
Binary files /dev/null and b/codesigntool/code_sign_lib/src/test/resources/haps/FA/false.hap differ
diff --git a/codesigntool/code_sign_lib/src/test/resources/haps/FA/none.hap b/codesigntool/code_sign_lib/src/test/resources/haps/FA/none.hap
new file mode 100644
index 0000000000000000000000000000000000000000..6658c74c516a9e7c29f220d2b55fe34faad60213
Binary files /dev/null and b/codesigntool/code_sign_lib/src/test/resources/haps/FA/none.hap differ
diff --git a/codesigntool/code_sign_lib/src/test/resources/haps/FA/true.hap b/codesigntool/code_sign_lib/src/test/resources/haps/FA/true.hap
new file mode 100644
index 0000000000000000000000000000000000000000..fe931367c2feb87b75efd4a36e4d0532f6a0abe7
Binary files /dev/null and b/codesigntool/code_sign_lib/src/test/resources/haps/FA/true.hap differ
diff --git a/codesigntool/code_sign_lib/src/test/resources/haps/STAGE/false.hap b/codesigntool/code_sign_lib/src/test/resources/haps/STAGE/false.hap
new file mode 100644
index 0000000000000000000000000000000000000000..3c9ce444ba43f2e05bd1c53d3b6f2d54a6876624
Binary files /dev/null and b/codesigntool/code_sign_lib/src/test/resources/haps/STAGE/false.hap differ
diff --git a/codesigntool/code_sign_lib/src/test/resources/haps/STAGE/none.hap b/codesigntool/code_sign_lib/src/test/resources/haps/STAGE/none.hap
new file mode 100644
index 0000000000000000000000000000000000000000..8814c1f87c8ceaf040d96d9b0fe72c662abc0920
Binary files /dev/null and b/codesigntool/code_sign_lib/src/test/resources/haps/STAGE/none.hap differ
diff --git a/codesigntool/code_sign_lib/src/test/resources/haps/STAGE/true.hap b/codesigntool/code_sign_lib/src/test/resources/haps/STAGE/true.hap
new file mode 100644
index 0000000000000000000000000000000000000000..093c309f9a59f7f1c982f4e12f3063268f37c4c5
Binary files /dev/null and b/codesigntool/code_sign_lib/src/test/resources/haps/STAGE/true.hap differ
diff --git a/codesigntool/code_sign_tool_lib/pom.xml b/codesigntool/code_sign_tool_lib/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..14c3ff543c19b1c1329c9af56478744bac6503e5
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/pom.xml
@@ -0,0 +1,91 @@
+
+
+
+ 4.0.0
+
+
+ codesigntool
+ com.ohos.codesigntool
+ ${revision}
+
+ code_sign_tool_lib
+
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+ 1.69
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ com.google.code.gson
+ gson
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ com.mikesamuel
+ json-sanitizer
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+
+ code_sign_tool_lib
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.7.0
+
+ 1.8
+ 1.8
+ UTF-8
+ true
+
+ -Xlint:all
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.0.2
+
+ UTF-8
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.6
+
+ ../outputs
+
+
+
+
+
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/api/CodeSignServer.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/api/CodeSignServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..cbf5a031b2b4e59bda35514d346024c7f0ee536f
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/api/CodeSignServer.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.api;
+
+/**
+ * sign code use remotesign online
+ *
+ * @since 2023/06/05
+ */
+public interface CodeSignServer {
+ /**
+ * interface of get signature from server
+ *
+ * @param data unsigned data
+ * @param signatureAlg Signature Algorithms
+ * @return signed data
+ */
+ String getSignature(byte[] data, String signatureAlg);
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/config/CodeSignConfig.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/config/CodeSignConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..500892ef285b87cf03a0777b3458ee21f5a68213
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/config/CodeSignConfig.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.config;
+
+import com.ohos.codesigntool.core.api.CodeSignServer;
+import com.ohos.codesigntool.core.response.DataFromSignCenterServer;
+import com.ohos.codesigntool.core.sign.SignAlgorithm;
+import com.ohos.codesigntool.core.utils.CertUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Signature mode super class
+ *
+ * @since 2023/06/05
+ */
+public class CodeSignConfig {
+ private static final Logger LOGGER = LogManager.getLogger(CodeSignConfig.class);
+
+ /**
+ * list of x509 certificate used for sign hap
+ *
+ */
+ private List x509CertList;
+
+ /**
+ * certificate revocation list return from server
+ *
+ */
+ private List x509CRLList;
+
+ /**
+ * list of signature Algorithm used for sign hap
+ *
+ */
+ private List signAlgList;
+
+ /**
+ * map of signature param for sign hap
+ *
+ */
+ private Map signParamMap = new HashMap();
+
+ /**
+ * server interface for get signature
+ *
+ */
+ private CodeSignServer server = null;
+
+ /**
+ * fill signature parameters
+ *
+ * @param params input paramters for sign hap
+ */
+ public void setSignParamsMap(Map params) {
+ this.signParamMap = params;
+ }
+
+ /**
+ * use signatureAlg to sigh the input data
+ *
+ * @param data unsigned data
+ * @param signatureAlg name of signature Algorithm
+ * @return signed data
+ */
+ public byte[] getSignature(byte[] data, String signatureAlg) {
+ return ArrayUtils.EMPTY_BYTE_ARRAY;
+ }
+
+ /**
+ * use signatureAlg with AlgorithmParameterSpec to sigh the input data
+ *
+ * @param data unsigned data
+ * @param signatureAlg name of signature Algorithm
+ * @param second paramters of signature Algorithm
+ * @return signed data
+ */
+ public byte[] getSignature(byte[] data, String signatureAlg, AlgorithmParameterSpec second) {
+ return ArrayUtils.EMPTY_BYTE_ARRAY;
+ }
+
+ /**
+ * signature function.
+ *
+ * @param data unsigned data.
+ * @param algName algorithm name.
+ * @param privateKey use to sign.
+ * @param second algorithm parameters.
+ * @return byte array of signature.
+ * @throws NoSuchProviderException get BC provider failed.
+ * @throws NoSuchAlgorithmException use error algorithm.
+ * @throws InvalidKeyException error key.
+ * @throws InvalidAlgorithmParameterException error parameters of algorithm.
+ * @throws SignatureException signing failed.
+ */
+ protected byte[] getSignature(byte[] data, String algName, PrivateKey privateKey, AlgorithmParameterSpec second)
+ throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException,
+ InvalidAlgorithmParameterException, SignatureException {
+ Signature signature = Signature.getInstance(algName, "BC");
+ signature.initSign(privateKey);
+ if (second != null) {
+ signature.setParameter(second);
+ }
+ signature.update(data);
+ return signature.sign();
+ }
+
+ /**
+ * refresh crl list by object of json.
+ *
+ * @param data input object of DataFromSignCenterServer.
+ */
+ protected void refreshCrlListByResponseData(DataFromSignCenterServer data) {
+ String encodeCRLData = data.getCrl();
+ if (isStringDataInvalid(encodeCRLData)) {
+ this.x509CRLList = null;
+ LOGGER.warn("Get CRL data is null!");
+ } else {
+ this.x509CRLList = new ArrayList<>();
+ this.x509CRLList.add(CertUtils.getX509CRLByBase64EncodedString(encodeCRLData));
+ }
+ }
+
+ /**
+ * refresh cert list by object of json.
+ *
+ * @param data input object of DataFromSignCenterServer.
+ * @return true, if get certificates successfully.
+ */
+ protected boolean refreshCertListByResponseData(DataFromSignCenterServer data) {
+ if (data.getCertchain() == null || data.getCertchain().length == 0) {
+ LOGGER.error("cert chain array is empty!");
+ return false;
+ }
+
+ this.x509CertList = new ArrayList<>();
+ for (String cert : data.getCertchain()) {
+ this.x509CertList.add(CertUtils.getX509CertByBase64EncodedString(cert));
+ }
+ return true;
+ }
+
+ /**
+ * check whether string data is invalid.
+ *
+ * @param stringData string data.
+ * @return true, if input is null or is empty.
+ */
+ protected boolean isStringDataInvalid(String stringData) {
+ return (stringData == null) || StringUtils.isEmpty(stringData);
+ }
+
+ public void setSignAlgList(List signAlgList) {
+ this.signAlgList = signAlgList;
+ }
+
+ public List getSignAlgList() {
+ return signAlgList;
+ }
+
+ public void setX509CertList(List x509CertList) {
+ this.x509CertList = x509CertList;
+ }
+
+ public List getX509CertList() {
+ return x509CertList;
+ }
+
+ public void setX509CRLList(List x509CRLList) {
+ this.x509CRLList = x509CRLList;
+ }
+
+ public List getX509CRLList() {
+ return x509CRLList;
+ }
+
+ public void setSignParamMap(Map signParamMap) {
+ this.signParamMap = signParamMap;
+ }
+
+ public Map getSignParamMap() {
+ return signParamMap;
+ }
+
+ public void setServer(CodeSignServer server) {
+ this.server = server;
+ }
+
+ public CodeSignServer getServer() {
+ return server;
+ }
+}
+
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/config/RemoteCodeSignConfig.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/config/RemoteCodeSignConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..d366d645ebfdd982ee4f6761956673156789749a
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/config/RemoteCodeSignConfig.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.config;
+
+import com.google.gson.Gson;
+import com.ohos.codesigntool.core.response.DataFromAppGallaryServer;
+import com.ohos.codesigntool.core.response.DataFromSignCenterServer;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Base64;
+
+/**
+ * Signature code by remote server online
+ *
+ * @since 2023/06/05
+ */
+public class RemoteCodeSignConfig extends CodeSignConfig {
+ private static final Logger LOGGER = LogManager.getLogger(RemoteCodeSignConfig.class);
+ @Override
+ public byte[] getSignature(byte[] data, String signatureAlg, AlgorithmParameterSpec second) {
+ LOGGER.info("Compute signature by remote mode!");
+ if (this.getServer() == null) {
+ LOGGER.error("server is null");
+ return null;
+ }
+ String responseData = this.getServer().getSignature(data, signatureAlg);
+ byte[] signBytes = getSignFromServer(responseData);
+ if (signBytes != null && signBytes.length > 0) {
+ LOGGER.info("Get signature data success!");
+ } else {
+ LOGGER.error("Get signature data failed!");
+ return null;
+ }
+ return signBytes;
+ }
+
+ /**
+ * parse response data from server and return the decrypted signature data.
+ *
+ * @param responseData response data from server
+ * @return binary data of signature
+ */
+ public byte[] getSignFromServer(String responseData) {
+ if (isStringDataInvalid(responseData)) {
+ LOGGER.error("Get invalid response from signature server!");
+ return ArrayUtils.EMPTY_BYTE_ARRAY;
+ }
+ DataFromAppGallaryServer dataFromAppGallaryServer =
+ new Gson().fromJson(responseData, DataFromAppGallaryServer.class);
+ if (dataFromAppGallaryServer == null || !isSignSuccess(dataFromAppGallaryServer)) {
+ LOGGER.error("ResponseJson is illegals!");
+ return ArrayUtils.EMPTY_BYTE_ARRAY;
+ }
+
+ DataFromSignCenterServer signCenterData =
+ dataFromAppGallaryServer.getDataFromSignCenterServer();
+ if (signCenterData == null) {
+ LOGGER.error("Get response data from sign center server error!");
+ return ArrayUtils.EMPTY_BYTE_ARRAY;
+ }
+
+ if (!refreshCertListByResponseData(signCenterData)) {
+ LOGGER.error("Refresh certificate list data failed!");
+ return ArrayUtils.EMPTY_BYTE_ARRAY;
+ }
+
+ refreshCrlListByResponseData(signCenterData);
+
+ String encodeSignedData = signCenterData.getSignedData();
+ if (isStringDataInvalid(encodeSignedData)) {
+ LOGGER.error("Get signedData data error!");
+ return ArrayUtils.EMPTY_BYTE_ARRAY;
+ }
+ return Base64.getUrlDecoder().decode(encodeSignedData);
+ }
+
+ private boolean isSignSuccess(DataFromAppGallaryServer dataFromAppGallaryServer) {
+ if (!"success".equals(dataFromAppGallaryServer.getCodeSignature())) {
+ if (dataFromAppGallaryServer.getMessage() != null) {
+ LOGGER.error("Get code signature failed: {}", dataFromAppGallaryServer.getMessage());
+ }
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/CodeSignException.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/CodeSignException.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f328a3406db05bbe90a13844b5c2cd6ce7ed9bb
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/CodeSignException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.exception;
+
+/**
+ * CodeSign exception.
+ *
+ * @since 2023/06/05
+ */
+public class CodeSignException extends Exception {
+
+ private static final long serialVersionUID = -281871003709431259L;
+
+ /**
+ * CodeSignException
+ *
+ * @param message msg
+ */
+ public CodeSignException(String message) {
+ super(message);
+ }
+
+ /**
+ * CodeSignException
+ *
+ * @param message msg
+ * @param cause cause
+ */
+ public CodeSignException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/FsVerityDigestException.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/FsVerityDigestException.java
new file mode 100644
index 0000000000000000000000000000000000000000..1fd13e43b5c9657fc51699c71de08560aa440fd2
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/FsVerityDigestException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.exception;
+
+/**
+ * Could't compute FsVerity digest of a file
+ *
+ * @since 2023/06/05
+ */
+public class FsVerityDigestException extends Exception {
+
+ private static final long serialVersionUID = 5788641970791287892L;
+
+ public FsVerityDigestException(String message) {
+ super(message);
+ }
+
+ public FsVerityDigestException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/InvalidParamsException.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/InvalidParamsException.java
new file mode 100644
index 0000000000000000000000000000000000000000..44caac11fef6c610266ef967f848376c3aca14ce
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/InvalidParamsException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.exception;
+
+/**
+ * Exception occurs when the inputted parameters are invalid.
+ *
+ * @since 2023/06/05
+ */
+public class InvalidParamsException extends Exception {
+
+ private static final long serialVersionUID = -3379598647287693325L;
+
+ /**
+ * Exception occurs when the inputted parameters are invalid.
+ *
+ * @param message msg
+ */
+ public InvalidParamsException(String message) {
+ super(message);
+ }
+
+ /**
+ * Exception occurs when the inputted parameters are invalid.
+ *
+ * @param message msg
+ * @param cause cause
+ */
+ public InvalidParamsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/MissingParamsException.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/MissingParamsException.java
new file mode 100644
index 0000000000000000000000000000000000000000..15d44184460d74f10ef13a4542b658d2e820da2e
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/exception/MissingParamsException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.exception;
+
+/**
+ * Exception occurs when the required parameters are not entered.
+ *
+ * @since 2023/06/05
+ */
+public class MissingParamsException extends Exception {
+
+ private static final long serialVersionUID = -8922730964374794468L;
+
+ /**
+ * Exception occurs when the required parameters are not entered.
+ *
+ * @param message msg
+ */
+ public MissingParamsException(String message) {
+ super(message);
+ }
+
+ /**
+ * Exception occurs when the required parameters are not entered.
+ *
+ * @param message msg
+ * @param cause cause
+ */
+ public MissingParamsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityDescriptor.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityDescriptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e7b1771ee1c2c6306b6a3e3077a12dc55b7465d
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityDescriptor.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.fsverity;
+
+import com.ohos.codesigntool.core.exception.FsVerityDigestException;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Format of FsVerity descriptor
+ *
+ * @since 2023/06/05
+ */
+public class FsVerityDescriptor {
+ // Format
+ // uint8 version
+ // uint8 hashAlgorithm
+ // uint8 log2BlockSize
+ // uint8 saltSize
+ // uint8[4] 0
+ // le64 dataSize
+ // uint8[64] rootHash
+ // uint8[32] salt
+ // uint8[144] 0
+
+ private static final byte VERSION = 1;
+ private static final int DESCRIPTOR_SIZE = 256;
+ private static final int ROOT_HASH_FILED_SIZE = 64;
+ private static final int SALT_SIZE = 32;
+ private static final int FIRST_RESERVED_SIZE = 4;
+ private static final int LAST_RESERVED_SIZE = 144;
+
+ /**
+ * Get FsVerity descriptor
+ *
+ * @param fileSize size of input
+ * @param hashAlgorithm hash algorithm id
+ * @param log2BlockSize log2 of hash block size
+ * @param salt salt used for hash
+ * @param rawRootHash root hash of merkle tree
+ * @return bytes of descriptor
+ * @throws FsVerityDigestException if error
+ */
+ public static byte[] getDescriptor(long fileSize, byte hashAlgorithm, byte log2BlockSize,
+ byte[] salt, byte[] rawRootHash) throws FsVerityDigestException {
+ ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+ buffer.put(VERSION);
+ buffer.put(hashAlgorithm);
+ buffer.put(log2BlockSize);
+ if (salt == null) {
+ buffer.put((byte) 0);
+ } else if (salt.length > SALT_SIZE) {
+ throw new FsVerityDigestException("Salt is too long");
+ } else {
+ buffer.put((byte) salt.length);
+ }
+ writeBytesWithSize(buffer, null, FIRST_RESERVED_SIZE);
+ buffer.putLong(fileSize);
+ writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE);
+ writeBytesWithSize(buffer, salt, SALT_SIZE);
+ return buffer.array();
+ }
+
+ /**
+ * Write bytes to ByteBuffer with specific size
+ *
+ * @param buffer target buffer
+ * @param src bytes to write
+ * @param size size of written bytes, fill 0 if src bytes is long enough
+ */
+ private static void writeBytesWithSize(ByteBuffer buffer, byte[] src, int size) {
+ int pos = buffer.position();
+ if (src != null) {
+ if (src.length > size) {
+ buffer.put(src, 0, size);
+ } else {
+ buffer.put(src);
+ }
+ }
+ buffer.position(pos + size);
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityDigest.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityDigest.java
new file mode 100644
index 0000000000000000000000000000000000000000..dcb02f078e0cc2499c8e46bee7c21c698a73d0d7
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityDigest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.fsverity;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Format of FsVerity digest
+ *
+ * @since 2023/06/05
+ */
+public class FsVerityDigest {
+ // Format
+ // int8[8] magic "FSVerity"
+ // le16 digestAlgorithm sha256 = 1, sha512 = 2
+ // le16 digestSize
+ // uint8[] digest
+ private static final String FSVERITY_DIGEST_MAGIC = "FSVerity";
+ private static final int DIGEST_HEADER_SIZE = 12;
+
+ /**
+ * Get formatted FsVerity digest
+ *
+ * @param algoID hash algorithm id
+ * @param digest raw digest computed from input
+ * @return formatted FsVerity digest bytes
+ */
+ public static byte[] getFsVerityDigest(byte algoID, byte[] digest) {
+ int size = DIGEST_HEADER_SIZE + digest.length;
+ ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
+ buffer.put(FSVERITY_DIGEST_MAGIC.getBytes(StandardCharsets.UTF_8));
+ buffer.putShort(algoID);
+ buffer.putShort((short) digest.length);
+ buffer.put(digest);
+ return buffer.array();
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityGenerator.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4ca3868542e2a86a4d02e2432b88f1e1e890873
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityGenerator.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.fsverity;
+
+import com.ohos.codesigntool.core.exception.FsVerityDigestException;
+import com.ohos.codesigntool.core.utils.DigestUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * FsVerity data generator supper class
+ *
+ * @since 2023/06/05
+ */
+public class FsVerityGenerator {
+ /**
+ * FsVerity hash algorithm
+ */
+ private static final FsVerityHashAlgorithm FS_VERITY_HASH_ALGORITHM = FsVerityHashAlgorithm.SHA256;
+ private static final byte LOG2_OF_FSVERITY_HASH_PAGE_SIZE = 12;
+
+ /**
+ * salt for hashing one page
+ */
+ protected byte[] salt = null;
+
+ private byte[] fsVerityDigest = null;
+ private byte[] treeBytes = null;
+
+ /**
+ * generate merkle tree of given input
+ *
+ * @param inputStream input stream for generating merkle tree
+ * @param size total size of input stream
+ * @param fsVerityHashAlgorithm hash algorithm for FsVerity
+ * @return merkle tree
+ * @throws FsVerityDigestException if error
+ */
+ public MerkleTree generateMerkleTree(InputStream inputStream, long size,
+ FsVerityHashAlgorithm fsVerityHashAlgorithm) throws FsVerityDigestException {
+ MerkleTree merkleTree;
+ try {
+ merkleTree = new MerkleTreeBuilder().generateMerkleTree(inputStream, size, fsVerityHashAlgorithm);
+ } catch (IOException e) {
+ throw new FsVerityDigestException("IOException: " + e.getMessage());
+ } catch (NoSuchAlgorithmException e) {
+ throw new FsVerityDigestException("Invalid algorithm:" + e.getMessage());
+ }
+ return merkleTree;
+ }
+
+ /**
+ * generate FsVerity digest of given input
+ *
+ * @param inputStream input stream for generating FsVerity digest
+ * @param size total size of input stream
+ * @throws FsVerityDigestException if error
+ */
+ public void generateFsVerityDigest(InputStream inputStream, long size)
+ throws FsVerityDigestException {
+ MerkleTree merkleTree;
+ if (size == 0) {
+ merkleTree = new MerkleTree(null, null, FS_VERITY_HASH_ALGORITHM);
+ } else {
+ merkleTree = generateMerkleTree(inputStream, size, FS_VERITY_HASH_ALGORITHM);
+ }
+ byte[] fsVerityDescriptor = FsVerityDescriptor.getDescriptor(size,
+ FS_VERITY_HASH_ALGORITHM.getId(), LOG2_OF_FSVERITY_HASH_PAGE_SIZE,
+ salt, merkleTree.rootHash);
+ byte[] digest;
+ try {
+ digest = DigestUtils.computeDigest(fsVerityDescriptor, FS_VERITY_HASH_ALGORITHM.getHashAlgorithm());
+ } catch (NoSuchAlgorithmException e) {
+ throw new FsVerityDigestException("Invalid algorithm" + e.getMessage(), e);
+ }
+ fsVerityDigest = FsVerityDigest.getFsVerityDigest(FS_VERITY_HASH_ALGORITHM.getId(), digest);
+ treeBytes = merkleTree.tree;
+ }
+
+ /**
+ * Get FsVerity digest
+ *
+ * @return bytes of FsVerity digest
+ */
+ public byte[] getFsVerityDigest() {
+ return fsVerityDigest;
+ }
+
+ /**
+ * Get merkle tree in bytes
+ *
+ * @return bytes of merkle tree
+ */
+ public byte[] getTreeBytes() {
+ return treeBytes;
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityHashAlgorithm.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityHashAlgorithm.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8d93bc162020d088a88ac69d61503e79269cf66
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/FsVerityHashAlgorithm.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.fsverity;
+
+/**
+ * FsVerity hash algorithm
+ *
+ * @since 2023/06/05
+ */
+public enum FsVerityHashAlgorithm {
+ SHA256((byte) 1, "SHA-256", 256 / 8),
+ SHA512((byte) 2, "SHA-512", 512 / 8);
+
+ private final byte id;
+ private final String hashAlgorithm;
+ private final int outputByteSize;
+
+ FsVerityHashAlgorithm(byte id, String hashAlgorithm, int outputByteSize) {
+ this.id = id;
+ this.hashAlgorithm = hashAlgorithm;
+ this.outputByteSize = outputByteSize;
+ }
+
+ public byte getId() {
+ return id;
+ }
+
+ public String getHashAlgorithm() {
+ return hashAlgorithm;
+ }
+
+ public int getOutputByteSize() {
+ return outputByteSize;
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/MerkleTree.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/MerkleTree.java
new file mode 100644
index 0000000000000000000000000000000000000000..544a536b3043c8489dd7264333a367d43c9d81bb
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/MerkleTree.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.fsverity;
+
+/**
+ * Merkel tree data
+ *
+ * @since 2023/06/05
+ */
+public class MerkleTree {
+ /**
+ * root hash of merkle tree
+ */
+ public final byte[] rootHash;
+
+ /**
+ * content data of merkle tree
+ */
+ public final byte[] tree;
+
+ /**
+ * hash algorithm used for merkle tree
+ */
+ public final FsVerityHashAlgorithm fsVerityHashAlgorithm;
+
+ MerkleTree(byte[] rootHash, byte[] tree, FsVerityHashAlgorithm fsVerityHashAlgorithm) {
+ this.rootHash = rootHash;
+ this.tree = tree;
+ this.fsVerityHashAlgorithm = fsVerityHashAlgorithm;
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/MerkleTreeBuilder.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/MerkleTreeBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..5fbc3b546ac935a660774fd7c47a4cc36f8cdc2a
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/fsverity/MerkleTreeBuilder.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.fsverity;
+
+import com.ohos.codesigntool.core.utils.DigestUtils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Phaser;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Merkle tree builder
+ *
+ * @since 2023/06/19
+ */
+public class MerkleTreeBuilder {
+ private static final Logger LOGGER = LogManager.getLogger(MerkleTreeBuilder.class);
+
+ private static final int FSVERITY_HASH_PAGE_SIZE = 4096;
+
+ private static final long INPUTSTREAM_MAX_SIZE = 4503599627370496L;
+
+ private static final int CHUNK_SIZE = 4096;
+
+ private static final long MAX_READ_SIZE = 4194304L;
+
+ private static final int MAX_PROCESSORS = 32;
+
+ private static final int BLOCKINGQUEUE = 4;
+
+ private static final int POOL_SIZE = Math.min(MAX_PROCESSORS,
+ Runtime.getRuntime().availableProcessors());
+
+ private String mAlgorithm = "SHA-256";
+
+ private final ExecutorService mPools = new ThreadPoolExecutor(POOL_SIZE, POOL_SIZE, 0L,
+ TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(BLOCKINGQUEUE),
+ new ThreadPoolExecutor.CallerRunsPolicy());
+
+ /**
+ * Turn off multitasking
+ */
+ public void close() {
+ this.mPools.shutdownNow();
+ }
+
+ /**
+ * set algorithm
+ *
+ * @param algorithm hash algorithm
+ */
+ private void setAlgorithm(String algorithm) {
+ this.mAlgorithm = algorithm;
+ }
+
+ /**
+ * translation inputStream to ByteBuffer
+ *
+ * @param inputStream input stream for generating merkle tree
+ * @param size total size of input stream
+ * @return ByteBuffer data
+ * @throws IOException if error
+ */
+ private ByteBuffer[] convertToByteBuffer(InputStream inputStream, long size)
+ throws IOException {
+ if (size == 0) {
+ throw new IOException("Input size is empty");
+ } else if (size > INPUTSTREAM_MAX_SIZE) {
+ throw new IOException("Input size is too long");
+ }
+ int count = (int) getChunkCount(size, MAX_READ_SIZE);
+ ByteBuffer[] byteBuffer = new ByteBuffer[count];
+ long readOffset = 0L;
+ for (int i = 0; i < count; i++) {
+ long readLimit = Math.min(readOffset + MAX_READ_SIZE, size);
+ int readSize = (int) (readLimit - readOffset);
+ int fullChunkSize = (int) getFullChunkSize(readSize, CHUNK_SIZE, CHUNK_SIZE);
+ byteBuffer[i] = ByteBuffer.allocate(fullChunkSize);
+
+ int offset = 0;
+ byte[] buffer = new byte[CHUNK_SIZE];
+ int num;
+ int len = CHUNK_SIZE;
+ while ((num = inputStream.read(buffer, 0, len)) > 0) {
+ byteBuffer[i].put(buffer, 0, num);
+ offset += num;
+ len = Math.min(CHUNK_SIZE, readSize - offset);
+ if (len <= 0 || offset == readSize) {
+ break;
+ }
+ }
+ if (offset != readSize) {
+ throw new IOException("IOException read buffer from input error.");
+ }
+ byteBuffer[i].flip();
+ readOffset += readSize;
+ }
+ return byteBuffer;
+ }
+
+ /**
+ * split buffer by begin and end information
+ *
+ * @param buffer original buffer
+ * @param begin begin position
+ * @param end end position
+ * @return slice buffer
+ */
+ private static ByteBuffer slice(ByteBuffer buffer, int begin, int end) {
+ ByteBuffer tempBuffer = buffer.duplicate();
+ tempBuffer.position(0);
+ tempBuffer.limit(end);
+ tempBuffer.position(begin);
+ return tempBuffer.slice();
+ }
+
+ /**
+ * calculate merkle tree level and size by data size and digest size
+ *
+ * @param dataSize original data size
+ * @param digestSize algorithm data size
+ * @return level offset list,contains the offset of
+ * each level from the root node to the leaf node
+ */
+ private static int[] getOffsetArrays(long dataSize, int digestSize) {
+ ArrayList levelSize = getLevelSize(dataSize, digestSize);
+ int[] levelOffset = new int[levelSize.size() + 1];
+ levelOffset[0] = 0;
+ for (int i = 0; i < levelSize.size(); i++) {
+ levelOffset[i + 1] = levelOffset[i] + Math.toIntExact(levelSize.get(levelSize.size() - i - 1));
+ }
+ return levelOffset;
+ }
+
+ /**
+ * calculate data size list by data size and digest size
+ *
+ * @param dataSize original data size
+ * @param digestSize algorithm data size
+ * @return data size list,contains the offset of
+ * each level from the root node to the leaf node
+ */
+ private static ArrayList getLevelSize(long dataSize, int digestSize) {
+ ArrayList levelSize = new ArrayList<>();
+ long fullChunkSize = 0L;
+ long originalDataSize = dataSize;
+ do {
+ fullChunkSize = getFullChunkSize(originalDataSize, CHUNK_SIZE, digestSize);
+ long size = getFullChunkSize(fullChunkSize, CHUNK_SIZE, CHUNK_SIZE);
+ levelSize.add(size);
+ originalDataSize = fullChunkSize;
+ } while (fullChunkSize > CHUNK_SIZE);
+ return levelSize;
+ }
+
+ /**
+ * hash data of input array
+ *
+ * @param inputBuffer original data
+ * @param size total size of input stream
+ * @param outputBuffer hash data
+ */
+ private void transInputDataToHashData(ByteBuffer[] inputBuffer, long size, ByteBuffer outputBuffer) {
+ int count = inputBuffer.length;
+ int chunks = (int) getChunkCount(size, CHUNK_SIZE);
+ byte[][] hashes = new byte[chunks][];
+ Phaser tasks = new Phaser(1);
+ for (int i = 0; i < count; i++) {
+ ByteBuffer buffer = inputBuffer[i];
+ buffer.rewind();
+ int readChunkIndex = (int) getFullChunkSize(MAX_READ_SIZE, CHUNK_SIZE, i);
+ runHashTask(hashes, tasks, buffer, readChunkIndex);
+ }
+ tasks.arriveAndAwaitAdvance();
+ for (byte[] hash : hashes) {
+ outputBuffer.put(hash, 0, hash.length);
+ }
+ }
+
+ private void runHashTask(byte[][] hashes, Phaser tasks, ByteBuffer buffer, int readChunkIndex) {
+ Runnable task = () -> {
+ int offset = 0;
+ int bufferSize = buffer.capacity();
+ int index = readChunkIndex;
+ while (offset < bufferSize) {
+ ByteBuffer chunk = slice(buffer, offset, offset + CHUNK_SIZE);
+ byte[] tempByte = new byte[CHUNK_SIZE];
+ chunk.get(tempByte);
+ try {
+ hashes[index++] = DigestUtils.computeDigest(tempByte, this.mAlgorithm);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException(e);
+ }
+ offset += CHUNK_SIZE;
+ }
+ tasks.arriveAndDeregister();
+ };
+ tasks.register();
+ this.mPools.execute(task);
+ }
+
+ /**
+ * hash data of buffer
+ *
+ * @param inputBuffer original data
+ * @param outputBuffer hash data
+ */
+ private void transInputDataToHashData(ByteBuffer inputBuffer, ByteBuffer outputBuffer) {
+ long size = inputBuffer.capacity();
+ int chunks = (int) getChunkCount(size, CHUNK_SIZE);
+ byte[][] hashes = new byte[chunks][];
+ Phaser tasks = new Phaser(1);
+ long readOffset = 0L;
+ int startChunkIndex = 0;
+ while (readOffset < size) {
+ long readLimit = Math.min(readOffset + MAX_READ_SIZE, size);
+ ByteBuffer buffer = slice(inputBuffer, (int) readOffset, (int) readLimit);
+ buffer.rewind();
+ int readChunkIndex = startChunkIndex;
+ runHashTask(hashes, tasks, buffer, readChunkIndex);
+ int readSize = (int) (readLimit - readOffset);
+ startChunkIndex += (int) getChunkCount(readSize, CHUNK_SIZE);
+ readOffset += readSize;
+ }
+ tasks.arriveAndAwaitAdvance();
+ for (byte[] hash : hashes) {
+ outputBuffer.put(hash, 0, hash.length);
+ }
+ }
+
+ /**
+ * generate merkle tree of given input
+ *
+ * @param inputStream input stream for generating merkle tree
+ * @param size total size of input stream
+ * @param fsVerityHashAlgorithm hash algorithm for FsVerity
+ * @return merkle tree
+ * @throws NoSuchAlgorithmException if error
+ * @throws IOException if error
+ */
+ public MerkleTree generateMerkleTree(InputStream inputStream, long size,
+ FsVerityHashAlgorithm fsVerityHashAlgorithm) throws IOException, NoSuchAlgorithmException {
+ setAlgorithm(fsVerityHashAlgorithm.getHashAlgorithm());
+ int digestSize = fsVerityHashAlgorithm.getOutputByteSize();
+ int[] offsetArrays = getOffsetArrays(size, digestSize);
+ ByteBuffer allHashBuffer = ByteBuffer.allocate(offsetArrays[offsetArrays.length - 1]);
+ generateHashDataByInputData(inputStream, size, allHashBuffer, offsetArrays, digestSize);
+ generateHashDataByHashData(allHashBuffer, offsetArrays, digestSize);
+ return getMerkleTree(allHashBuffer, size, fsVerityHashAlgorithm);
+ }
+
+ /**
+ * translation inputBuffer arrays to hash ByteBuffer
+ *
+ * @param inputStream input stream for generating merkle tree
+ * @param size total size of input stream
+ * @param outputBuffer hash data
+ * @param offsetArrays level offset
+ * @param digestSize algorithm output byte size
+ * @throws IOException if error
+ */
+ private void generateHashDataByInputData(InputStream inputStream, long size, ByteBuffer outputBuffer,
+ int[] offsetArrays, int digestSize) throws IOException {
+ int inputDataOffsetBegin = offsetArrays[offsetArrays.length - 2];
+ int inputDataOffsetEnd = offsetArrays[offsetArrays.length - 1];
+ long inputDataBufferSize = 0L;
+ ByteBuffer hashBuffer = slice(outputBuffer, inputDataOffsetBegin, inputDataOffsetEnd);
+ ByteBuffer[] inputBuffer = convertToByteBuffer(inputStream, size);
+ for (ByteBuffer tempinputBuffer : inputBuffer) {
+ inputDataBufferSize += tempinputBuffer.capacity();
+ }
+ transInputDataToHashData(inputBuffer, inputDataBufferSize, hashBuffer);
+ dataRoundupChunkSize(hashBuffer, inputDataBufferSize, digestSize);
+ }
+
+ /**
+ * get buffer data by level offset,transforms digest data, save in another memory
+ *
+ * @param buffer hash data
+ * @param offsetArrays level offset
+ * @param digestSize algorithm output byte size
+ */
+ private void generateHashDataByHashData(ByteBuffer buffer, int[] offsetArrays, int digestSize) {
+ for (int i = offsetArrays.length - 3; i >= 0; i--) {
+ ByteBuffer generateHashBuffer = slice(buffer, offsetArrays[i], offsetArrays[i + 1]);
+ ByteBuffer originalHashBuffer = slice(buffer.asReadOnlyBuffer(),
+ offsetArrays[i + 1], offsetArrays[i + 2]);
+ transInputDataToHashData(originalHashBuffer, generateHashBuffer);
+ dataRoundupChunkSize(generateHashBuffer, originalHashBuffer.capacity(), digestSize);
+ }
+ }
+
+ /**
+ * generate merkle tree of given input
+ *
+ * @param dataBuffer tree data memory block
+ * @param inputDataSize total size of input stream
+ * @param fsVerityHashAlgorithm hash algorithm for FsVerity
+ * @return merkle tree
+ * @throws NoSuchAlgorithmException if error
+ */
+ private MerkleTree getMerkleTree(ByteBuffer dataBuffer, long inputDataSize,
+ FsVerityHashAlgorithm fsVerityHashAlgorithm) throws NoSuchAlgorithmException {
+ int digestSize = fsVerityHashAlgorithm.getOutputByteSize();
+ dataBuffer.flip();
+ byte[] rootHash = null;
+ byte[] tree = null;
+ if (inputDataSize < FSVERITY_HASH_PAGE_SIZE) {
+ ByteBuffer fsVerityHashPageBuffer = slice(dataBuffer, 0, digestSize);
+ rootHash = new byte[digestSize];
+ fsVerityHashPageBuffer.get(rootHash);
+ } else {
+ tree = dataBuffer.array();
+ ByteBuffer fsVerityHashPageBuffer = slice(dataBuffer.asReadOnlyBuffer(), 0, FSVERITY_HASH_PAGE_SIZE);
+ byte[] fsVerityHashPage = new byte[FSVERITY_HASH_PAGE_SIZE];
+ fsVerityHashPageBuffer.get(fsVerityHashPage);
+ rootHash = DigestUtils.computeDigest(fsVerityHashPage, this.mAlgorithm);
+ }
+ return new MerkleTree(rootHash, tree, fsVerityHashAlgorithm);
+ }
+
+ /**
+ * generate merkle tree of given input
+ *
+ * @param data original data
+ * @param originalDataSize data size
+ * @param digestSize algorithm output byte size
+ */
+ private void dataRoundupChunkSize(ByteBuffer data, long originalDataSize, int digestSize) {
+ long fullChunkSize = getFullChunkSize(originalDataSize, CHUNK_SIZE, digestSize);
+ int diffValue = (int) (fullChunkSize % CHUNK_SIZE);
+ if (diffValue > 0) {
+ byte[] padding = new byte[CHUNK_SIZE - diffValue];
+ data.put(padding, 0, padding.length);
+ }
+ }
+
+ /**
+ * get mount of chucks to store data
+ *
+ * @param dataSize data size
+ * @param divisor split chunk size
+ * @return chunk count
+ */
+ private static long getChunkCount(long dataSize, long divisor) {
+ return (long) Math.ceil((double) dataSize / (double) divisor);
+ }
+
+ /**
+ * get total size of chunk to store data
+ *
+ * @param dataSize data size
+ * @param divisor split chunk size
+ * @param multiplier chunk multiplier
+ * @return chunk size
+ */
+ private static long getFullChunkSize(long dataSize, long divisor, long multiplier) {
+ return getChunkCount(dataSize, divisor) * multiplier;
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/provider/CodeSignProvider.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/provider/CodeSignProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..0a84e28003de950cc3bb1102d35794ec71dc7b94
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/provider/CodeSignProvider.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.provider;
+
+import com.ohos.codesigntool.core.api.CodeSignServer;
+import com.ohos.codesigntool.core.config.CodeSignConfig;
+import com.ohos.codesigntool.core.exception.FsVerityDigestException;
+import com.ohos.codesigntool.core.exception.InvalidParamsException;
+import com.ohos.codesigntool.core.exception.MissingParamsException;
+import com.ohos.codesigntool.core.exception.CodeSignException;
+import com.ohos.codesigntool.core.sign.SignCode;
+import com.ohos.codesigntool.core.sign.SignAlgorithm;
+import com.ohos.codesigntool.core.utils.CertUtils;
+import com.ohos.codesigntool.core.utils.FileUtils;
+import com.ohos.codesigntool.core.utils.ParamConstants;
+import com.ohos.codesigntool.core.utils.ParamProcessUtil;
+import com.ohos.codesigntool.core.utils.Pair;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Security;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * provider for code sign
+ *
+ * @since 2023/06/05
+ */
+public abstract class CodeSignProvider {
+ private static final Logger LOGGER = LogManager.getLogger(CodeSignProvider.class);
+ private static final Set SIGN_ALG_SUPPORT_LIST = new HashSet<>();
+ private static final Set SIGN_PARAMS_NEED_LIST = new HashSet<>();
+ private static final Set SIGN_PARAMS_CHECK_LIST = new HashSet<>();
+ private static final String SIGN_FILE_SUFFIX = ".sig";
+
+ static {
+ SIGN_ALG_SUPPORT_LIST.add(ParamConstants.SIG_ALGORITHM_SHA256_ECDSA);
+ SIGN_PARAMS_NEED_LIST.add(ParamConstants.PARAM_BASIC_SIGN_ALG);
+ SIGN_PARAMS_NEED_LIST.add(ParamConstants.PARAM_BASIC_INPUT_FILE);
+ SIGN_PARAMS_NEED_LIST.add(ParamConstants.PARAM_BASIC_OUTPUT_PATH);
+ SIGN_PARAMS_NEED_LIST.add(ParamConstants.PARAM_OUTPUT_MEKLE_TREE);
+ SIGN_PARAMS_CHECK_LIST.add(ParamConstants.PARAM_BASIC_SIGN_ALG);
+ SIGN_PARAMS_CHECK_LIST.add(ParamConstants.PARAM_BASIC_INPUT_FILE);
+ SIGN_PARAMS_CHECK_LIST.add(ParamConstants.PARAM_BASIC_OUTPUT_PATH);
+ if (Security.getProvider("BC") == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ }
+
+ /**
+ * parameters input by sign
+ */
+ protected List> inputParamsList = new ArrayList<>();
+
+ /**
+ * parameters only use in signing
+ */
+ protected Map signParams = new HashMap<>();
+
+ /**
+ * interface of sign server
+ */
+ protected CodeSignServer server = null;
+
+ /**
+ * set interface of sign server
+ *
+ * @param server interface of sign server
+ */
+ public void setCodeSignServer(CodeSignServer server) {
+ this.server = server;
+ }
+
+ /**
+ * Get public certificate chain used to sign.
+ *
+ * @return list of x509 certificates.
+ */
+ private List getPublicCerts() {
+ String publicCertPath = signParams.get(ParamConstants.PARAM_LOCAL_PUBLIC_CERT);
+ if (StringUtils.isEmpty(publicCertPath)) {
+ return Collections.emptyList();
+ }
+ return CertUtils.getCertChainsFromFile(publicCertPath);
+ }
+
+ /**
+ * get certificate revocation list used to sign
+ *
+ * @return certificate revocation list
+ */
+ public abstract X509CRL getCrl();
+
+
+ /**
+ * Create SignConfig by certificate chain and certificate revocation list.
+ *
+ * @param x509CertList certificate chain
+ * @param crl certificate revocation list
+ * @return Object of SignConfig
+ * @throws InvalidKeyException on error when the key is invalid.
+ */
+ public CodeSignConfig createSignConfigs(List x509CertList, X509CRL crl)
+ throws InvalidKeyException {
+ CodeSignConfig signConfig = new CodeSignConfig();
+ initSignConfigs(signConfig, x509CertList, crl);
+ return signConfig;
+ }
+
+ /**
+ * Init SignConfig by certificate chain and certificate revocation list.
+ *
+ * @param signConfig sign Config
+ * @param x509CertList certificate chain
+ * @param crl certificate revocation list
+ * @throws InvalidKeyException on error when the key is invalid.
+ */
+ public void initSignConfigs(CodeSignConfig signConfig, List x509CertList, X509CRL crl)
+ throws InvalidKeyException {
+ signConfig.setSignParamsMap(this.signParams);
+ signConfig.setX509CertList(x509CertList);
+
+ List signAlgList = new ArrayList<>();
+ signAlgList.add(
+ ParamProcessUtil.getSignAlgorithm(this.signParams.get(ParamConstants.PARAM_BASIC_SIGN_ALG)));
+ signConfig.setSignAlgList(signAlgList);
+
+ if (crl != null) {
+ signConfig.setX509CRLList(Collections.singletonList(crl));
+ }
+ }
+
+ /**
+ * sign code
+ *
+ * @param params parameters used to sign code
+ * @return true, if sign successfully
+ */
+ public boolean sign(String[] params) {
+ File output = null;
+ try {
+ // 1. check the parameters
+ checkParams(params);
+ // 2. get x509 certificate
+ List publicCerts = getPublicCerts();
+ // 3. check input hap validation
+ File input = new File(signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE));
+ FileUtils.isValid(input);
+ // 4. generate output file path
+ String outputPath = signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_PATH);
+ if (!outputPath.endsWith(File.separator)) {
+ outputPath += File.separator;
+ }
+ String outputFile = outputPath + input.getName() + SIGN_FILE_SUFFIX;
+ output = new File(outputFile);
+ // 5. check whether store tree
+ String outTreeSwitch = signParams.get(ParamConstants.PARAM_OUTPUT_MEKLE_TREE);
+ boolean storeTree = (outTreeSwitch != null) && (outTreeSwitch.equals("true"));
+ // 6. sign code
+ CodeSignConfig signConfig = createSignConfigs(publicCerts, getCrl());
+ SignCode signCode = new SignCode(signConfig);
+ signCode.signCode(input, output, storeTree);
+ } catch (CodeSignException | IOException | InvalidKeyException |
+ MissingParamsException | InvalidParamsException | FsVerityDigestException e) {
+ printErrorLog(e);
+ return false;
+ }
+ return true;
+ }
+
+ private void printErrorLog(Exception exception) {
+ if (exception != null) {
+ LOGGER.error("code-sign-tool: error: {}", exception.getMessage(), exception);
+ }
+ }
+
+ /**
+ * Check input parameters is valid. And put valid parameters input signParams.
+ *
+ * @param params parameters input by user.
+ * @throws MissingParamsException Exception occurs when the required parameters are not entered.
+ * @throws InvalidParamsException Exception occurs when the inputted parameters are invalid.
+ */
+ public void checkParams(String[] params) throws MissingParamsException, InvalidParamsException {
+ checkRemoteSignParams(params);
+ }
+
+ /**
+ * Check input parameters used to remotesign file is valid. And put valid parameters input signParams.
+ *
+ * @param params parameters input by user.
+ * @throws MissingParamsException Exception occurs when the required parameters are not entered.
+ * @throws InvalidParamsException Exception occurs when the inputted parameters are invalid.
+ */
+ protected void checkRemoteSignParams(String[] params) throws InvalidParamsException, MissingParamsException {
+ for (int i = 0; i < params.length; i += 2) {
+ if (!params[i].startsWith("-")) {
+ continue;
+ }
+ inputParamsList.add(Pair.create(params[i].substring(1), params[i + 1]));
+ }
+ for (Pair pairParam : inputParamsList) {
+ if (SIGN_PARAMS_NEED_LIST.contains(pairParam.getKey())) {
+ signParams.put(pairParam.getKey(), pairParam.getValue());
+ }
+ }
+
+ checkInputParamsComplete();
+ }
+
+ /**
+ * Check whether the input parameters are complete
+ *
+ * @throws MissingParamsException Exception occurs when input parameters is not entered.
+ * @throws InvalidParamsException Exception occurs when input parameters is invalid.
+ */
+ private void checkInputParamsComplete()
+ throws MissingParamsException, InvalidParamsException {
+ for (String param : SIGN_PARAMS_CHECK_LIST) {
+ if (!signParams.containsKey(param)) {
+ throw new MissingParamsException("Missing parameter: " + param);
+ }
+ if (param.equals(ParamConstants.PARAM_BASIC_SIGN_ALG)) {
+ checkSignAlg();
+ }
+ }
+ }
+
+ /**
+ * check signature algorithm
+ *
+ * @throws MissingParamsException Exception occurs when the name of sign algorithm is not entered.
+ * @throws InvalidParamsException Exception occurs when the inputted sign algorithm is invalid.
+ */
+ private void checkSignAlg() throws MissingParamsException, InvalidParamsException {
+ String signAlg = signParams.get(ParamConstants.PARAM_BASIC_SIGN_ALG);
+ for (String supportAlg : SIGN_ALG_SUPPORT_LIST) {
+ if (supportAlg.equalsIgnoreCase(signAlg)) {
+ return;
+ }
+ }
+ LOGGER.error("Unsupported signature algorithm :" + signAlg);
+ throw new InvalidParamsException("Invalid parameter: Sign Alg");
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/response/DataFromAppGallaryServer.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/response/DataFromAppGallaryServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..58e04f9df9724bd5d6ffe336783e45021ed776ee
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/response/DataFromAppGallaryServer.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.response;
+
+/**
+ * class of data from App Gallary server.
+ *
+ * @since 2023/06/05
+ */
+public class DataFromAppGallaryServer {
+ /**
+ * AppGallary server signature result.
+ */
+ private String codeSignature;
+
+ /**
+ * AppGallary server message.
+ */
+ private String message;
+
+ /**
+ * json value of data from server of signcenter.
+ */
+ private DataFromSignCenterServer dataFromSignCenterServer;
+
+ public String getCodeSignature() {
+ return codeSignature;
+ }
+
+ public void setCodeSignature(String codeSignature) {
+ this.codeSignature = codeSignature;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public DataFromSignCenterServer getDataFromSignCenterServer() {
+ return dataFromSignCenterServer;
+ }
+
+ public void setDataFromSignCenterServer(DataFromSignCenterServer dataFromSignCenterServer) {
+ this.dataFromSignCenterServer = dataFromSignCenterServer;
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/response/DataFromSignCenterServer.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/response/DataFromSignCenterServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..4330b5ae1687b679571f0fcb0d4c69b133208324
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/response/DataFromSignCenterServer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.response;
+
+/**
+ * class of data from signcenter server
+ *
+ * @since 2023/06/05
+ */
+public class DataFromSignCenterServer {
+ /**
+ * Signature data.
+ */
+ private String signedData;
+
+ /**
+ * Certificates chain.
+ */
+ private String[] certchain;
+
+ /**
+ * Certificate revocation list.
+ */
+ private String crl;
+
+ /**
+ * Set Certificates chain.
+ *
+ * @param certchain Giving certchain
+ */
+ public void setCertchain(String[] certchain) {
+ if (certchain == null) {
+ this.certchain = new String[0];
+ } else {
+ this.certchain = certchain.clone();
+ }
+ }
+
+ /**
+ * Get Certificates chain.
+ *
+ * @return Certificates chain
+ */
+ public String[] getCertchain() {
+ return certchain.clone();
+ }
+
+ public void setCrl(String crl) {
+ this.crl = crl;
+ }
+
+ public String getCrl() {
+ return crl;
+ }
+
+ public void setSignedData(String signedData) {
+ this.signedData = signedData;
+ }
+
+ public String getSignedData() {
+ return signedData;
+ }
+}
\ No newline at end of file
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/BcSignedDataGenerator.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/BcSignedDataGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..9333592d8dd1ee856e37ece292fd0c9562dae151
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/BcSignedDataGenerator.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.sign;
+
+import com.ohos.codesigntool.core.config.CodeSignConfig;
+import com.ohos.codesigntool.core.exception.CodeSignException;
+import com.ohos.codesigntool.core.utils.CmsUtils;
+import com.ohos.codesigntool.core.utils.DigestUtils;
+import com.ohos.codesigntool.core.utils.Pair;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BERSet;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.Time;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.SignedData;
+import org.bouncycastle.asn1.pkcs.SignerInfo;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * BC implementation
+ *
+ * @since 2023/06/05
+ */
+public class BcSignedDataGenerator implements SignedDataGenerator {
+ private static final Logger LOGGER = LogManager.getLogger(BcSignedDataGenerator.class);
+ private static final SignatureAlgorithmIdentifierFinder SIGN_ALG_ID_FINDER =
+ new DefaultSignatureAlgorithmIdentifierFinder();
+ private static final DigestAlgorithmIdentifierFinder DIGEST_ALG_ID_FINDER =
+ new DefaultDigestAlgorithmIdentifierFinder();
+
+ @Override
+ public byte[] generateSignedData(byte[] content, CodeSignConfig signConfig)
+ throws CodeSignException {
+ if (content == null) {
+ throw new CodeSignException("Verity digest is null");
+ }
+ Pair pairDigestAndSignInfo = getSignInfo(content, signConfig);
+ SignedData signedData = new SignedData(
+ new ASN1Integer(1),
+ pairDigestAndSignInfo.getKey(),
+ new ContentInfo(PKCSObjectIdentifiers.data, null),
+ createBerSetFromLst(signConfig.getX509CertList()),
+ createBerSetFromLst(signConfig.getX509CRLList()),
+ pairDigestAndSignInfo.getValue());
+ return encodingUnsignedData(content, signedData);
+ }
+
+ private Pair getSignInfo(byte[] content, CodeSignConfig signConfig)
+ throws CodeSignException {
+ ASN1EncodableVector signInfoVector = new ASN1EncodableVector();
+ ASN1EncodableVector digestVector = new ASN1EncodableVector();
+ for (SignAlgorithm signAlgorithm : signConfig.getSignAlgList()) {
+ SignerInfo signInfo = createSignInfo(signAlgorithm, content, signConfig);
+ signInfoVector.add(signInfo);
+ digestVector.add(signInfo.getDigestAlgorithm());
+ LOGGER.info("Create a sign info successfully.");
+ }
+ return Pair.create(new DERSet(digestVector), new DERSet(signInfoVector));
+ }
+
+ private SignerInfo createSignInfo(
+ SignAlgorithm signAlgorithm, byte[] unsignedDataDigest, CodeSignConfig signConfig)
+ throws CodeSignException {
+ SecureHashAlgorithm hashAlgorithm = signAlgorithm.getSecureHashAlgorithm();
+ byte[] digest = computeDigest(unsignedDataDigest, hashAlgorithm.name());
+ ASN1Set authed = getPKCS9Attributes(digest);
+ byte[] codeAuthed = getEncoded(authed);
+ Pair signPair =
+ signAlgorithm.getSignAlgorithmAndParams().getPairSignAlgorithmAndParams();
+ byte[] signBytes = signConfig.getSignature(
+ codeAuthed, signPair.getKey(), signPair.getValue());
+ if (signBytes == null) {
+ throw new CodeSignException("get signature failed");
+ }
+ if (signConfig.getX509CertList().isEmpty()) {
+ throw new CodeSignException("No certificates configured for sign");
+ }
+ X509Certificate cert = signConfig.getX509CertList().get(0);
+ if (!verifySignFromServer(cert.getPublicKey(), signBytes, signPair, codeAuthed)) {
+ throw new CodeSignException("verifySignatureFromServer failed");
+ }
+ JcaX509CertificateHolder certificateHolder = getJcaX509CertificateHolder(cert);
+ return new SignerInfo(
+ new ASN1Integer(1),
+ new IssuerAndSerialNumber(certificateHolder.getIssuer(), certificateHolder.getSerialNumber()),
+ DIGEST_ALG_ID_FINDER.find(hashAlgorithm.getHashAlgorithm()),
+ authed,
+ SIGN_ALG_ID_FINDER.find(signPair.getKey()),
+ new DEROctetString(signBytes),
+ null);
+ }
+
+ private byte[] computeDigest(byte[] unsignedDataDigest, String algorithm) throws CodeSignException {
+ byte[] digest;
+ try {
+ digest = DigestUtils.computeDigest(unsignedDataDigest, algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ throw new CodeSignException("Invalid algorithm" + e.getMessage(), e);
+ }
+ return digest;
+ }
+
+ private byte[] getEncoded(ASN1Set authed) throws CodeSignException {
+ byte[] codeAuthed;
+ try {
+ codeAuthed = authed.getEncoded();
+ } catch (IOException e) {
+ throw new CodeSignException("cannot encode authed", e);
+ }
+ return codeAuthed;
+ }
+
+ private JcaX509CRLHolder getJcaX509CRLHolder(X509CRL crl)
+ throws CodeSignException {
+ JcaX509CRLHolder crlHolder;
+ try {
+ crlHolder = new JcaX509CRLHolder(crl);
+ } catch (CRLException e) {
+ throw new CodeSignException("Create crl failed", e);
+ }
+ return crlHolder;
+ }
+
+ private JcaX509CertificateHolder getJcaX509CertificateHolder(X509Certificate cert)
+ throws CodeSignException {
+ JcaX509CertificateHolder certificateHolder;
+ try {
+ certificateHolder = new JcaX509CertificateHolder(cert);
+ } catch (CertificateEncodingException e) {
+ throw new CodeSignException("Create sign info failed", e);
+ }
+ return certificateHolder;
+ }
+
+ private ASN1Set getPKCS9Attributes(byte[] digest) {
+ ASN1EncodableVector table = new ASN1EncodableVector();
+ Attribute signingTimeAttr = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_signingTime,
+ new DERSet(new Time(new Date())));
+ Attribute contentTypeAttr = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_contentType,
+ new DERSet(PKCSObjectIdentifiers.data));
+ Attribute messageDigestAttr = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_messageDigest,
+ new DERSet(new DEROctetString(digest)));
+ table.add(signingTimeAttr);
+ table.add(contentTypeAttr);
+ table.add(messageDigestAttr);
+ return new DERSet(table);
+ }
+
+ private boolean verifySignFromServer(
+ PublicKey publicKey,
+ byte[] signBytes,
+ Pair signPair,
+ byte[] authed)
+ throws CodeSignException {
+ try {
+ Signature signature = Signature.getInstance(signPair.getKey());
+ signature.initVerify(publicKey);
+ if (signPair.getValue() != null) {
+ signature.setParameter(signPair.getValue());
+ }
+ signature.update(authed);
+ if (!signature.verify(signBytes)) {
+ throw new CodeSignException("Signature verify failed");
+ }
+ return true;
+ } catch (InvalidKeyException | java.security.SignatureException e) {
+ LOGGER.error("The generated signature could not be verified "
+ + " using the public key in the certificate", e);
+ } catch (NoSuchAlgorithmException e) {
+ LOGGER.error("The generated signature " + signPair.getKey()
+ + " could not be verified using the public key in the certificate", e);
+ } catch (InvalidAlgorithmParameterException e) {
+ LOGGER.error("The generated signature " + signPair.getValue()
+ + " could not be verified using the public key in the certificate", e);
+ }
+ return false;
+ }
+
+ private ASN1Set createBerSetFromLst(List> lists) throws CodeSignException {
+ if (lists == null || lists.size() == 0) {
+ return null;
+ }
+ ASN1EncodableVector vector = new ASN1EncodableVector();
+ for (Object obj : lists) {
+ if (obj instanceof X509CRL) {
+ vector.add(getJcaX509CRLHolder((X509CRL) obj).toASN1Structure());
+ } else if (obj instanceof X509Certificate) {
+ vector.add(getJcaX509CertificateHolder((X509Certificate) obj).toASN1Structure());
+ }
+ }
+ return new BERSet(vector);
+ }
+
+ private byte[] encodingUnsignedData(byte[] unsignedDataDigest, SignedData signedData) throws CodeSignException {
+ byte[] signResult;
+ try {
+ ContentInfo contentInfo = new ContentInfo(PKCSObjectIdentifiers.signedData, signedData);
+ signResult = contentInfo.getEncoded(ASN1Encoding.DER);
+ } catch (IOException e) {
+ throw new CodeSignException("Failed to encode unsigned data!", e);
+ }
+ verifySignResult(unsignedDataDigest, signResult);
+ return signResult;
+ }
+
+ private void verifySignResult(byte[] unsignedDataDigest, byte[] signResult) throws CodeSignException {
+ boolean result = false;
+ try {
+ result = CmsUtils.verifySignDataWithUnsignedDataDigest(unsignedDataDigest, signResult);
+ } catch (CMSException e) {
+ throw new CodeSignException("Failed to verify signed data and unsigned data digest", e);
+ }
+ if (!result) {
+ throw new CodeSignException("PKCS cms data did not verify");
+ }
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SecureHashAlgorithm.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SecureHashAlgorithm.java
new file mode 100644
index 0000000000000000000000000000000000000000..0aaea5a363899b31e18090ba18b4c26928131af0
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SecureHashAlgorithm.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.sign;
+
+/**
+ * Secure hash algorithm
+ *
+ * @since 2023/06/05
+ */
+public enum SecureHashAlgorithm {
+ SHA256("SHA-256", 256 / 8),
+ SHA384("SHA-384", 384 / 8),
+ SHA512("SHA-512", 512 / 8);
+
+ private final String hashAlgorithm;
+
+ private final int digestByteSize;
+
+ SecureHashAlgorithm(String hashAlgorithm, int digestByteSize) {
+ this.hashAlgorithm = hashAlgorithm;
+ this.digestByteSize = digestByteSize;
+ }
+
+ public String getHashAlgorithm() {
+ return hashAlgorithm;
+ }
+
+ public int getdigestByteSize() {
+ return digestByteSize;
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignAlgorithm.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignAlgorithm.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b80c04e01fb203a0868cc48cfeed791af90e45c
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignAlgorithm.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.sign;
+
+/**
+ * Signature algorithm
+ *
+ * @since 2023/06/05
+ */
+public enum SignAlgorithm {
+ RSA_PSS_WITH_SHA256(0x101, "RSA", SecureHashAlgorithm.SHA256, SignAlgorithmAndParams.SHA256_WITH_RSA_AND_MGF1),
+ RSA_PSS_WITH_SHA384(0x102, "RSA", SecureHashAlgorithm.SHA384, SignAlgorithmAndParams.SHA384_WITH_RSA_AND_MGF1),
+ RSA_PSS_WITH_SHA512(0x103, "RSA", SecureHashAlgorithm.SHA512, SignAlgorithmAndParams.SHA512_WITH_RSA_AND_MGF1),
+ RSA_PKCS1_V1_5_WITH_SHA256(0x104, "RSA", SecureHashAlgorithm.SHA256, SignAlgorithmAndParams.SHA256_WITH_RSA),
+ RSA_PKCS1_V1_5_WITH_SHA384(0x105, "RSA", SecureHashAlgorithm.SHA384, SignAlgorithmAndParams.SHA384_WITH_RSA),
+ RSA_PKCS1_V1_5_WITH_SHA512(0x106, "RSA", SecureHashAlgorithm.SHA512, SignAlgorithmAndParams.SHA512_WITH_RSA),
+ ECDSA_WITH_SHA256(0x201, "EC", SecureHashAlgorithm.SHA256, SignAlgorithmAndParams.SHA256_WITH_ECDSA),
+ ECDSA_WITH_SHA384(0x202, "EC", SecureHashAlgorithm.SHA384, SignAlgorithmAndParams.SHA384_WITH_ECDSA),
+ ECDSA_WITH_SHA512(0x203, "EC", SecureHashAlgorithm.SHA512, SignAlgorithmAndParams.SHA512_WITH_ECDSA),
+ DSA_WITH_SHA256(0x301, "DSA", SecureHashAlgorithm.SHA256, SignAlgorithmAndParams.SHA256_WITH_DSA),
+ DSA_WITH_SHA384(0x302, "DSA", SecureHashAlgorithm.SHA384, SignAlgorithmAndParams.SHA384_WITH_DSA),
+ DSA_WITH_SHA512(0x303, "DSA", SecureHashAlgorithm.SHA512, SignAlgorithmAndParams.SHA512_WITH_DSA);
+
+ private final int id;
+
+ private final String keyAlgorithm;
+
+ private final SecureHashAlgorithm secureHashAlgorithm;
+
+ private final SignAlgorithmAndParams signAlgorithmAndParams;
+
+ SignAlgorithm(
+ int id,
+ String keyAlgorithm,
+ SecureHashAlgorithm secureHashAlgorithm,
+ SignAlgorithmAndParams signAlgorithmAndParams) {
+ this.id = id;
+ this.keyAlgorithm = keyAlgorithm;
+ this.secureHashAlgorithm = secureHashAlgorithm;
+ this.signAlgorithmAndParams = signAlgorithmAndParams;
+ }
+
+ public String getKeyAlgorithm() {
+ return keyAlgorithm;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public SignAlgorithmAndParams getSignAlgorithmAndParams() {
+ return signAlgorithmAndParams;
+ }
+
+ public SecureHashAlgorithm getSecureHashAlgorithm() {
+ return secureHashAlgorithm;
+ }
+
+ /**
+ * Find SignAlgorithm value by SignAlgorithm object ID.
+ *
+ * @param id Id of SignAlgorithm object.
+ * @return SignAlgorithm value
+ */
+ public static SignAlgorithm findById(int id) {
+ for (SignAlgorithm signAlgorithm : SignAlgorithm.values()) {
+ if (id == signAlgorithm.getId()) {
+ return signAlgorithm;
+ }
+ }
+ return null;
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignAlgorithmAndParams.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignAlgorithmAndParams.java
new file mode 100644
index 0000000000000000000000000000000000000000..1be68a6e5bd98b6077592e31db1a71d1a15d9c36
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignAlgorithmAndParams.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.sign;
+
+import com.ohos.codesigntool.core.utils.Pair;
+import com.ohos.codesigntool.core.utils.ParamConstants;
+
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+
+/**
+ * Signature algorithm and algorithmParameterSpec
+ *
+ * @since 2023/08/01
+ */
+public enum SignAlgorithmAndParams {
+ SHA256_WITH_RSA_AND_MGF1(
+ ParamConstants.SIG_ALGORITHM_SHA256_RSA_MGF1,
+ new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1)),
+ SHA384_WITH_RSA_AND_MGF1(
+ ParamConstants.SIG_ALGORITHM_SHA384_RSA_MGF1,
+ new PSSParameterSpec("SHA-384", "MGF1", MGF1ParameterSpec.SHA384, 384 / 8, 1)),
+ SHA512_WITH_RSA_AND_MGF1(
+ ParamConstants.SIG_ALGORITHM_SHA512_RSA_MGF1,
+ new PSSParameterSpec("SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1)),
+ SHA256_WITH_RSA(ParamConstants.SIG_ALGORITHM_SHA256_RSA, null),
+ SHA384_WITH_RSA(ParamConstants.SIG_ALGORITHM_SHA384_RSA, null),
+ SHA512_WITH_RSA(ParamConstants.SIG_ALGORITHM_SHA512_RSA, null),
+ SHA256_WITH_ECDSA(ParamConstants.SIG_ALGORITHM_SHA256_ECDSA, null),
+ SHA384_WITH_ECDSA(ParamConstants.SIG_ALGORITHM_SHA384_ECDSA, null),
+ SHA512_WITH_ECDSA(ParamConstants.SIG_ALGORITHM_SHA512_ECDSA, null),
+ SHA256_WITH_DSA(ParamConstants.SIG_ALGORITHM_SHA256_DSA, null),
+ SHA384_WITH_DSA(ParamConstants.SIG_ALGORITHM_SHA384_DSA, null),
+ SHA512_WITH_DSA(ParamConstants.SIG_ALGORITHM_SHA512_DSA, null);
+
+ private final Pair pairSignAlgorithmAndParams;
+
+ SignAlgorithmAndParams(String signAlgorithm, PSSParameterSpec params) {
+ this.pairSignAlgorithmAndParams = Pair.create(signAlgorithm, params);
+ }
+
+ public Pair getPairSignAlgorithmAndParams() {
+ return pairSignAlgorithmAndParams;
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignCode.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignCode.java
new file mode 100644
index 0000000000000000000000000000000000000000..552408e1e10d2f4877768507478d1487e14518dd
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignCode.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.sign;
+
+import com.ohos.codesigntool.core.config.CodeSignConfig;
+import com.ohos.codesigntool.core.config.RemoteCodeSignConfig;
+import com.ohos.codesigntool.core.exception.FsVerityDigestException;
+import com.ohos.codesigntool.core.exception.CodeSignException;
+import com.ohos.codesigntool.core.fsverity.FsVerityGenerator;
+import com.ohos.codesigntool.core.utils.HapUtils;
+
+import com.ohos.codesigntool.core.utils.Pair;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * core functions to sign code
+ *
+ * @since 2023/06/05
+ */
+public class SignCode {
+ private static final Logger LOGGER = LogManager.getLogger(SignCode.class);
+ private static final int COMPRESSION_MODE = 9;
+ private static final String HAP_SIGNATURE_ENTRY_NAME = "Hap";
+ private static final String SIGNATURE_FILE_SUFFIX = ".fsv-sig";
+ private static final String MERKLE_TREE_FILE_SUFFIX = ".fsv-tree";
+ private static final String NATIVE_LIB_AN_SUFFIX = ".an";
+ private static final String NATIVE_LIB_SO_SUFFIX = ".so";
+ private static final List EXTRACTED_NATIVE_LIB_SUFFIXS = new ArrayList<>();
+
+ static {
+ EXTRACTED_NATIVE_LIB_SUFFIXS.add(NATIVE_LIB_AN_SUFFIX);
+ EXTRACTED_NATIVE_LIB_SUFFIXS.add(NATIVE_LIB_SO_SUFFIX);
+ }
+
+ private final CodeSignConfig signConfig;
+ private long timestamp = 0L;
+
+ /**
+ * provide code sign functions to sign a hap
+ *
+ * @param signConfig configuration of sign
+ */
+ public SignCode(CodeSignConfig signConfig) {
+ this.signConfig = signConfig;
+ }
+
+ /**
+ * Sign the given hap file, and pack all signature into output file
+ *
+ * @param input file to sign
+ * @param output returned signature file
+ * @param storeTree determine whether merkle tree is also output in signature file
+ * @throws IOException io error
+ * @throws FsVerityDigestException computing FsVerity digest error
+ * @throws CodeSignException signing error
+ */
+ public void signCode(File input, File output, boolean storeTree)
+ throws IOException, FsVerityDigestException, CodeSignException {
+ LOGGER.info("Start to sign code.");
+ try (FileOutputStream outputFile = new FileOutputStream(output);
+ ZipOutputStream outputZip = new ZipOutputStream(outputFile)) {
+ timestamp = System.currentTimeMillis();
+ outputZip.setLevel(COMPRESSION_MODE);
+
+ LOGGER.debug("Sign hap.");
+ try (FileInputStream inputStream = new FileInputStream(input)) {
+ signFileAndAddToZip(inputStream, input.length(), HAP_SIGNATURE_ENTRY_NAME, outputZip, storeTree);
+ }
+
+ // no need to sign native files if libs are not extracted
+ if (!HapUtils.checkCompressNativeLibs(input)) {
+ LOGGER.info("No need to sign native libs.");
+ return;
+ }
+
+ // sign native files
+ try (JarFile inputJar = new JarFile(input, false)) {
+ List entryNames = getNativeEntriesFromHap(inputJar);
+ if (entryNames.isEmpty()) {
+ LOGGER.info("No native libs.");
+ return;
+ }
+ signFilesFromJar(entryNames, inputJar, outputZip, storeTree);
+ }
+ }
+ LOGGER.info("Sign successfully.");
+ }
+
+ /**
+ * Sign a single file with given inputStream
+ *
+ * @param inputStream input stream
+ * @param fileSize file size of the target file
+ * @param entryName the origin entry name of file in hap
+ * @param outputZip output zip which packs the generated signature
+ * @param storeTree determine whether merkle tree is also output
+ * @throws IOException io error
+ * @throws FsVerityDigestException computing FsVerity digest error
+ * @throws CodeSignException signing error
+ */
+ private void signFileAndAddToZip(InputStream inputStream, long fileSize, String entryName,
+ ZipOutputStream outputZip, boolean storeTree)
+ throws IOException, FsVerityDigestException, CodeSignException {
+ Pair result = signFile(inputStream, fileSize);
+ addEntryToZip(result.getKey(), entryName + SIGNATURE_FILE_SUFFIX, outputZip);
+ if (storeTree) {
+ addEntryToZip(result.getValue(), entryName + MERKLE_TREE_FILE_SUFFIX, outputZip);
+ }
+
+ }
+
+ private void addEntryToZip(byte[] data, String entryName, ZipOutputStream outputZip)
+ throws IOException {
+ ZipEntry entry = new ZipEntry(entryName);
+ entry.setTime(timestamp);
+ outputZip.putNextEntry(entry);
+ outputZip.write(data, 0, data.length);
+ outputZip.flush();
+ }
+
+ /**
+ * Get entry name of all native files in hap
+ *
+ * @param hap the given hap
+ * @return list of entry name
+ */
+ private List getNativeEntriesFromHap(JarFile hap) {
+ List result = new ArrayList<>();
+ for (Enumeration e = hap.entries(); e.hasMoreElements();) {
+ JarEntry entry = e.nextElement();
+ if (!entry.isDirectory()) {
+ if (!isNativeFile(entry.getName())) {
+ continue;
+ }
+ result.add(entry.getName());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Check whether the entry is a native file
+ *
+ * @param entryName the name of entry
+ * @return true if it is a native file, and false otherwise
+ */
+ private boolean isNativeFile(String entryName) {
+ for (String suffix : EXTRACTED_NATIVE_LIB_SUFFIXS) {
+ if (entryName.endsWith(suffix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sign specific entries in a hap
+ *
+ * @param entryNames list of entries which need to be signed
+ * @param hap input hap
+ * @param outputZip output zip which packs the generated signature
+ * @param storeTree determine whether merkle tree is also output
+ * @throws IOException io error
+ * @throws FsVerityDigestException computing FsVerity digest error
+ * @throws CodeSignException signing error
+ */
+ public void signFilesFromJar(List entryNames, JarFile hap, ZipOutputStream outputZip, boolean storeTree)
+ throws IOException, FsVerityDigestException, CodeSignException {
+ for (String name : entryNames) {
+ LOGGER.debug("Sign entry name = " + name);
+ JarEntry inEntry = hap.getJarEntry(name);
+ try (InputStream inputStream = hap.getInputStream(inEntry)) {
+ long fileSize = inEntry.getSize();
+ signFileAndAddToZip(inputStream, fileSize, name, outputZip, storeTree);
+ }
+ }
+ }
+
+ /**
+ * Sign a file from input stream
+ *
+ * @param inputStream input stream of a file
+ * @param fileSize size of the file
+ * @return pair of signature and tree
+ * @throws FsVerityDigestException computing FsVerity Digest error
+ * @throws CodeSignException signing error
+ */
+ public Pair signFile(InputStream inputStream, long fileSize)
+ throws FsVerityDigestException, CodeSignException {
+ FsVerityGenerator fsVerityGenerator = new FsVerityGenerator();
+ fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize);
+ byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest();
+ byte[] signature = generateSignature(fsVerityDigest);
+ return Pair.create(signature, fsVerityGenerator.getTreeBytes());
+ }
+
+ private byte[] generateSignature(byte[] signedData) throws CodeSignException {
+ if (!(signConfig instanceof RemoteCodeSignConfig)) {
+ if (signConfig.getX509CertList().isEmpty()) {
+ throw new CodeSignException("No certificates configured for sign");
+ }
+ }
+ return SignedDataGenerator.BC.generateSignedData(signedData, signConfig);
+ }
+
+}
\ No newline at end of file
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignedDataGenerator.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignedDataGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..6986b3fb5e22c3dec1d73118e850402844830044
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/sign/SignedDataGenerator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.sign;
+
+import com.ohos.codesigntool.core.config.CodeSignConfig;
+import com.ohos.codesigntool.core.exception.CodeSignException;
+
+/**
+ * Signed data generator interface
+ *
+ * @since 2023/06/05
+ */
+@FunctionalInterface
+public interface SignedDataGenerator {
+ /**
+ * Creat a BcSignedDataGenerator instance
+ *
+ * @return BcSignedDataGenerator instance.
+ */
+ SignedDataGenerator BC = new BcSignedDataGenerator();
+
+ /**
+ * Generate signature data with specific content and sign configuration.
+ *
+ * @param content unsigned file digest content.
+ * @param signConfig sign configurations.
+ * @return signed data.
+ * @throws CodeSignException if error.
+ */
+ byte[] generateSignedData(byte[] content, CodeSignConfig signConfig) throws CodeSignException;
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/CertUtils.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/CertUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..e746c9131754ad68dcec4ba37a6c8d10e2a1107e
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/CertUtils.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.utils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import javax.security.auth.x500.X500Principal;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Certificate utils class
+ *
+ * @since 2023/06/05
+ */
+public class CertUtils {
+ private static final Logger LOGGER = LogManager.getLogger(CertUtils.class);
+
+ /**
+ * Get certificates chains from input file.
+ *
+ * @param certFilePath input file path
+ * @return list of certificate chain
+ */
+ public static List getCertChainsFromFile(String certFilePath) {
+ List x509CertList = null;
+ try (FileInputStream fileStream = FileUtils.open(new File(certFilePath));) {
+ x509CertList = getX509Certificates(getX509CertificateFactory(), fileStream);
+ } catch (IOException e) {
+ LOGGER.error("Certificate file exception: " + e.getMessage());
+ return Collections.emptyList();
+ }
+ sortCertChain(x509CertList);
+ verifyCertChain(x509CertList);
+ return x509CertList;
+ }
+
+ private static List getX509Certificates(
+ CertificateFactory certificateFactory, FileInputStream fileInputStream) {
+ Collection extends Certificate> certificates = null;
+ try {
+ certificates = certificateFactory.generateCertificates(fileInputStream);
+ } catch (CertificateException e) {
+ LOGGER.error("Certificate file does not exist! " + e.getMessage());
+ }
+ if (certificates == null || certificates.size() == 0) {
+ return Collections.emptyList();
+ }
+ List x509CertList = new ArrayList<>();
+ for (Certificate certificate : certificates) {
+ if (certificate instanceof X509Certificate) {
+ x509CertList.add((X509Certificate) certificate);
+ }
+ }
+ return x509CertList;
+ }
+
+ private static void verifyCertChain(List certs) {
+ if (certs.size() <= 1) {
+ return;
+ }
+ for (int i = 1; i < certs.size(); i++) {
+ try {
+ certs.get(i - 1).verify(certs.get(i).getPublicKey());
+ } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException
+ | NoSuchProviderException | SignatureException e) {
+ LOGGER.error("verify certificate chain failed! " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * certificate chain reversed
+ * when the last certificate subject is not equal to previous certificate issuer
+ *
+ * @param certificates input certificate-chain.
+ */
+ private static void sortCertChain(List certificates) {
+ int size = certificates.size();
+ if (certificates == null || size <= 1) {
+ return;
+ }
+ X500Principal lastSubjectX500Principal = certificates.get(size - 1).getSubjectX500Principal();
+ X500Principal beforeIssuerX500Principal = certificates.get(size - 2).getIssuerX500Principal();
+ if (!lastSubjectX500Principal.equals(beforeIssuerX500Principal)) {
+ Collections.reverse(certificates);
+ }
+ }
+
+ /**
+ * Get the X509Certificate object from the base64-encoded certificate string.
+ *
+ * @param encodeString base64-encoded certificate string.
+ * @return object of X509Certificate.
+ */
+ public static X509Certificate getX509CertByBase64EncodedString(String encodeString) {
+ String header = "-----BEGIN CERTIFICATE-----" + System.lineSeparator();
+ String tail = "-----END CERTIFICATE-----" + System.lineSeparator();
+ byte[] certificateDatas = null;
+ if (encodeString.startsWith(header) && encodeString.endsWith(tail)) {
+ certificateDatas = encodeString.getBytes(StandardCharsets.UTF_8);
+ } else {
+ certificateDatas = Base64.getUrlDecoder().decode(encodeString);
+ }
+ return getX509Certificate(certificateDatas);
+ }
+
+ /**
+ * Get the x509CRL object from the base64-encoded certificate string.
+ *
+ * @param encodeString base64-encoded certificate string
+ * @return an object of x509CRL
+ */
+ public static X509CRL getX509CRLByBase64EncodedString(String encodeString) {
+ byte[] certificateDatas = Base64.getUrlDecoder().decode(encodeString);
+ return getX509CRL(certificateDatas);
+ }
+
+ private static CertificateFactory getX509CertificateFactory() {
+ CertificateFactory certificateFactory = null;
+ try {
+ certificateFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ LOGGER.error("Failed to get X509 certificateFactory ", e);
+ }
+ return certificateFactory;
+ }
+
+ private static X509Certificate getX509Certificate(byte[] certificateDatas) {
+ CertificateFactory certificateFactory = getX509CertificateFactory();
+ Certificate certificate = null;
+ try {
+ certificate = certificateFactory.generateCertificate(
+ new ByteArrayInputStream(certificateDatas));
+ } catch (CertificateException e) {
+ LOGGER.error("Failed to decode base64 string as certificate", e);
+ }
+ if (!(certificate instanceof X509Certificate)) {
+ LOGGER.error("Cannot decode input as X509 cert");
+ return null;
+ }
+ return (X509Certificate) certificate;
+ }
+
+ private static X509CRL getX509CRL(byte[] certificateDatas) {
+ CertificateFactory certificateFactory = getX509CertificateFactory();
+ CRL crl = null;
+ try {
+ crl = certificateFactory.generateCRL(
+ new ByteArrayInputStream(certificateDatas));
+ } catch (CRLException e) {
+ LOGGER.error("Failed to decode base64 string as crl", e);
+ }
+ if (!(crl instanceof X509CRL)) {
+ LOGGER.error("Cannot decode input as X509 crl");
+ return null;
+ }
+ return (X509CRL) crl;
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/CmsUtils.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/CmsUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..61605fae5e8d3efc492766360d5cebbc4f622ad3
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/CmsUtils.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.utils;
+
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+
+import java.security.Security;
+import java.security.cert.CertificateException;
+import java.util.Collection;
+
+/**
+ * CMS utils class
+ *
+ * @since 2023/06/05
+ */
+public class CmsUtils {
+ static {
+ if (Security.getProvider("BC") == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ }
+
+ /**
+ * Private constructor
+ */
+ private CmsUtils() {
+ }
+
+ private static void isCollectionVaild(Collection collection)
+ throws OperatorCreationException {
+ if (collection == null) {
+ throw new OperatorCreationException("No matched cert: " + collection);
+ }
+ if (collection.size() != 1) {
+ throw new OperatorCreationException(
+ "More than one matched certs,matched certs size: " + collection.size());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static boolean verifyCmsSignedData(CMSSignedData cmsSignedData) throws CMSException {
+ return cmsSignedData.verifySignatures(signId -> {
+ Collection collection =
+ cmsSignedData.getCertificates().getMatches(signId);
+ isCollectionVaild(collection);
+ X509CertificateHolder cert = collection.iterator().next();
+ try {
+ return new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert);
+ } catch (CertificateException e) {
+ throw new OperatorCreationException("Verify BC signatures failed: " + e.getMessage(), e);
+ }
+ });
+ }
+
+ /**
+ * Verify signed data using an unsigned data digest
+ *
+ * @param unsignedDataDigest unsigned data digest
+ * @param signedData signed data
+ * @return true if verify success
+ * @throws CMSException if error
+ */
+ public static boolean verifySignDataWithUnsignedDataDigest(byte[] unsignedDataDigest, byte[] signedData)
+ throws CMSException {
+ CMSSignedData cmsSignedData = new CMSSignedData(
+ new CMSProcessableByteArray(unsignedDataDigest), signedData);
+ return verifyCmsSignedData(cmsSignedData);
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/DigestUtils.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/DigestUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..d5b54b0357a9d2d885f4e4723883795d392f2a2c
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/DigestUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.utils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Digest util class
+ *
+ * @since 2023/06/05
+ */
+public class DigestUtils {
+ private static final Logger LOGGER = LogManager.getLogger(DigestUtils.class);
+
+ /**
+ * digest the inputContent with specific algorithm
+ *
+ * @param inputContentArray input Content Array
+ * @param algorithm hash algorithm
+ * @return the result of digest,is a byte array
+ * @throws NoSuchAlgorithmException if error
+ */
+ public static byte[] computeDigest(byte[] inputContentArray, String algorithm) throws NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ md.update(inputContentArray);
+ return md.digest();
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/FileUtils.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/FileUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..05ab106661d0e03973a90c28f8093cfa5fc861ad
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/FileUtils.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.utils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * util function about-file
+ *
+ * @since 2023/06/05
+ */
+public class FileUtils {
+ private static final Logger LOGGER = LogManager.getLogger(FileUtils.class);
+
+ /**
+ * Check input file is valid.
+ *
+ * @param file input file.
+ * @throws IOException file is not valid.
+ */
+ public static void isValid(File file) throws IOException {
+ if (!file.exists()) {
+ throw new FileNotFoundException(file + " does not exist");
+ }
+
+ if (file.isDirectory()) {
+ throw new IOException(file + " is a directory");
+ }
+
+ if (!file.isFile()) {
+ throw new IOException(file + " is not a file!");
+ }
+
+ if (!file.canRead()) {
+ throw new IOException(file + " cannot be read");
+ }
+ }
+
+ /**
+ * Open an inputstream of input file safely.
+ *
+ * @param file input file.
+ * @return file inputStream
+ * @throws IOException file is a directory or can not be read.
+ */
+ public static FileInputStream open(File file) throws IOException {
+ isValid(file);
+ return new FileInputStream(file);
+ }
+}
\ No newline at end of file
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/HapUtils.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/HapUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..b1f9bec3afe73eae9423afcd51e730d612641ff9
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/HapUtils.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.utils;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * utility for check hap configs
+ *
+ * @since 2023/06/05
+ */
+public class HapUtils {
+ /**
+ * Represents the end-of-file.
+ */
+ private static final Logger LOGGER = LogManager.getLogger(HapUtils.class);
+ private static final String COMPRESS_NATIVE_LIBS_OPTION = "compressNativeLibs";
+ private static final List HAP_CONFIG_FILES = new ArrayList();
+ private static final String HAP_FA_CONFIG_JSON_FILE = "config.json";
+ private static final String HAP_STAGE_MODULE_JSON_FILE = "module.json";
+
+ static {
+ HAP_CONFIG_FILES.add(HAP_FA_CONFIG_JSON_FILE);
+ HAP_CONFIG_FILES.add(HAP_STAGE_MODULE_JSON_FILE);
+ }
+
+ private HapUtils() {
+ }
+
+ /**
+ * Check configuration in hap to find out whether the native libs are compressed
+ *
+ * @param hapFile the given hap
+ * @return boolean value of parsing result
+ * @throws IOException io error
+ */
+ public static boolean checkCompressNativeLibs(File hapFile) throws IOException {
+ try (JarFile inputJar = new JarFile(hapFile, false)) {
+ for (String configFile : HAP_CONFIG_FILES) {
+ JarEntry entry = inputJar.getJarEntry(configFile);
+ if (entry == null) {
+ continue;
+ }
+ try (InputStream data = inputJar.getInputStream(entry)) {
+ String jsonString = new String(
+ InputStreamUtils.toByteArray(data, (int) entry.getSize()), StandardCharsets.UTF_8);
+ return checkCompressNativeLibs(jsonString);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check whether the native libs are compressed by parsing config json
+ *
+ * @param jsonString the config json string
+ * @return boolean value of parsing result
+ */
+ public static boolean checkCompressNativeLibs(String jsonString) {
+ JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject();
+ Queue queue = new LinkedList<>();
+ queue.offer(jsonObject);
+ while (queue.size() > 0) {
+ JsonObject curJsonObject = queue.poll();
+ JsonElement jsonElement = curJsonObject.get(COMPRESS_NATIVE_LIBS_OPTION);
+ if (jsonElement != null) {
+ return jsonElement.getAsBoolean();
+ }
+ for (Map.Entry entry : curJsonObject.entrySet()) {
+ if (entry.getValue().isJsonObject()) {
+ queue.offer(entry.getValue().getAsJsonObject());
+ }
+
+ }
+ }
+ // default to compress native libs
+ return true;
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/InputStreamUtils.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/InputStreamUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ca52f3c87cd14942dfecaf775875daea2c1ad73
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/InputStreamUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.utils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * InputStream util class
+ *
+ * @since 2023/08/10
+ */
+public class InputStreamUtils {
+ private static final Logger LOGGER = LogManager.getLogger(InputStream.class);
+ private static final int BUFFER_SIZE = 4096;
+
+ /**
+ * get byte array by inputStream and size
+ *
+ * @param inputStream inputStream data
+ * @param inputStreamSize inputStream size
+ * @return byte array value
+ * @throws IOException io error
+ */
+ public static byte[] toByteArray(InputStream inputStream, int inputStreamSize) throws IOException {
+ if (inputStreamSize == 0) {
+ return new byte[0];
+ }
+ if (inputStreamSize < 0) {
+ throw new IllegalArgumentException("inputStreamSize:" + inputStreamSize + " is less than zero: ");
+ }
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy(inputStream, inputStreamSize, output);
+ return output.toByteArray();
+ }
+
+ private static int copy(InputStream inputStream, int inputStreamSize, OutputStream output) throws IOException {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int readSize = 0;
+ int count = 0;
+ while (readSize < inputStreamSize && (readSize = inputStream.read(buffer)) != -1) {
+ output.write(buffer, 0, readSize);
+ count += readSize;
+ }
+ if (count != inputStreamSize) {
+ throw new IOException("read size err. readSizeCount: " + count + ", inputStreamSize: " + inputStreamSize);
+ }
+ return count;
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/Pair.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/Pair.java
new file mode 100644
index 0000000000000000000000000000000000000000..35401b43308e9bf7a286d439b2f225530ac09ca6
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/Pair.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.utils;
+
+/**
+ * Pair of two elements.
+ *
+ * @since 2023/06/05
+ */
+public final class Pair {
+ private final Key mKey;
+
+ private final Value mValue;
+
+ private Pair(Key key, Value value) {
+ mKey = key;
+ mValue = value;
+ }
+
+ /**
+ * create a pair with key and value
+ *
+ * @param key key of pair
+ * @param value value of pair
+ * @param type of key
+ * @param type of value
+ * @return a pair with key and value
+ */
+ public static Pair create(Key key, Value value) {
+ return new Pair(key, value);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = hashCode(prime, result, mKey);
+ return hashCode(prime, result, mValue);
+ }
+
+ private int hashCode(int prime, int result, T value) {
+ return prime * result + ((value == null) ? 0 : value.hashCode());
+ }
+
+ /**
+ * get value of pair
+ *
+ * @return value of pair
+ */
+ public Value getValue() {
+ return mValue;
+ }
+
+ /**
+ * get key of pair
+ *
+ * @return key of pair
+ */
+ public Key getKey() {
+ return mKey;
+ }
+
+ private boolean compare(T o1, T o2) {
+ if (o1 == null) {
+ return o2 == null;
+ }
+ return o1.equals(o2);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object == null) {
+ return false;
+ }
+ if (!(object instanceof Pair)) {
+ return false;
+ }
+ Pair, ?> pair = (Pair, ?>) object;
+ return compare(mKey, pair.getKey()) && compare(mValue, pair.getValue());
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/ParamConstants.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/ParamConstants.java
new file mode 100644
index 0000000000000000000000000000000000000000..5df3abf45c9bd873f1fe075be168ff791dc71619
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/ParamConstants.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.utils;
+
+/**
+ * define const parameters.
+ *
+ * @since 2023/06/05
+ */
+public class ParamConstants {
+
+ /**
+ * Signature Algorithm name of SHA256withECDSA
+ */
+ public static final String SIG_ALGORITHM_SHA256_ECDSA = "SHA256withECDSA";
+
+ /**
+ * Signature Algorithm name of SHA384withECDSA
+ */
+ public static final String SIG_ALGORITHM_SHA384_ECDSA = "SHA384withECDSA";
+
+ /**
+ * Signature Algorithm name of SHA512withECDSA
+ */
+ public static final String SIG_ALGORITHM_SHA512_ECDSA = "SHA512withECDSA";
+
+ /**
+ * Signature Algorithm name of SHA256withRSA
+ */
+ public static final String SIG_ALGORITHM_SHA256_RSA = "SHA256withRSA";
+
+ /**
+ * Signature Algorithm name of SHA384withRSA
+ */
+ public static final String SIG_ALGORITHM_SHA384_RSA = "SHA384withRSA";
+
+ /**
+ * Signature Algorithm name of SHA512withRSA
+ */
+ public static final String SIG_ALGORITHM_SHA512_RSA = "SHA512withRSA";
+
+ /**
+ * Signature Algorithm name of SHA256withRSA/PSS
+ */
+ public static final String SIG_ALGORITHM_SHA256_RSA_PSS = "SHA256withRSA/PSS";
+
+ /**
+ * Signature Algorithm name of SHA384withRSA/PSS
+ */
+ public static final String SIG_ALGORITHM_SHA384_RSA_PSS = "SHA384withRSA/PSS";
+
+ /**
+ * Signature Algorithm name of SHA512withRSA/PSS
+ */
+ public static final String SIG_ALGORITHM_SHA512_RSA_PSS = "SHA512withRSA/PSS";
+
+ /**
+ * Signature Algorithm name of SHA256withRSAANDMGF1
+ */
+ public static final String SIG_ALGORITHM_SHA256_RSA_MGF1 = "SHA256withRSAANDMGF1";
+
+ /**
+ * Signature Algorithm name of SHA384withRSAANDMGF1
+ */
+ public static final String SIG_ALGORITHM_SHA384_RSA_MGF1 = "SHA384withRSAANDMGF1";
+
+ /**
+ * Signature Algorithm name of SHA512withRSAANDMGF1
+ */
+ public static final String SIG_ALGORITHM_SHA512_RSA_MGF1 = "SHA512withRSAANDMGF1";
+
+ /**
+ * Signature Algorithm name of SHA256withDSA
+ */
+ public static final String SIG_ALGORITHM_SHA256_DSA = "SHA256withDSA";
+
+ /**
+ * Signature Algorithm name of SHA384withDSA
+ */
+ public static final String SIG_ALGORITHM_SHA384_DSA = "SHA384withDSA";
+
+ /**
+ * Signature Algorithm name of SHA512withDSA
+ */
+ public static final String SIG_ALGORITHM_SHA512_DSA = "SHA512withDSA";
+
+ /**
+ * Certificate revoke list.
+ */
+ public static final String PARAM_BASIC_CRL = "crl";
+
+ /**
+ * Private key used in signature.
+ */
+ public static final String PARAM_BASIC_PRIVATE_KEY = "privatekey";
+
+ /**
+ * File used to sign.
+ */
+ public static final String PARAM_BASIC_INPUT_FILE = "inputFile";
+
+ /**
+ * Signed file.
+ */
+ public static final String PARAM_BASIC_OUTPUT_PATH = "outputPath";
+
+ /**
+ * Algorithm name of signatures.
+ */
+ public static final String PARAM_BASIC_SIGN_ALG = "signAlg";
+
+ /**
+ * Flag indicates whether profile is signed.
+ */
+ public static final String PARAM_OUTPUT_MEKLE_TREE = "outTree";
+
+ /**
+ * The certificate-file path.
+ */
+ public static final String PARAM_LOCAL_PUBLIC_CERT = "certpath";
+}
diff --git a/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/ParamProcessUtil.java b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/ParamProcessUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..76632fe7d1d8976469873d4904c8d491f34c6f1d
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/main/java/com/ohos/codesigntool/core/utils/ParamProcessUtil.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.core.utils;
+
+import com.ohos.codesigntool.core.sign.SignAlgorithm;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Utils functions for process parameters.
+ *
+ * @since 2023/06/05
+ */
+public class ParamProcessUtil {
+ private static final Logger LOGGER = LogManager.getLogger(ParamProcessUtil.class);
+ private static Map mapSignAlg =
+ new TreeMap(String.CASE_INSENSITIVE_ORDER);
+
+ static {
+ mapSignAlg.put(ParamConstants.SIG_ALGORITHM_SHA256_ECDSA, SignAlgorithm.ECDSA_WITH_SHA256);
+ mapSignAlg.put(ParamConstants.SIG_ALGORITHM_SHA384_ECDSA, SignAlgorithm.ECDSA_WITH_SHA384);
+ mapSignAlg.put(ParamConstants.SIG_ALGORITHM_SHA512_ECDSA, SignAlgorithm.ECDSA_WITH_SHA512);
+ mapSignAlg.put(ParamConstants.SIG_ALGORITHM_SHA256_RSA_PSS, SignAlgorithm.RSA_PSS_WITH_SHA256);
+ mapSignAlg.put(ParamConstants.SIG_ALGORITHM_SHA384_RSA_PSS, SignAlgorithm.RSA_PSS_WITH_SHA384);
+ mapSignAlg.put(ParamConstants.SIG_ALGORITHM_SHA512_RSA_PSS, SignAlgorithm.RSA_PSS_WITH_SHA512);
+ mapSignAlg.put(ParamConstants.SIG_ALGORITHM_SHA256_RSA_MGF1, SignAlgorithm.RSA_PSS_WITH_SHA256);
+ mapSignAlg.put(ParamConstants.SIG_ALGORITHM_SHA384_RSA_MGF1, SignAlgorithm.RSA_PSS_WITH_SHA384);
+ mapSignAlg.put(ParamConstants.SIG_ALGORITHM_SHA512_RSA_MGF1, SignAlgorithm.RSA_PSS_WITH_SHA512);
+ }
+
+ /**
+ * Get SignAlgorithm value by algorithm name.
+ *
+ * @param signAlgorithm algorithm name.
+ * @return SignAlgorithm value
+ */
+ public static SignAlgorithm getSignAlgorithm(String signAlgorithm) {
+ if (!mapSignAlg.containsKey(signAlgorithm)) {
+ throw new IllegalArgumentException("Unsupported signature algorithm: " + signAlgorithm);
+ }
+ return mapSignAlg.get(signAlgorithm);
+ }
+}
diff --git a/codesigntool/code_sign_tool_lib/src/test/java/com/ohos/codesigntool/test/MerkleTreeTest.java b/codesigntool/code_sign_tool_lib/src/test/java/com/ohos/codesigntool/test/MerkleTreeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef612369675bd3da2f6bb01d8f2c2d5b5d307fe4
--- /dev/null
+++ b/codesigntool/code_sign_tool_lib/src/test/java/com/ohos/codesigntool/test/MerkleTreeTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.codesigntool.test;
+
+import com.ohos.codesigntool.core.exception.FsVerityDigestException;
+import com.ohos.codesigntool.core.fsverity.FsVerityHashAlgorithm;
+import com.ohos.codesigntool.core.fsverity.MerkleTree;
+import com.ohos.codesigntool.core.fsverity.MerkleTreeBuilder;
+import com.ohos.codesigntool.core.utils.FileUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * MerkleTree Hash Test.
+ *
+ * @since 2023/06/27
+ */
+public class MerkleTreeTest {
+ private static final String SMALL_UNFULL_CHUNK_SHA256_HASH =
+ "e7295a358de9d164adc111e80f7e6298e9f5abe44e94f4fb181ac85267a200c6";
+
+ private static final String SMALL_UNFULL_CHUNK_SHA512_HASH =
+ "b4173cc4b36f910efac553871044ac9a211f7658cbf406711f7c1492116069f4" +
+ "a30f04f03a21229da1df85201e1e2aa79ce40f8e1a95d82be3d522ea43a7b02b";
+
+ private static final String MID_UNFULL_CHUNK_SHA256_HASH =
+ "bcf7182fe7de3fb2076a648f95c09891fb050d318f09ce29e4c1e91b64ae92d2";
+
+ private static final String MID_UNFULL_CHUNK_SHA512_HASH =
+ "70f766c85a3cb4adec160668e1bbba68aa7e780414e147feb74cabce0e5aab60" +
+ "08b7c9142a8a1392e5d39528feee666cc3b4456de11e76e8737e251f7fa66251";
+
+ private static final String MID_FULL_CHUNK_SHA256_HASH =
+ "4cba0ca57bb71fb3ab739499eb448cceaac3728edc15ce0984bb3ecadb8f43bb";
+
+ private static final String MID_FULL_CHUNK_SHA512_HASH =
+ "a623e2b24335507d35190e0dc750ad26f853ac8da75d1e0dcccca76b7634a0b0" +
+ "5447f45a43376d75cc032b99364f97260a563be61fbdcd68c232fd326c817af0";
+
+ /**
+ * Test check small unfull chunk data sha256 hash
+ *
+ * @throws FsVerityDigestException on error.
+ */
+ @Test
+ public void testSmallUnFullChunkSha256Hash() throws FsVerityDigestException {
+ String path = "src/test/resources/hashtestfile/4095file";
+ checkFileHash(path, SMALL_UNFULL_CHUNK_SHA256_HASH, FsVerityHashAlgorithm.SHA256);
+ }
+
+ /**
+ * Test check mid unfull chunk data sha256 hash
+ *
+ * @throws FsVerityDigestException on error.
+ */
+ @Test
+ public void testMidUnFullChunkSha256Hash() throws FsVerityDigestException {
+ String path = "src/test/resources/hashtestfile/1000000file";
+ checkFileHash(path, MID_UNFULL_CHUNK_SHA256_HASH, FsVerityHashAlgorithm.SHA256);
+ }
+
+ /**
+ * Test check mid full chunk data sha256 hash
+ *
+ * @throws FsVerityDigestException on error.
+ */
+ @Test
+ public void testMidFullChunkSha256Hash() throws FsVerityDigestException {
+ String path = "src/test/resources/hashtestfile/10485760file";
+ checkFileHash(path, MID_FULL_CHUNK_SHA256_HASH, FsVerityHashAlgorithm.SHA256);
+ }
+
+ /**
+ * Test check small unfull chunk data sha512 hash
+ *
+ * @throws FsVerityDigestException on error.
+ */
+ @Test
+ public void testSmallUnFullChunkSha512Hash() throws FsVerityDigestException {
+ String path = "src/test/resources/hashtestfile/4095file";
+ checkFileHash(path, SMALL_UNFULL_CHUNK_SHA512_HASH, FsVerityHashAlgorithm.SHA512);
+ }
+
+ /**
+ * Test check mid unfull chunk data sha512 hash
+ *
+ * @throws FsVerityDigestException on error.
+ */
+ @Test
+ public void testMidUnFullChunkSha512Hash() throws FsVerityDigestException {
+ String path = "src/test/resources/hashtestfile/1000000file";
+ checkFileHash(path, MID_UNFULL_CHUNK_SHA512_HASH, FsVerityHashAlgorithm.SHA512);
+ }
+
+ /**
+ * Test check mid full chunk data sha512 hash
+ *
+ * @throws FsVerityDigestException on error.
+ */
+ @Test
+ public void testMidFullChunkSha512Hash() throws FsVerityDigestException {
+ String path = "src/test/resources/hashtestfile/10485760file";
+ checkFileHash(path, MID_FULL_CHUNK_SHA512_HASH, FsVerityHashAlgorithm.SHA512);
+ }
+
+ private void checkFileHash(String path, String hash, FsVerityHashAlgorithm fsVerityHashAlgorithm)
+ throws FsVerityDigestException {
+ MerkleTree merkleTree;
+ File tempFile = new File(path);
+ try (FileInputStream inputStream = FileUtils.open(tempFile);) {
+ MerkleTreeBuilder builder = new MerkleTreeBuilder();
+ merkleTree = builder.generateMerkleTree(inputStream, tempFile.length(), fsVerityHashAlgorithm);
+ builder.close();
+ } catch (IOException | NoSuchAlgorithmException e) {
+ throw new FsVerityDigestException("IOException" + e.getMessage(), e);
+ }
+ String stringHash = byte2hex(merkleTree.rootHash);
+ Assert.assertEquals(stringHash, hash);
+ }
+
+ private String byte2hex(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ String tmp = null;
+ for (byte b : bytes) {
+ tmp = Integer.toHexString(0xFF & b);
+ if (tmp.length() == 1) {
+ tmp = "0" + tmp;
+ }
+ sb.append(tmp);
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/codesigntool/code_sign_tool_lib/src/test/resources/hashtestfile/1000000file b/codesigntool/code_sign_tool_lib/src/test/resources/hashtestfile/1000000file
new file mode 100644
index 0000000000000000000000000000000000000000..03ee4904a8274847a61799cfb256545f3983938f
Binary files /dev/null and b/codesigntool/code_sign_tool_lib/src/test/resources/hashtestfile/1000000file differ
diff --git a/codesigntool/code_sign_tool_lib/src/test/resources/hashtestfile/10485760file b/codesigntool/code_sign_tool_lib/src/test/resources/hashtestfile/10485760file
new file mode 100644
index 0000000000000000000000000000000000000000..b8711b2f2a73567abc4baaad24e20d9c7f301125
Binary files /dev/null and b/codesigntool/code_sign_tool_lib/src/test/resources/hashtestfile/10485760file differ
diff --git a/codesigntool/code_sign_tool_lib/src/test/resources/hashtestfile/4095file b/codesigntool/code_sign_tool_lib/src/test/resources/hashtestfile/4095file
new file mode 100644
index 0000000000000000000000000000000000000000..ad9838293c55c22a7e909a80d9fb73cbc1ac4f9a
Binary files /dev/null and b/codesigntool/code_sign_tool_lib/src/test/resources/hashtestfile/4095file differ
diff --git a/codesigntool/pom.xml b/codesigntool/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8b97e15022e55b801ea86a84847bdf5d53bd81e0
--- /dev/null
+++ b/codesigntool/pom.xml
@@ -0,0 +1,110 @@
+
+
+
+
+ 4.0.0
+
+ com.ohos.codesigntool
+ codesigntool
+ ${revision}
+ pom
+
+
+ 8
+ 8
+ UTF-8
+ 1.0.0.0
+ 1.71
+
+
+
+ code_sign_tool_lib
+ code_sign_lib
+
+
+
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+ 1.69
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.18.0
+
+
+ com.google.code.gson
+ gson
+ 2.9.0
+
+
+ org.apache.commons
+ commons-lang3
+ 3.1
+
+
+ com.mikesamuel
+ json-sanitizer
+ 1.2.2
+
+
+ com.ohos.codesigntool
+ code_sign_tool_lib
+ ${project.version}
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ 3.1.0
+
+ false
+
+
+ outputs
+ false
+ true
+
+ **/*
+
+
+
+ findBugCheckDocument
+ false
+ true
+
+ **/*
+
+
+
+
+
+
+
+