diff --git a/README.md b/README.md index 04a052a4c0aa121b15ced6b456cc391123347bc7..314e0f59854c8c478915b673f6ba7e72b5d70ae7 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ The parameters in the command are described as follows: ```shell -java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" +java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1" ``` The parameters in the command are described as follows: @@ -103,6 +103,7 @@ The parameters in the command are described as follows: ├── -keystoreFile # KeyStore (KS) file, in JKS or P12 format. It is mandatory if the signing mode is localSign. ├── -keystorePwd # KS password. It is optional. ├── -outFile # Signed HAP file to generate. It is mandatory. + ├── -codesign # Signed HAP file to code sign, The value 1 means code signed, and value 0 means code unsigned. The default value is 1. It is optional. 2. One-click signature @@ -276,6 +277,7 @@ Procedure: ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory if the signing mode is localSign. ├── -keystorePwd # KS password. It is optional. ├── -outFile # Signed HAP file to generate. It is mandatory. + ├── -codesign # Signed HAP file to code sign, The value 1 means code signed, and value 0 means code unsigned. The default value is 1. It is optional. 10.Verify the HAP Signature. diff --git a/README_ZH.md b/README_ZH.md index 5ce50a28953648bd0b855f8d0e1ac0edc18d3149..2be1bfc220b51d8677bc60afca9febb6b0239149 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -96,7 +96,7 @@ java -jar hap-sign-tool.jar sign-profile -keyAlias "oh-profile1-key-v1" -signAl ```shell -java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" +java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1" ``` 该命令的参数说明如下: @@ -113,7 +113,8 @@ java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256 ├── -keystoreFile #密钥库文件,localSign模式时为必填项,JKS或P12格式 ├── -keystorePwd #密钥库口令,可选项 ├── -outFile #输出签名后的包文件,必填项 - + ├── -codesign #指示包文件是否带有代码签名,1表示有代码签名,0表示没有代码签名,默认1。可选项 + 2.一键签名 @@ -286,6 +287,7 @@ java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256 ├── -keystoreFile # 密钥库文件,localSign模式时为必填项,JKS或P12格式 ├── -keystorePwd # 密钥库口令,可选项 ├── -outFile # 输出签名后的包文件,必填项 + ├── -codesign # 指示包文件是否带有代码签名,1表示有代码签名,0表示没有代码签名,默认1。可选项 10.hap应用包文件验签 diff --git a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java index ab4c6cdb35d93af34fbf47ac63adc34b95d44905..0d7cd776e7ffc445cc41e146659bf5934ec99394 100644 --- a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -277,7 +277,7 @@ public final class HapSignTool { } checkProfile(params); String inForm = params.getString(Options.IN_FORM); - if (!StringUtils.isEmpty(inForm) && !"zip".equalsIgnoreCase(inForm) && !"bin".equalsIgnoreCase(inForm)) { + if (!StringUtils.isEmpty(inForm) && !"zip".equalsIgnoreCase(inForm) && !"bin".equalsIgnoreCase(inForm) && !"elf".equalsIgnoreCase(inForm)) { CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, "inForm params is incorrect"); } String signAlg = params.getString(Options.SIGN_ALG); @@ -321,7 +321,7 @@ public final class HapSignTool { private static boolean runVerifyApp(Options params, ServiceApi api) { params.required(Options.IN_FILE, Options.OUT_CERT_CHAIN, Options.OUT_PROFILE); - FileUtils.validFileType(params.getString(Options.IN_FILE), "hap", "bin"); + FileUtils.validFileType(params.getString(Options.IN_FILE), "hap"); FileUtils.validFileType(params.getString(Options.OUT_CERT_CHAIN), "cer"); FileUtils.validFileType(params.getString(Options.OUT_PROFILE), "p7b"); return api.verifyHap(params); diff --git a/hapsigntool/hap_sign_tool/src/main/resources/help.txt b/hapsigntool/hap_sign_tool/src/main/resources/help.txt index 4f293806a83eb915ac8de69a36c3b109391a92ec..216713aff15a96b664f90f1376a66ea573216a0b 100644 --- a/hapsigntool/hap_sign_tool/src/main/resources/help.txt +++ b/hapsigntool/hap_sign_tool/src/main/resources/help.txt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -183,6 +183,7 @@ USAGE: [options] -username : user account for online auth, required fields on remoteSign mode with account auth mode; -userPwd : user password for online auth, required fields on remoteSign mode with account auth mode; -ext : extend parameters for remote signer plugin, optional fields; + -codesign : 0 do not code sign, 1 do code sign, default 1; EXAMPLE: sign-app -mode localSign -keyAlias "oh-app1-key-v1" -appCertFile "D:\OH\app-release-cert.cer" -profileFile "D:\OH\signed-profile.p7b" -inFile "D:\OH\app1-unsigned.hap" -signAlg SHA256withECDSA -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outFile "D:\OH\app1-signed.hap -compatibleVersion 8" diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java index ff79b211a39cc5c3418060ef2de062d0e799a9c7..a0a016577fce4b3bf149f7aa1741f3e663666867 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -44,10 +44,8 @@ import java.security.Security; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Arrays; +import java.util.List; /** * Main entry of lib. @@ -322,6 +320,8 @@ public class SignToolServiceImpl implements ServiceApi { String inForm = options.getString(Options.IN_FORM, "zip"); if ("zip".equalsIgnoreCase(inForm)) { return signProvider.sign(options); + } else if ("elf".equalsIgnoreCase(inForm)) { + return signProvider.signElf(options); } else { return signProvider.signBin(options); } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java new file mode 100644 index 0000000000000000000000000000000000000000..40679e46f2debe76c55a50d57980aaad9cd708d4 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * code sign block is a chunk of bytes attached to hap package or file. + * It consists of two headers: + * 1) code sign block header + * 2) segment header + * three segments: + * 1) fs-verity info segment + * 2) hap info segment + * 3) so info segment + * one zero padding area in order to align merkle tree raw bytes + * 1) zero padding + * and one area storing merkle tree bytes: + * 1) merkle tree raw bytes + *

+ * After signing a hap, call toByteArray() method to generate a block of bytes. + * + * @since 2023/09/08 + */ +public class CodeSignBlock { + /** + * page size in bytes + */ + public static final long PAGE_SIZE_4K = 4096L; + + /** + * Segment header count, including fs-verity info, hap info, so info segment + */ + public static final int SEGMENT_HEADER_COUNT = 3; + + private CodeSignBlockHeader codeSignBlockHeader; + + private final List segmentHeaderList; + + private FsVerityInfoSegment fsVerityInfoSegment; + + private HapInfoSegment hapInfoSegment; + + private NativeLibInfoSegment nativeLibInfoSegment; + + private byte[] zeroPadding; + + private final Map merkleTreeMap; + + public CodeSignBlock() { + this.codeSignBlockHeader = new CodeSignBlockHeader.Builder().build(); + this.segmentHeaderList = new ArrayList<>(); + this.fsVerityInfoSegment = new FsVerityInfoSegment(); + this.hapInfoSegment = new HapInfoSegment(); + this.nativeLibInfoSegment = new NativeLibInfoSegment.Builder().build(); + this.merkleTreeMap = new HashMap<>(); + } + + /** + * Add one merkle tree into merkleTreeMap + * + * @param key file name + * @param merkleTree merkle tree raw bytes + */ + public void addOneMerkleTree(String key, byte[] merkleTree) { + if (merkleTree == null) { + this.merkleTreeMap.put(key, new byte[0]); + } else { + this.merkleTreeMap.put(key, merkleTree); + } + } + + /** + * Get one merkle tree from merkleTreeMap by file name + * + * @param key file name + * @return merkle tree bytes + */ + public byte[] getOneMerkleTreeByFileName(String key) { + return this.merkleTreeMap.get(key); + } + + /** + * set code sign block flag + */ + public void setCodeSignBlockFlag() { + int flags = CodeSignBlockHeader.FLAG_MERKLE_TREE_INLINED; + if (this.nativeLibInfoSegment.getSectionNum() != 0) { + flags += CodeSignBlockHeader.FLAG_NATIVE_LIB_INCLUDED; + } + this.codeSignBlockHeader.setFlags(flags); + } + + /** + * set segmentNum defined in code sign block header, equals length of segmentHeaderList + */ + public void setSegmentNum() { + this.codeSignBlockHeader.setSegmentNum(segmentHeaderList.size()); + } + + /** + * add one segment to segmentHeaderList + * + * @param sh segment header + */ + public void addToSegmentList(SegmentHeader sh) { + this.segmentHeaderList.add(sh); + } + + public List getSegmentHeaderList() { + return segmentHeaderList; + } + + /** + * set segment header list + */ + public void setSegmentHeaders() { + // fs-verity info segment + segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_FSVERITY_INFO_SEG, this.fsVerityInfoSegment.size())); + // hap info segment + segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_HAP_META_SEG, this.hapInfoSegment.size())); + // native lib info segment + segmentHeaderList.add( + new SegmentHeader(SegmentHeader.CSB_NATIVE_LIB_INFO_SEG, this.nativeLibInfoSegment.size())); + } + + public CodeSignBlockHeader getCodeSignBlockHeader() { + return codeSignBlockHeader; + } + + public void setCodeSignBlockHeader(CodeSignBlockHeader csbHeader) { + this.codeSignBlockHeader = csbHeader; + } + + public void setFsVerityInfoSegment(FsVerityInfoSegment fsVeritySeg) { + this.fsVerityInfoSegment = fsVeritySeg; + } + + public FsVerityInfoSegment getFsVerityInfoSegment() { + return fsVerityInfoSegment; + } + + public HapInfoSegment getHapInfoSegment() { + return hapInfoSegment; + } + + public void setHapInfoSegment(HapInfoSegment hapSeg) { + this.hapInfoSegment = hapSeg; + } + + public NativeLibInfoSegment getSoInfoSegment() { + return nativeLibInfoSegment; + } + + public void setSoInfoSegment(NativeLibInfoSegment soSeg) { + this.nativeLibInfoSegment = soSeg; + } + + /** + * Convert code sign block object to a newly created byte array + * + * @return Byte array representation of a CodeSignBlock object + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(this.codeSignBlockHeader.getBlockSize()).order(ByteOrder.LITTLE_ENDIAN); + bf.put(this.codeSignBlockHeader.toByteArray()); + for (SegmentHeader sh : this.segmentHeaderList) { + bf.put(sh.toByteArray()); + } + bf.put(this.zeroPadding); + // Hap merkle tree + if (this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED) != null) { + bf.put(merkleTreeMap.get("Hap")); + } + bf.put(this.fsVerityInfoSegment.toByteArray()); + bf.put(this.hapInfoSegment.toByteArray()); + bf.put(this.nativeLibInfoSegment.toByteArray()); + return bf.array(); + } + + /** + * SegmentOffset is the position of each segment defined in segmentHeaderList, + * based on the start position of code sign block + */ + public void computeSegmentOffset() { + // 1) the first segment is placed after merkle tree + int segmentOffset = CodeSignBlockHeader.size() + + this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length + + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length; + for (SegmentHeader sh : segmentHeaderList) { + sh.setSegmentOffset(segmentOffset); + segmentOffset += sh.getSegmentSize(); + } + } + + /** + * Compute the offset to store merkle tree raw bytes based on file start + * + * @param codeSignBlockOffset offset to store code sign block based on file start + * @return offset to store merkle tree based on the file start, it includes the codeSignBlockOffset + */ + public long computeMerkleTreeOffset(long codeSignBlockOffset) { + long sizeWithoutMerkleTree = CodeSignBlockHeader.size() + + SEGMENT_HEADER_COUNT * SegmentHeader.SEGMENT_HEADER_LENGTH; + // add code sign block offset while computing align position for merkle tree + long residual = (codeSignBlockOffset + sizeWithoutMerkleTree) % PAGE_SIZE_4K; + if (residual == 0) { + this.zeroPadding = new byte[0]; + } else { + this.zeroPadding = new byte[(int) (PAGE_SIZE_4K - residual)]; + } + return codeSignBlockOffset + sizeWithoutMerkleTree + zeroPadding.length; + } + + /** + * Convert CodeSignBlock to bytes + * + * @param fsvTreeOffset merkle tree offset + * @return byte array representing the code sign block + */ + public byte[] generateCodeSignBlockByte(long fsvTreeOffset) { + // 1) compute overall block size without merkle tree + long csbSize = CodeSignBlockHeader.size() + + (long) this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length + + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length + + this.fsVerityInfoSegment.size() + this.hapInfoSegment.size() + this.nativeLibInfoSegment.size(); + Extension ext = this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); + if (ext instanceof MerkleTreeExtension) { + MerkleTreeExtension merkleTreeExtension = (MerkleTreeExtension) ext; + merkleTreeExtension.setMerkleTreeOffset(fsvTreeOffset); + } + this.codeSignBlockHeader.setBlockSize(csbSize); + // 2) generate byte array of complete code sign block + return toByteArray(); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, + "CodeSignBlockHeader[%s], SegmentHeaders[%s], FsVeritySeg[%s], HapInfoSeg[%s], SoInfoSeg[%s]", + this.codeSignBlockHeader, Arrays.toString(this.segmentHeaderList.toArray()), this.fsVerityInfoSegment, + this.hapInfoSegment, this.nativeLibInfoSegment); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..1e81a60a9dc0a5be80ef33cc0a811c3cc089c5f2 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Code sign block header + *

+ * Structure: + * 1) u64 magic: magic number + * 2) u32 version: sign tool version + * 3) u32 blockSize: size of code sign block + * 4) u32 segmentNum: number of segments, i.e. FsVerityInfoSegment, HapInfoSegment, SoInfoSegment + * 5) u32 flags + * 6) u8[8] reserved: for reservation + *

+ * The Size of Code sign Block header if fixed, getBlockLength() method returns the size. + * + * @since 2023/09/08 + */ +public class CodeSignBlockHeader { + /** + * Flag indicating that merkle tree is included in code sign block + */ + public static final int FLAG_MERKLE_TREE_INLINED = 0x1; + + /** + * Flag indicating that native lib is included in code sign block + */ + public static final int FLAG_NATIVE_LIB_INCLUDED = 0x2; + + // code signing version + private static final int CODE_SIGNING_VERSION = 1; + + // byte size of magic number + private static final byte MAGIC_BYTE_ARRAY_LENGTH = Long.BYTES; + + // lower 8 bytes of MD5 result of string "hap code sign block" (E046 C8C6 5389 FCCD) + private static final long MAGIC_NUM = ((0xE046C8C6L << 32) + 0x5389FCCDL); + + // size of byte[8] reserved + private static final byte RESERVED_BYTE_ARRAY_LENGTH = 8; + + // At all times three segment are always included in code sign block, update this if new segments are created. + private static final int SEGMENT_NUM = 3; + + private long magic; + + private int version; + + private int blockSize; + + private int segmentNum; + + private int flags; + + private byte[] reserved; + + /** + * Construct of CodeSignBlockHeader + * + * @param builder builder + */ + private CodeSignBlockHeader(Builder builder) { + this.magic = builder.magic; + this.version = builder.version; + this.blockSize = builder.blockSize; + this.segmentNum = builder.segmentNum; + this.flags = builder.flags; + this.reserved = builder.reserved; + } + + public void setSegmentNum(int num) { + this.segmentNum = num; + } + + public int getSegmentNum() { + return segmentNum; + } + + public void setBlockSize(long size) { + this.blockSize = (int) size; + } + + public int getBlockSize() { + return blockSize; + } + + public void setFlags(int flags) { + this.flags = flags; + } + + /** + * Converts code sign block headers to a newly created byte array + * + * @return Byte array representation of a code sign block header + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN); + bf.putLong(magic); + bf.putInt(version); + bf.putInt(blockSize); + bf.putInt(segmentNum); + bf.putInt(flags); + bf.put(reserved); + return bf.array(); + } + + /** + * Init the CodeSignBlockHeader by a byte array + * + * @param bytes Byte array representation of a CodeSignBlockHeader object + * @return a newly created CodeSignBlockHeader object + * @throws VerifyCodeSignException parse result invalid + */ + public static CodeSignBlockHeader fromByteArray(byte[] bytes) throws VerifyCodeSignException { + if (bytes.length != size()) { + throw new VerifyCodeSignException("Invalid size of CodeSignBlockHeader"); + } + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + // after put, rewind is mandatory before get + bf.rewind(); + long inMagic = bf.getLong(); + if (inMagic != MAGIC_NUM) { + throw new VerifyCodeSignException("Invalid magic num of CodeSignBlockHeader"); + } + int inVersion = bf.getInt(); + if (inVersion != CODE_SIGNING_VERSION) { + throw new VerifyCodeSignException("Invalid version of CodeSignBlockHeader"); + } + int inBlockSize = bf.getInt(); + int inSegmentNum = bf.getInt(); + if (inSegmentNum != SEGMENT_NUM) { + throw new VerifyCodeSignException("Invalid segmentNum of CodeSignBlockHeader"); + } + int inFlags = bf.getInt(); + if (inFlags < 0 || inFlags > (FLAG_MERKLE_TREE_INLINED + FLAG_NATIVE_LIB_INCLUDED)) { + throw new VerifyCodeSignException("Invalid flags of CodeSignBlockHeader"); + } + byte[] inReserved = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + bf.get(inReserved); + return new Builder().setMagic(inMagic).setVersion(inVersion).setBlockSize(inBlockSize) + .setSegmentNum(inSegmentNum).setFlags(inFlags).setReserved(inReserved).build(); + } + + /** + * Return the byte size of code sign block header + * + * @return byte size of code sign block header + */ + public static int size() { + return MAGIC_BYTE_ARRAY_LENGTH + Integer.BYTES * 4 + RESERVED_BYTE_ARRAY_LENGTH; + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, + "CodeSignBlockHeader{magic: %d, version: %d, blockSize: %d, segmentNum: %d, flags: %d}", this.magic, + this.version, this.blockSize, this.segmentNum, this.flags); + } + + /** + * Builder of CodeSignBlockHeader class + */ + public static class Builder { + private long magic = MAGIC_NUM; + + private int version = CODE_SIGNING_VERSION; + + private int blockSize; + + private int segmentNum; + + private int flags; + + private byte[] reserved = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + + public Builder setMagic(long magic) { + this.magic = magic; + return this; + } + + public Builder setVersion(int version) { + this.version = version; + return this; + } + + public Builder setBlockSize(int blockSize) { + this.blockSize = blockSize; + return this; + } + + public Builder setSegmentNum(int segmentNum) { + this.segmentNum = segmentNum; + return this; + } + + public Builder setFlags(int flags) { + this.flags = flags; + return this; + } + + public Builder setReserved(byte[] reserved) { + this.reserved = reserved; + return this; + } + + public CodeSignBlockHeader build() { + return new CodeSignBlockHeader(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java new file mode 100644 index 0000000000000000000000000000000000000000..c9a13268601fa83c968fb8b0d37378ac93951307 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * elf sign block is a chunk of bytes attached to elf file. + * 1) u32 type: 0x2 merkle tree + * 2) u32 length: merkle tree with padding size + * 3) u8[] merkle tree data + * 4) u32 type: 0x1 fsverity descriptor + * 5) u32 length: fsverity descriptor size + * 6) u8 version: fs-verity version + * 7) u8 hashAlgorithm: hash algorithm to use for the Merkle tree + * 8) u8 log2BlockSize: log2 of size of data and tree blocks + * 9) u8 saltSize: byte size of salt + * 10) u32 signSize: byte size of signature + * 11) u64 dataSize: byte size of data being signed + * 12) u8[64] rootHash: merkle tree root hash + * 13) u8[32] salt: salt used in signing + * 14) u32 flags + * 15) u32 reserved + * 16) u64 treeOffset: merkle tree offset + * 17) u8[128] reserved + * 18) u8 csVersion: code sign version + * 19) u8[] signature: signature after signing the data in byte array representation + * + * @since 2023/09/08 + */ +public class ElfSignBlock { + /** + * page size in bytes + */ + public static final int PAGE_SIZE_4K = 4096; + + /** + * Type of MerkleTree + */ + public static final int MERKLE_TREE_INLINED = 0x2; + + /** + * code sign version + */ + public static final byte CODE_SIGN_VERSION = 0x1; + + private static final int ROOT_HASH_SIZE = 64; + + private static final int SALT_BUFFER_LENGTH = 32; + + private static final int RESERVED_BYTE_ARRAY_LENGTH = 127; + + private static final int FSD_WITHOUT_SIGN_LENGTH = 256; + + private int treeType; + + private int treeLength; + + // zero padding before merkle tree + private byte[] paddingBeforeTree; + + private byte[] merkleTreeData; + + private int fsdType; + + private int fsdLength; + + private byte fsVersion; + + private byte fsHashAlgorithm; + + private byte log2BlockSize; + + private byte saltSize; + + private int signSize; + + private long dataSize; + + private byte[] rootHash; + + private byte[] salt; + + private int flags; + + private int reservedInt; + + private long treeOffset; + + private byte[] reservedData; + + private byte csVersion; + + private byte[] signature; + + private ElfSignBlock(Builder builder) { + this.treeType = builder.treeType; + this.treeLength = builder.treeLength; + this.paddingBeforeTree = builder.paddingBeforeTree; + this.merkleTreeData = builder.merkleTreeData; + this.fsdType = builder.fsdType; + this.fsdLength = builder.fsdLength; + this.fsVersion = builder.fsVersion; + this.fsHashAlgorithm = builder.fsHashAlgorithm; + this.log2BlockSize = builder.log2BlockSize; + this.saltSize = builder.saltSize; + this.signSize = builder.signSize; + this.dataSize = builder.dataSize; + this.rootHash = builder.rootHash; + this.salt = builder.salt; + this.flags = builder.flags; + this.reservedInt = builder.reservedInt; + this.treeOffset = builder.treeOffset; + this.reservedData = builder.reservedData; + this.csVersion = builder.csVersion; + this.signature = builder.signature; + } + + /** + * Return the byte size of code sign block + * + * @return byte size of code sign block + */ + public int size() { + return Integer.BYTES * 2 + paddingBeforeTree.length + merkleTreeData.length + Integer.BYTES * 2 + + FSD_WITHOUT_SIGN_LENGTH + signature.length; + } + + /** + * add fs-verity info + * + * @param version fs-verity version + * @param hashAlgorithm fs-verity hash algorithm id + * @param log2BlockSize log2 of hash block size + */ + public void addFsVerityInfo(byte version, byte hashAlgorithm, byte log2BlockSize) { + this.fsVersion = version; + this.fsHashAlgorithm = hashAlgorithm; + this.log2BlockSize = log2BlockSize; + } + + /** + * add sign info + * + * @param saltSize byte size of salt + * @param flags reserved flags + * @param fileSize byte size of data being signed + * @param salt salt in byte array representation + * @param signature signature after signing the data in byte array representation + */ + public void addSignInfo(int saltSize, int flags, long fileSize, byte[] salt, byte[] signature) { + this.saltSize = (byte) saltSize; + this.flags = flags; + this.dataSize = fileSize; + if (salt != null) { + this.salt = Arrays.copyOf(salt, SALT_BUFFER_LENGTH); + } + if (signature != null) { + this.signature = signature; + } + this.signSize = this.signature.length; + this.fsdLength = FSD_WITHOUT_SIGN_LENGTH + signSize; + } + + /** + * add merkle tree info + * + * @param merkleTreeBytes merkle tree data + * @param fsvTreeOffset merkle tree offset based on file start + * @param rootHash Root hash of the merkle tree + */ + public void addMerkleTreeInfo(byte[] merkleTreeBytes, long fsvTreeOffset, byte[] rootHash) { + if (merkleTreeBytes != null) { + this.merkleTreeData = merkleTreeBytes; + } + this.treeOffset = fsvTreeOffset; + if (rootHash != null) { + this.rootHash = Arrays.copyOf(rootHash, ROOT_HASH_SIZE); + } + treeLength = paddingBeforeTree.length + merkleTreeData.length; + } + + /** + * add padding length offset while computing align position for merkle tree + * + * @param signBlockOffset sign block offset based on the start of file + * @return merkle tree raw bytes offset based on the start of file + */ + public long computeMerkleTreeOffset(long signBlockOffset) { + long residual = (signBlockOffset + Integer.BYTES * 2) % PAGE_SIZE_4K; + if (residual > 0) { + this.paddingBeforeTree = new byte[(int) (PAGE_SIZE_4K - residual)]; + } + return signBlockOffset + Integer.BYTES * 2 + paddingBeforeTree.length; + } + + public byte[] getMerkleTreeData() { + return merkleTreeData; + } + + public long getDataSize() { + return dataSize; + } + + public long getTreeOffset() { + return treeOffset; + } + + public byte[] getSignature() { + return signature; + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(treeType); + bf.putInt(paddingBeforeTree.length + merkleTreeData.length); + bf.put(paddingBeforeTree); + bf.put(merkleTreeData); + bf.putInt(fsdType); + bf.putInt(fsdLength); + bf.put(fsVersion); + bf.put(fsHashAlgorithm); + bf.put(log2BlockSize); + bf.put(saltSize); + bf.putInt(signSize); + bf.putLong(dataSize); + bf.put(rootHash); + bf.put(salt); + bf.putInt(flags); + bf.putInt(reservedInt); + bf.putLong(treeOffset); + bf.put(reservedData); + bf.put(csVersion); + bf.put(signature); + return bf.array(); + } + + /** + * Init the ElfSignBlock by a byte array + * + * @param bytes Byte array representation of a CodeSignBlockHeader object + * @param signBlockOffset start position of code sign block based on the start of the elf file + * @return a newly created CodeSignBlockHeader object + * @throws VerifyCodeSignException parse result invalid + */ + public static ElfSignBlock fromByteArray(byte[] bytes, long signBlockOffset) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + // after put, rewind is mandatory before get + bf.rewind(); + Builder builder = new Builder(); + int inTreeType = bf.getInt(); + if (MERKLE_TREE_INLINED != inTreeType) { + throw new VerifyCodeSignException("Invalid merkle tree type of ElfSignBlock"); + } + int inTreeLength = bf.getInt(); + builder.setTreeType(inTreeType).setTreeLength(inTreeLength); + int paddingLength = (int) (PAGE_SIZE_4K - (signBlockOffset + Integer.BYTES * 2) % PAGE_SIZE_4K); + byte[] zeroPadding = new byte[paddingLength]; + bf.get(zeroPadding); + byte[] treeWithoutPadding = new byte[inTreeLength - paddingLength]; + bf.get(treeWithoutPadding); + builder.setPaddingBeforeTree(zeroPadding).setMerkleTreeData(treeWithoutPadding); + int inFsdType = bf.getInt(); + if (FsVerityDescriptor.FS_VERITY_DESCRIPTOR_TYPE != inFsdType) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor type of ElfSignBlock"); + } + int inFsdLength = bf.getInt(); + if (bytes.length != Integer.BYTES * 2 + inTreeLength + Integer.BYTES * 2 + inFsdLength) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor with signature length of ElfSignBlock"); + } + builder.setFsdType(inFsdType).setFsdLength(inFsdLength); + fillFsd(builder, bf, inFsdLength); + return builder.build(); + } + + private static void fillFsd(Builder builder, ByteBuffer bf, int inFsdLength) + throws VerifyCodeSignException { + byte inFsVersion = bf.get(); + if (FsVerityDescriptor.VERSION != inFsVersion) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor version of ElfSignBlock"); + } + byte inFsHashAlgorithm = bf.get(); + byte inLog2BlockSize = bf.get(); + builder.setFsVersion(inFsVersion).setFsHashAlgorithm(inFsHashAlgorithm).setLog2BlockSize(inLog2BlockSize); + byte inSaltSize = bf.get(); + int inSignSize = bf.getInt(); + if (inFsdLength != inSignSize + FSD_WITHOUT_SIGN_LENGTH) { + throw new VerifyCodeSignException("Invalid sign size of ElfSignBlock"); + } + int inDataSize = bf.getInt(); + byte[] inRootHash = new byte[ROOT_HASH_SIZE]; + bf.get(inRootHash); + builder.setSaltSize(inSaltSize).setSignSize(inSignSize).setDataSize(inDataSize).setRootHash(inRootHash); + byte[] inSalt = new byte[SALT_BUFFER_LENGTH]; + bf.get(inSalt); + int inFlags = bf.getInt(); + bf.getInt(); + long inTreeOffset = bf.getLong(); + if (inTreeOffset % PAGE_SIZE_4K != 0) { + throw new VerifyCodeSignException("Invalid merkle tree offset of ElfSignBlock"); + } + bf.get(new byte[RESERVED_BYTE_ARRAY_LENGTH]); + byte[] inSignature = new byte[inSignSize]; + bf.get(inSignature); + builder.setSalt(inSalt).setFlags(inFlags).setTreeOffset(inTreeOffset).setSignature(inSignature); + } + + /** + * Builder of ElfSignBlock class + */ + public static class Builder { + private int treeType = MERKLE_TREE_INLINED; + + private int treeLength; + + // zero padding before merkle tree + private byte[] paddingBeforeTree = new byte[0]; + + private byte[] merkleTreeData = new byte[0]; + + private int fsdType = FsVerityDescriptor.FS_VERITY_DESCRIPTOR_TYPE; + + private int fsdLength = FSD_WITHOUT_SIGN_LENGTH; + + private byte fsVersion = FsVerityDescriptor.VERSION; + + private byte fsHashAlgorithm; + + private byte log2BlockSize; + + private byte saltSize; + + private int signSize; + + private long dataSize; + + private byte[] rootHash = new byte[ROOT_HASH_SIZE]; + + private byte[] salt = new byte[SALT_BUFFER_LENGTH]; + + private int flags; + + private int reservedInt = 0; + + private long treeOffset; + + private byte[] reservedData = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + + private byte csVersion = CODE_SIGN_VERSION; + + private byte[] signature = new byte[0]; + + public Builder setTreeType(int treeType) { + this.treeType = treeType; + return this; + } + + public Builder setTreeLength(int treeLength) { + this.treeLength = treeLength; + return this; + } + + public Builder setPaddingBeforeTree(byte[] paddingBeforeTree) { + this.paddingBeforeTree = paddingBeforeTree; + return this; + } + + public Builder setMerkleTreeData(byte[] merkleTreeData) { + this.merkleTreeData = merkleTreeData; + return this; + } + + public Builder setFsdType(int fsdType) { + this.fsdType = fsdType; + return this; + } + + public Builder setFsdLength(int fsdLength) { + this.fsdLength = fsdLength; + return this; + } + + public Builder setFsVersion(byte fsVersion) { + this.fsVersion = fsVersion; + return this; + } + + public Builder setFsHashAlgorithm(byte fsHashAlgorithm) { + this.fsHashAlgorithm = fsHashAlgorithm; + return this; + } + + public Builder setLog2BlockSize(byte log2BlockSize) { + this.log2BlockSize = log2BlockSize; + return this; + } + + public Builder setSaltSize(byte saltSize) { + this.saltSize = saltSize; + return this; + } + + public Builder setSignSize(int signSize) { + this.signSize = signSize; + return this; + } + + public Builder setDataSize(long dataSize) { + this.dataSize = dataSize; + return this; + } + + public Builder setRootHash(byte[] rootHash) { + this.rootHash = rootHash; + return this; + } + + public Builder setSalt(byte[] salt) { + this.salt = salt; + return this; + } + + public Builder setFlags(int flags) { + this.flags = flags; + return this; + } + + public Builder setReservedInt(int reservedInt) { + this.reservedInt = reservedInt; + return this; + } + + public Builder setTreeOffset(long treeOffset) { + this.treeOffset = treeOffset; + return this; + } + + public Builder setReservedData(byte[] reservedData) { + this.reservedData = reservedData; + return this; + } + + public Builder setCsVersion(byte csVersion) { + this.csVersion = csVersion; + return this; + } + + public Builder setSignature(byte[] signature) { + this.signature = signature; + return this; + } + + /** + * return a ElfSignBlock object + * + * @return a ElfSignBlock object + */ + public ElfSignBlock build() { + return new ElfSignBlock(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java new file mode 100644 index 0000000000000000000000000000000000000000..2d23c548812465c6419fea07d40299268c9121cb --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.datastructure; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Extension is an optional field in relative to SignInfo. + * It is the base class for all types of extensions, i.e. MerkleTreeExtension. + *

+ * Structure: + * u32 type: Indicates the type of extension + *

+ * u32 size: byte size of extension data + * + * @since 2023/09/08 + */ +public class Extension { + /** + * Byte size of Extension base class. + */ + public static final int EXTENSION_HEADER_SIZE = 8; + + private final int type; + + private final int size; + + public Extension(int type, int size) { + this.type = type; + this.size = size; + } + + public int size() { + return EXTENSION_HEADER_SIZE; + } + + public boolean isType(int type) { + return this.type == type; + } + + /** + * Converts Extension to a newly created byte array + * + * @return Byte array representation of Extension + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(EXTENSION_HEADER_SIZE).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(this.type); + bf.putInt(this.size); + return bf.array(); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, "Extension: type[%d], size[%d]", this.type, this.size); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/FsVerityInfoSegment.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/FsVerityInfoSegment.java new file mode 100644 index 0000000000000000000000000000000000000000..117431cf1dbb769c4f3757d3f0c955824e574cfd --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/FsVerityInfoSegment.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Fs-verity info segment contains information of fs-verity protection + * More information of fs-verity can be found here + *

+ * Structure + *

+ * 1) u32 magic: magic number + *

+ * 2) u8 version: fs-verity version + *

+ * 3) u8 hashAlgorithm: hash algorithm to use for the Merkle tree + *

+ * 4) u8 log2BlockSize: log2 of size of data and tree blocks + *

+ * 5) u8[] reserved: for reservation + * + * @since 2023/09/08 + */ +public class FsVerityInfoSegment { + /** + * fs-verity info segment size in bytes + */ + public static final int FS_VERITY_INFO_SEGMENT_SIZE = 64; + + // lower 4 bytes of the MD5 result of string "fs-verity info segment" (1E38 31AB) + private static final int MAGIC = (0x1E38 << 16) + (0x31AB); + + private static final int RESERVED_BYTE_ARRAY_LENGTH = 57; + + private int magic = MAGIC; + + private byte hashAlgorithm; + + private byte version; + + private byte log2BlockSize; + + private byte[] reserved = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + + /** + * Default constructor + */ + public FsVerityInfoSegment() { + } + + public FsVerityInfoSegment(byte version, byte hashAlgorithm, byte log2BlockSize) { + this(MAGIC, version, hashAlgorithm, log2BlockSize, new byte[RESERVED_BYTE_ARRAY_LENGTH]); + } + + /** + * Constructor of FsVerityInfoSegment + * + * @param magic magic num + * @param version version of fs-verity + * @param hashAlgorithm hash algorithm to use for the Merkle tree + * @param log2BlockSize log2 of size of data and tree blocks + * @param reserved for reservation + */ + public FsVerityInfoSegment(int magic, byte version, byte hashAlgorithm, byte log2BlockSize, byte[] reserved) { + this.magic = magic; + this.version = version; + this.hashAlgorithm = hashAlgorithm; + this.log2BlockSize = log2BlockSize; + this.reserved = reserved; + } + + public int size() { + return FS_VERITY_INFO_SEGMENT_SIZE; + } + + /** + * Converts FsVerityInfoSegment to a newly created byte array + * + * @return Byte array representation of FsVerityInfoSegment + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(FS_VERITY_INFO_SEGMENT_SIZE).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(this.magic); + bf.put(version); + bf.put(hashAlgorithm); + bf.put(log2BlockSize); + bf.put(reserved); + return bf.array(); + } + + /** + * Init the FsVerityInfoSegment by a byte array + * + * @param bytes Byte array representation of a FsVerityInfoSegment object + * @return a newly created FsVerityInfoSegment object + * @throws VerifyCodeSignException parsing result invalid + */ + public static FsVerityInfoSegment fromByteArray(byte[] bytes) throws VerifyCodeSignException { + if (bytes.length != FS_VERITY_INFO_SEGMENT_SIZE) { + throw new VerifyCodeSignException("Invalid size of FsVerityInfoSegment"); + } + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inMagic = bf.getInt(); + if (inMagic != MAGIC) { + throw new VerifyCodeSignException("Invalid magic number of FsVerityInfoSegment"); + } + byte inVersion = bf.get(); + if (inVersion != FsVerityDescriptor.VERSION) { + throw new VerifyCodeSignException("Invalid version of FsVerityInfoSegment"); + } + byte inHashAlgorithm = bf.get(); + if (inHashAlgorithm != FsVerityGenerator.getFsVerityHashAlgorithm()) { + throw new VerifyCodeSignException("Invalid hashAlgorithm of FsVerityInfoSegment"); + } + byte inLog2BlockSize = bf.get(); + if (inLog2BlockSize != FsVerityGenerator.getLog2BlockSize()) { + throw new VerifyCodeSignException("Invalid log2BlockSize of FsVerityInfoSegment"); + } + byte[] inReservedBytes = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + bf.get(inReservedBytes); + return new FsVerityInfoSegment(inMagic, inVersion, inHashAlgorithm, inLog2BlockSize, inReservedBytes); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, "FsVerityInfoSeg: magic[%d], version[%d], hashAlg[%d], log2BlockSize[%d]", + this.magic, this.version, this.hashAlgorithm, this.log2BlockSize); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java new file mode 100644 index 0000000000000000000000000000000000000000..0a25ba2532b931737894f1372ca2e4643c2bbf15 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Hap info segment + *

+ * Structure + *

+ * 1) u32 magic: magic number + *

+ * 2) SignInfo hapSignInfo: data struct of sign info, refer to SignInfo.java + * + * @since 2023/09/08 + */ +public class HapInfoSegment { + private static final int MAGIC_NUM_BYTES = 4; + + /** + * lower 4 bytes of the MD5 result of string "hap info segment" (C1B5 CC66) + */ + private static final int MAGIC_NUM = (0xC1B5 << 16) + 0xCC66; + + private int magic = MAGIC_NUM; + + private SignInfo hapSignInfo; + + /** + * Default constructor of HapInfoSegment + */ + public HapInfoSegment() { + this(MAGIC_NUM, new SignInfo(0, 0, 0, null, null)); + } + + /** + * Default constructor of HapInfoSegment + * + * @param magic magic number + * @param hapSignInfo hap sign info + */ + public HapInfoSegment(int magic, SignInfo hapSignInfo) { + this.magic = magic; + this.hapSignInfo = hapSignInfo; + } + + public void setSignInfo(SignInfo signInfo) { + this.hapSignInfo = signInfo; + } + + public SignInfo getSignInfo() { + return hapSignInfo; + } + + /** + * Returns byte size of HapInfoSegment + * + * @return byte size of HapInfoSegment + */ + public int size() { + return MAGIC_NUM_BYTES + hapSignInfo.size(); + } + + /** + * Converts HapInfoSegment to a newly created byte array + * + * @return Byte array representation of HapInfoSegment + */ + public byte[] toByteArray() { + byte[] hapSignInfoByteArray = this.hapSignInfo.toByteArray(); + // For now, only hap info segment has a merkle tree extension. So info segment + // has none extension. + ByteBuffer bf = ByteBuffer.allocate(MAGIC_NUM_BYTES + hapSignInfoByteArray.length) + .order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(magic); + bf.put(hapSignInfoByteArray); + return bf.array(); + } + + /** + * Init the HapInfoSegment by a byte array + * + * @param bytes Byte array representation of a HapInfoSegment object + * @return a newly created HapInfoSegment object + * @throws VerifyCodeSignException parsing result invalid + */ + public static HapInfoSegment fromByteArray(byte[] bytes) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inMagic = bf.getInt(); + if (inMagic != MAGIC_NUM) { + throw new VerifyCodeSignException("Invalid magic number of HapInfoSegment"); + } + byte[] hapSignInfoByteArray = new byte[bytes.length - MAGIC_NUM_BYTES]; + bf.get(hapSignInfoByteArray); + SignInfo inHapSignInfo = SignInfo.fromByteArray(hapSignInfoByteArray); + if (inHapSignInfo.getDataSize() % CodeSignBlock.PAGE_SIZE_4K != 0) { + throw new VerifyCodeSignException( + String.format(Locale.ROOT, "Invalid dataSize number of HapInfoSegment, not a multiple of 4096: %d", + inHapSignInfo.getDataSize())); + } + if (inHapSignInfo.getExtensionNum() != SignInfo.MAX_EXTENSION_NUM) { + throw new VerifyCodeSignException("Invalid extensionNum of HapInfoSegment"); + } + if (inHapSignInfo.getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED) == null) { + throw new VerifyCodeSignException("No merkle tree extension is found in HapInfoSegment"); + } + return new HapInfoSegment(inMagic, inHapSignInfo); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, "HapInfoSegment: magic[%d], signInfo[%s]", this.magic, + this.hapSignInfo.toString()); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..303d92edf13effedc56dec7605e4b2c3bd5aa908 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Locale; + +/** + * Merkle tree extension is a type of Extension to store a merkle tree's information, i.e. size and root hash, ect. + *

+ * structure + *

+ * 1) u32 type + *

+ * 2) u64 merkleTreeSize: the size of merkle tree + *

+ * 3) u64 merkleTreeOffset: offset of the merkle tree by the start of the file. + *

+ * 4) u8[64] rootHash: merkle tree root hash + * + * @since 2023/09/08 + */ +public class MerkleTreeExtension extends Extension { + /** + * Type of MerkleTreeExtension + */ + public static final int MERKLE_TREE_INLINED = 0x1; + + /** + * Byte size of MerkleTreeExtension including merkleTreeSize, offset and root hash. + */ + public static final int MERKLE_TREE_EXTENSION_DATA_SIZE = 80; + + private static final int ROOT_HASH_SIZE = 64; + + private final long merkleTreeSize; + + private long merkleTreeOffset; + + private byte[] rootHash; + + /** + * Constructor for MerkleTreeExtension + * + * @param merkleTreeSize Byte array representation of merkle tree + * @param merkleTreeOffset merkle tree offset based on file start + * @param rootHash Root hash of the merkle tree + */ + public MerkleTreeExtension(long merkleTreeSize, long merkleTreeOffset, byte[] rootHash) { + super(MERKLE_TREE_INLINED, MERKLE_TREE_EXTENSION_DATA_SIZE); + this.merkleTreeSize = merkleTreeSize; + this.merkleTreeOffset = merkleTreeOffset; + if (rootHash == null) { + this.rootHash = new byte[ROOT_HASH_SIZE]; + } else { + this.rootHash = Arrays.copyOf(rootHash, ROOT_HASH_SIZE); + } + } + + @Override + public int size() { + return Extension.EXTENSION_HEADER_SIZE + MERKLE_TREE_EXTENSION_DATA_SIZE; + } + + public long getMerkleTreeSize() { + return merkleTreeSize; + } + + public long getMerkleTreeOffset() { + return merkleTreeOffset; + } + + public void setMerkleTreeOffset(long offset) { + this.merkleTreeOffset = offset; + } + + /** + * Converts MerkleTreeExtension to a newly created byte array + * + * @return Byte array representation of MerkleTreeExtension + */ + @Override + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(Extension.EXTENSION_HEADER_SIZE + MERKLE_TREE_EXTENSION_DATA_SIZE) + .order(ByteOrder.LITTLE_ENDIAN); + bf.put(super.toByteArray()); + bf.putLong(this.merkleTreeSize); + bf.putLong(this.merkleTreeOffset); + bf.put(this.rootHash); + return bf.array(); + } + + /** + * Init the MerkleTreeExtension by a byte array + * + * @param bytes Byte array representation of a MerkleTreeExtension object + * @return a newly created MerkleTreeExtension object + * @throws VerifyCodeSignException parsing result invalid + */ + public static MerkleTreeExtension fromByteArray(byte[] bytes) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + long inMerkleTreeSize = bf.getLong(); + if (inMerkleTreeSize % CodeSignBlock.PAGE_SIZE_4K != 0) { + throw new VerifyCodeSignException("merkleTreeSize is not a multiple of 4096"); + } + long inMerkleTreeOffset = bf.getLong(); + if (inMerkleTreeOffset % CodeSignBlock.PAGE_SIZE_4K != 0) { + throw new VerifyCodeSignException("merkleTreeOffset is not a aligned to 4096"); + } + byte[] inRootHash = new byte[ROOT_HASH_SIZE]; + bf.get(inRootHash); + return new MerkleTreeExtension(inMerkleTreeSize, inMerkleTreeOffset, inRootHash); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + @Override + public String toString() { + return String.format(Locale.ROOT, "MerkleTreeExtension: merkleTreeSize[%d], merkleTreeOffset[%d]," + + " rootHash[%s]", this.merkleTreeSize, this.merkleTreeOffset, Arrays.toString(this.rootHash)); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java new file mode 100644 index 0000000000000000000000000000000000000000..c1feeb7f22fbcc8a6798eb478442aefe2b242cda --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.hap.entity.Pair; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * SoInfoSegment consists of a header part: + *

+ * u32 magic: magic number + *

+ * u32 length: byte size of SoInfoSegment + *

+ * u32 section num: the amount of file being signed + *

+ * Followed by an area containing the offset and size of each file being signed with its signed info: + *

+ * u32 file name offset: position of file name based on the start of SoInfoSegment + *

+ * u32 file name size : byte size of file name string + *

+ * u32 sign info offset : position of signed info based on the start of SoInfoSegment + *

+ * u32 sign size: byte size of signed info + *

+ * Ends with the file name and signed info content: + *

+ * file name List : file name of each signed file + *

+ * sign info List : signed info of each file + *

+ * + * @since 2023/09/08 + */ +public class NativeLibInfoSegment { + private static final int MAGIC_LENGTH_SECNUM_BYTES = 12; + + private static final int SIGNED_FILE_POS_SIZE = 16; + + // lower 4 bytes of the MD5 result of string "native lib info segment" (0ED2 E720) + private static final int MAGIC_NUM = (0x0ED2 << 16) + 0xE720; + + private static final int ALIGNMENT_FOR_SIGNINFO = 4; + + private int magic; + + private int segmentSize; + + private int sectionNum; + + private List> soInfoList = new ArrayList<>(); + + private List signedFilePosList; + + private List fileNameList; + + private List signInfoList; + + private byte[] zeroPadding; + + private int fileNameListBlockSize; + + private int signInfoListBlockSize; + + /** + * Constructor for SoInfoSegment + * + * @param builder Builder + */ + private NativeLibInfoSegment(Builder builder) { + this.magic = builder.magic; + this.segmentSize = builder.segmentSize; + this.sectionNum = builder.sectionNum; + this.signedFilePosList = builder.signedFilePosList; + this.fileNameList = builder.fileNameList; + this.signInfoList = builder.signInfoList; + this.zeroPadding = builder.zeroPadding; + } + + /** + * set soInfoList, generate fileNameList and soInfoList + * + * @param soInfoList list of file and its signed info + */ + public void setSoInfoList(List> soInfoList) { + this.soInfoList = soInfoList; + // Once map is set, update length, sectionNum as well + this.sectionNum = soInfoList.size(); + // generate file name list and sign info list + generateList(); + } + + public int getSectionNum() { + return sectionNum; + } + + public List getFileNameList() { + return fileNameList; + } + + public List getSignInfoList() { + return signInfoList; + } + + // generate List based on current so + private void generateList() { + // empty all before generate list + this.fileNameList.clear(); + this.signInfoList.clear(); + this.signedFilePosList.clear(); + int fileNameOffset = 0; + int signInfoOffset = 0; + for (Pair soInfo : soInfoList) { + String fileName = soInfo.getFirst(); + SignInfo signInfo = soInfo.getSecond(); + int fileNameSizeInBytes = fileName.getBytes(StandardCharsets.UTF_8).length; + int signInfoSizeInBytes = signInfo.toByteArray().length; + this.fileNameList.add(fileName); + this.signInfoList.add(signInfo); + this.signedFilePosList.add( + new SignedFilePos(fileNameOffset, fileNameSizeInBytes, signInfoOffset, signInfoSizeInBytes)); + // increase fileNameOffset and signInfoOffset + fileNameOffset += fileNameSizeInBytes; + signInfoOffset += signInfoSizeInBytes; + } + this.fileNameListBlockSize = fileNameOffset; + this.signInfoListBlockSize = signInfoOffset; + // alignment for signInfo + this.zeroPadding = new byte[(ALIGNMENT_FOR_SIGNINFO - this.fileNameListBlockSize % ALIGNMENT_FOR_SIGNINFO) + % ALIGNMENT_FOR_SIGNINFO]; + // after fileNameList and signInfoList is generated, update segment size + this.segmentSize = this.size(); + // adjust file name and sign info offset base on segment start + int fileNameOffsetBase = MAGIC_LENGTH_SECNUM_BYTES + signedFilePosList.size() * SIGNED_FILE_POS_SIZE; + int signInfoOffsetBase = fileNameOffsetBase + this.fileNameListBlockSize; + for (SignedFilePos pos : this.signedFilePosList) { + pos.increaseFileNameOffset(fileNameOffsetBase); + pos.increaseSignInfoOffset(signInfoOffsetBase + this.zeroPadding.length); + } + } + + /** + * Returns byte size of SoInfoSegment + * + * @return byte size of SoInfoSegment + */ + public int size() { + int blockSize = MAGIC_LENGTH_SECNUM_BYTES; + blockSize += signedFilePosList.size() * SIGNED_FILE_POS_SIZE; + blockSize += this.fileNameListBlockSize + this.zeroPadding.length + this.signInfoListBlockSize; + return blockSize; + } + + /** + * Converts SoInfoSegment to a newly created byte array + * + * @return Byte array representation of SoInfoSegment + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(this.size()).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(magic); + bf.putInt(segmentSize); + bf.putInt(sectionNum); + for (SignedFilePos offsetAndSize : this.signedFilePosList) { + bf.putInt(offsetAndSize.getFileNameOffset()); + bf.putInt(offsetAndSize.getFileNameSize()); + bf.putInt(offsetAndSize.getSignInfoOffset()); + bf.putInt(offsetAndSize.getSignInfoSize()); + } + for (String fileName : fileNameList) { + bf.put(fileName.getBytes(StandardCharsets.UTF_8)); + } + bf.put(this.zeroPadding); + for (SignInfo signInfo : signInfoList) { + bf.put(signInfo.toByteArray()); + } + return bf.array(); + } + + /** + * Init the SoInfoSegment by a byte array + * + * @param bytes Byte array representation of a SoInfoSegment object + * @return a newly created NativeLibInfoSegment object + * @throws VerifyCodeSignException parsing result invalid + */ + public static NativeLibInfoSegment fromByteArray(byte[] bytes) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inMagic = bf.getInt(); + if (inMagic != MAGIC_NUM) { + throw new VerifyCodeSignException("Invalid magic number of NativeLibInfoSegment"); + } + int inSegmentSize = bf.getInt(); + if (inSegmentSize < 0) { + throw new VerifyCodeSignException("Invalid segmentSize of NativeLibInfoSegment"); + } + int inSectionNum = bf.getInt(); + if (inSectionNum < 0) { + throw new VerifyCodeSignException("Invalid sectionNum of NativeLibInfoSegment"); + } + List inSignedFilePosList = new ArrayList<>(); + for (int i = 0; i < inSectionNum; i++) { + byte[] entry = new byte[SIGNED_FILE_POS_SIZE]; + bf.get(entry); + inSignedFilePosList.add(SignedFilePos.fromByteArray(entry)); + } + // parse file name list + List inFileNameList = new ArrayList<>(); + int fileNameListSize = 0; + for (SignedFilePos pos : inSignedFilePosList) { + byte[] fileNameBuffer = new byte[pos.getFileNameSize()]; + fileNameListSize += pos.getFileNameSize(); + bf.get(fileNameBuffer); + inFileNameList.add(new String(fileNameBuffer, StandardCharsets.UTF_8)); + } + // parse zeroPadding + byte[] inZeroPadding = new byte[(ALIGNMENT_FOR_SIGNINFO - fileNameListSize % ALIGNMENT_FOR_SIGNINFO) + % ALIGNMENT_FOR_SIGNINFO]; + bf.get(inZeroPadding); + // parse sign info list + List inSignInfoList = new ArrayList<>(); + for (SignedFilePos pos : inSignedFilePosList) { + if (pos.getSignInfoOffset() % ALIGNMENT_FOR_SIGNINFO != 0) { + throw new VerifyCodeSignException("SignInfo not aligned in NativeLibInfoSegment"); + } + byte[] signInfoBuffer = new byte[pos.getSignInfoSize()]; + bf.get(signInfoBuffer); + inSignInfoList.add(SignInfo.fromByteArray(signInfoBuffer)); + } + return new Builder().setMagic(inMagic).setSegmentSize(inSegmentSize).setSectionNum(inSectionNum) + .setSignedFilePosList(inSignedFilePosList).setFileNameList(inFileNameList) + .setSignInfoList(inSignInfoList).setZeroPadding(inZeroPadding).build(); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, + "SoInfoSegment: magic[%d], length[%d], secNum[%d], signedFileEntryList[%s], fileNameList[%s], " + + "zeroPadding[%s], signInfoList[%s]", this.magic, this.segmentSize, this.sectionNum, + Arrays.toString(this.signedFilePosList.toArray()), Arrays.toString(this.fileNameList.toArray()), + Arrays.toString(this.zeroPadding), Arrays.toString(this.signInfoList.toArray())); + } + + /** + * Builder of NativeLibInfoSegment class + */ + public static class Builder { + private int magic = MAGIC_NUM; + + private int segmentSize; + + private int sectionNum; + + private List signedFilePosList = new ArrayList<>(); + + private List fileNameList = new ArrayList<>(); + + private List signInfoList = new ArrayList<>(); + + private byte[] zeroPadding = new byte[0]; + + public Builder setMagic(int magic) { + this.magic = magic; + return this; + } + + public Builder setSegmentSize(int segmentSize) { + this.segmentSize = segmentSize; + return this; + } + + public Builder setSectionNum(int sectionNum) { + this.sectionNum = sectionNum; + return this; + } + + public Builder setSignedFilePosList(List signedFilePosList) { + this.signedFilePosList = signedFilePosList; + return this; + } + + public Builder setFileNameList(List fileNameList) { + this.fileNameList = fileNameList; + return this; + } + + public Builder setSignInfoList(List signInfoList) { + this.signInfoList = signInfoList; + return this; + } + + public Builder setZeroPadding(byte[] zeroPadding) { + this.zeroPadding = zeroPadding; + return this; + } + + public NativeLibInfoSegment build() { + return new NativeLibInfoSegment(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SegmentHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SegmentHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..975dda21553f8f8687784640839fc365852615de --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SegmentHeader.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * segment header has three field: + *

+ * u32 type: indicates the type of segment: fs-verity/so/hap info segment + *

+ * u32 segment offset: the segment position based on the start of code sign block + *

+ * u32 segment size: byte size of the segment + * + * @since 2023/09/08 + */ +public class SegmentHeader { + /** + * Byte size of SegmentHeader + */ + public static final int SEGMENT_HEADER_LENGTH = 12; + + /** + * Fs-verity segment type + */ + public static final int CSB_FSVERITY_INFO_SEG = 0x1; + + /** + * Hap info segment type + */ + public static final int CSB_HAP_META_SEG = 0x2; + + /** + * So info segment type + */ + public static final int CSB_NATIVE_LIB_INFO_SEG = 0x3; + + private final int type; + + private int segmentOffset; + + private final int segmentSize; + + /** + * Constructor for SegmentHeader + * + * @param type segment type + * @param segmentSize byte size of the segment + */ + public SegmentHeader(int type, int segmentSize) { + this(type, 0, segmentSize); + } + + /** + * Constructor for SegmentHeader + * + * @param type segment type + * @param segmentOffset segment offset based on the start of code sign block + * @param segmentSize byte size of segment + */ + public SegmentHeader(int type, int segmentOffset, int segmentSize) { + this.type = type; + this.segmentOffset = segmentOffset; + this.segmentSize = segmentSize; + } + + public int getType() { + return type; + } + + public void setSegmentOffset(int offset) { + this.segmentOffset = offset; + } + + public int getSegmentOffset() { + return segmentOffset; + } + + public int getSegmentSize() { + return segmentSize; + } + + /** + * Converts SegmentHeader to a newly created byte array + * + * @return Byte array representation of SegmentHeader + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(SEGMENT_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(type); + bf.putInt(segmentOffset); + bf.putInt(segmentSize); + return bf.array(); + } + + /** + * Init the SegmentHeader by a byte array + * + * @param bytes Byte array representation of a SegmentHeader object + * @return a newly created SegmentHeader object + * @throws VerifyCodeSignException parsing result invalid + */ + public static SegmentHeader fromByteArray(byte[] bytes) throws VerifyCodeSignException { + if (bytes.length != SEGMENT_HEADER_LENGTH) { + throw new VerifyCodeSignException("Invalid size of SegmentHeader"); + } + ByteBuffer bf = ByteBuffer.allocate(SEGMENT_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inType = bf.getInt(); + if ((inType != CSB_FSVERITY_INFO_SEG) && (inType != CSB_HAP_META_SEG) && (inType != CSB_NATIVE_LIB_INFO_SEG)) { + throw new VerifyCodeSignException("Invalid type of SegmentHeader"); + } + int inSegmentOffset = bf.getInt(); + // segment offset is always larger than the size of CodeSignBlockHeader + if (inSegmentOffset < CodeSignBlockHeader.size()) { + throw new VerifyCodeSignException("Invalid segmentOffset of SegmentHeader"); + } + int inSegmentSize = bf.getInt(); + if (inSegmentSize < 0) { + throw new VerifyCodeSignException("Invalid segmentSize of SegmentHeader"); + } + if ((inType == CSB_FSVERITY_INFO_SEG) && (inSegmentSize != FsVerityInfoSegment.FS_VERITY_INFO_SEGMENT_SIZE)) { + throw new VerifyCodeSignException("Invalid segmentSize of fs-verity SegmentHeader"); + } + return new SegmentHeader(inType, inSegmentOffset, inSegmentSize); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, "Segment Header: type=%d, seg_offset = %d, seg_size = %d", this.type, + this.segmentOffset, this.segmentSize); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignInfo.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..e2d538c4f3dc2cd6d05518caf9240c7be54b9d50 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignInfo.java @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * Sign info represents information after signing a file, including signature, merkle tree. + * Structure: + *

+ * 1) u32 saltSize: byte size of salt + *

+ * 2) u32 sigSize: byte size of signature + *

+ * 3) u32 flags: reserved flags + *

+ * 4) u64 dataSize: byte size of data being signed + *

+ * 5) u8[32] salt: salt used in signing + *

+ * 6) u32 extensionNum: number of extension + *

+ * 7) u32 extensionOffset + *

+ * 8) u8[] signature: signature of the data + *

+ * MerkleTree is represented as an extension of the sign info. + * Its structure is defined in MerkleTreeExtension.java + * + * @since 2023/09/08 + */ +public class SignInfo { + /** + * merkle tree extension is included in sign info + */ + public static final int FLAG_MERKLE_TREE_INCLUDED = 0x1; + + /** + * maximum of extension number + */ + public static final int MAX_EXTENSION_NUM = 1; + + /** + * sign info structure without signature in bytes, refer to toByteArray() method + */ + private static final int SIGN_INFO_SIZE_WITHOUT_SIGNATURE = 60; + + private static final int SALT_BUFFER_LENGTH = 32; + + private static final int SIGNATURE_ALIGNMENT = 4; + + private int saltSize; + + private int sigSize; + + private int flags; + + private long dataSize; + + private byte[] salt; + + private int extensionNum; + + private int extensionOffset; + + private byte[] signature; + + private byte[] zeroPadding; + + // temporary, use list instead + private List extensionList = new ArrayList<>(); + + /** + * Constructor for SignInfo + * + * @param saltSize byte size of salt + * @param flags reserved flags + * @param dataSize byte size of data being signed + * @param salt salt in byte array representation + * @param sig signature after signing the data in byte array representation + */ + public SignInfo(int saltSize, int flags, long dataSize, byte[] salt, byte[] sig) { + this.saltSize = saltSize; + this.flags = flags; + this.dataSize = dataSize; + if (salt == null) { + this.salt = new byte[SALT_BUFFER_LENGTH]; + } else { + this.salt = salt; + } + this.signature = sig; + this.sigSize = sig == null ? 0 : sig.length; + // align for extension after signature + this.zeroPadding = new byte[(SIGNATURE_ALIGNMENT - (this.sigSize % SIGNATURE_ALIGNMENT)) % SIGNATURE_ALIGNMENT]; + } + + /** + * Constructor by a SignInfoBuilder + * + * @param builder SignInfoBuilder + */ + private SignInfo(SignInfoBuilder builder) { + this.saltSize = builder.saltSize; + this.sigSize = builder.sigSize; + this.flags = builder.flags; + this.dataSize = builder.dataSize; + this.salt = builder.salt; + this.extensionNum = builder.extensionNum; + this.extensionOffset = builder.extensionOffset; + this.signature = builder.signature; + this.zeroPadding = builder.zeroPadding; + this.extensionList = builder.extensionList; + } + + /** + * Add one Extension into SignInfo Object + * + * @param extension Extension object + */ + public void addExtension(Extension extension) { + this.extensionOffset = this.size(); + this.extensionList.add(extension); + this.extensionNum = this.extensionList.size(); + } + + /** + * Get Extension from SignInfo based on extension type + * + * @param type extension type + * @return Extension object + */ + public Extension getExtensionByType(int type) { + for (Extension ext : this.extensionList) { + if (ext.isType(type)) { + return ext; + } + } + return null; + } + + /** + * Returns extensionNum + * + * @return extensionNum + */ + public int getExtensionNum() { + return extensionNum; + } + + public byte[] getSignature() { + return signature; + } + + public long getDataSize() { + return dataSize; + } + + /** + * Returns byte size of SignInfo object + * + * @return byte size of SignInfo object + */ + public int size() { + int blockSize = SIGN_INFO_SIZE_WITHOUT_SIGNATURE + this.signature.length + this.zeroPadding.length; + for (Extension ext : this.extensionList) { + blockSize += ext.size(); + } + return blockSize; + } + + /** + * Converts SignInfo to a newly created byte array + * + * @return Byte array representation of SignInfo + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(this.size()).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(this.saltSize); + bf.putInt(this.sigSize); + bf.putInt(this.flags); + bf.putLong(this.dataSize); + bf.put(this.salt); + bf.putInt(this.extensionNum); + bf.putInt(this.extensionOffset); + bf.put(this.signature); + bf.put(this.zeroPadding); + // put extension + for (Extension ext : this.extensionList) { + bf.put(ext.toByteArray()); + } + return bf.array(); + } + + /** + * Init the SignInfo by a byte array + * + * @param bytes Byte array representation of a SignInfo object + * @return a newly created SignInfo object + * @throws VerifyCodeSignException parsing result invalid + */ + public static SignInfo fromByteArray(byte[] bytes) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inSaltSize = bf.getInt(); + if (inSaltSize < 0) { + throw new VerifyCodeSignException("Invalid saltSize of SignInfo"); + } + int inSigSize = bf.getInt(); + if (inSigSize < 0) { + throw new VerifyCodeSignException("Invalid sigSize of SignInfo"); + } + int inFlags = bf.getInt(); + if (inFlags != 0 && inFlags != FLAG_MERKLE_TREE_INCLUDED) { + throw new VerifyCodeSignException("Invalid flags of SignInfo"); + } + long inDataSize = bf.getLong(); + if (inDataSize < 0) { + throw new VerifyCodeSignException("Invalid dataSize of SignInfo"); + } + byte[] inSalt = new byte[SALT_BUFFER_LENGTH]; + bf.get(inSalt); + int inExtensionNum = bf.getInt(); + if (inExtensionNum < 0 || inExtensionNum > MAX_EXTENSION_NUM) { + throw new VerifyCodeSignException("Invalid extensionNum of SignInfo"); + } + int inExtensionOffset = bf.getInt(); + if (inExtensionOffset < 0 || inExtensionOffset % 4 != 0) { + throw new VerifyCodeSignException("Invalid extensionOffset of SignInfo"); + } + byte[] inSignature = new byte[inSigSize]; + bf.get(inSignature); + byte[] inZeroPadding = new byte[(SIGNATURE_ALIGNMENT - (inSigSize % SIGNATURE_ALIGNMENT)) + % SIGNATURE_ALIGNMENT]; + bf.get(inZeroPadding); + // parse merkle tree extension + List inExtensionList = parseMerkleTreeExtension(bf, inExtensionNum); + return new SignInfoBuilder().setSaltSize(inSaltSize) + .setSigSize(inSigSize) + .setFlags(inFlags) + .setDataSize(inDataSize) + .setSalt(inSalt) + .setExtensionNum(inExtensionNum) + .setExtensionOffset(inExtensionOffset) + .setSignature(inSignature) + .setZeroPadding(inZeroPadding) + .setExtensionList(inExtensionList) + .build(); + } + + private static List parseMerkleTreeExtension(ByteBuffer bf, int inExtensionNum) + throws VerifyCodeSignException { + List inExtensionList = new ArrayList<>(); + if (inExtensionNum == 1) { + // parse merkle tree extension + int extensionType = bf.getInt(); + if (extensionType != MerkleTreeExtension.MERKLE_TREE_INLINED) { + throw new VerifyCodeSignException("Invalid extensionType of SignInfo"); + } + int extensionSize = bf.getInt(); + if (extensionSize != MerkleTreeExtension.MERKLE_TREE_EXTENSION_DATA_SIZE) { + throw new VerifyCodeSignException("Invalid extensionSize of SignInfo"); + } + byte[] merkleTreeExtension = new byte[MerkleTreeExtension.MERKLE_TREE_EXTENSION_DATA_SIZE]; + bf.get(merkleTreeExtension); + inExtensionList.add(MerkleTreeExtension.fromByteArray(merkleTreeExtension)); + } + return inExtensionList; + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + String str = String.format(Locale.ROOT, "SignInfo: saltSize[%d], sigSize[%d]," + + "flags[%d], dataSize[%d], salt[%s], zeroPad[%s], extNum[%d], extOffset[%d]", + this.saltSize, this.sigSize, this.flags, this.dataSize, Arrays.toString(this.salt), + Arrays.toString(this.zeroPadding), this.extensionNum, this.extensionOffset); + if (this.getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED) != null) { + str += String.format(Locale.ROOT, "SignInfo.merkleTreeExtension[%s]", + this.getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED).toString()); + } + return str; + } + + /** + * Builder of SignInfo object + */ + public static class SignInfoBuilder { + private int saltSize; + + private int sigSize; + + private int flags; + + private long dataSize; + + private byte[] salt; + + private int extensionNum; + + private int extensionOffset; + + private byte[] signature; + + private byte[] zeroPadding; + + // temporary, use list instead + private List extensionList = new ArrayList<>(); + + /** + * set saltSize + * + * @param saltSize saltSize + * @return SignInfoBuilder + */ + public SignInfoBuilder setSaltSize(int saltSize) { + this.saltSize = saltSize; + return this; + } + + /** + * set sigSize + * + * @param sigSize sigSize + * @return SignInfoBuilder + */ + public SignInfoBuilder setSigSize(int sigSize) { + this.sigSize = sigSize; + return this; + } + + /** + * set flags + * + * @param flags flags + * @return SignInfoBuilder + */ + public SignInfoBuilder setFlags(int flags) { + this.flags = flags; + return this; + } + + /** + * set dataSize + * + * @param dataSize dataSize + * @return SignInfoBuilder + */ + public SignInfoBuilder setDataSize(long dataSize) { + this.dataSize = dataSize; + return this; + } + + /** + * set salt + * + * @param salt salt + * @return SignInfoBuilder + */ + public SignInfoBuilder setSalt(byte[] salt) { + this.salt = salt; + return this; + } + + /** + * set extensionNum + * + * @param extensionNum extensionNum + * @return SignInfoBuilder + */ + public SignInfoBuilder setExtensionNum(int extensionNum) { + this.extensionNum = extensionNum; + return this; + } + + /** + * set extensionOffset + * + * @param extensionOffset extensionOffset + * @return SignInfoBuilder + */ + public SignInfoBuilder setExtensionOffset(int extensionOffset) { + this.extensionOffset = extensionOffset; + return this; + } + + /** + * set signature + * + * @param signature signature + * @return SignInfoBuilder + */ + public SignInfoBuilder setSignature(byte[] signature) { + this.signature = signature; + return this; + } + + /** + * set zeroPadding + * + * @param zeroPadding zeroPadding + * @return SignInfoBuilder + */ + public SignInfoBuilder setZeroPadding(byte[] zeroPadding) { + this.zeroPadding = zeroPadding; + return this; + } + + /** + * set extensionList + * + * @param extensionList extensionList + * @return SignInfoBuilder + */ + public SignInfoBuilder setExtensionList(List extensionList) { + this.extensionList = extensionList; + return this; + } + + /** + * return a SignInfo object + * + * @return SignInfo object + */ + public SignInfo build() { + return new SignInfo(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignedFilePos.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignedFilePos.java new file mode 100644 index 0000000000000000000000000000000000000000..6b2eb33593798e0390b476cbf13ea72e6a12ad39 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignedFilePos.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.datastructure; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Sign info of file + * + * @since 2023/09/08 + */ +public class SignedFilePos { + /** + * file name offset based on start of so info segment + */ + private int fileNameOffset; + + /** + * byte size of file + */ + private final int fileNameSize; + + /** + * sign info offset based on start of so info segment + */ + private int signInfoOffset; + + /** + * byte size of sign info + */ + private final int signInfoSize; + + /** + * Constructor for SignedFilePos + * + * @param fileNameOffset file name offset based on segment start + * @param fileNameSize byte size of file name string + * @param signInfoOffset sign info offset based on segment start + * @param signInfoSize byte size of sign info + */ + public SignedFilePos(int fileNameOffset, int fileNameSize, int signInfoOffset, int signInfoSize) { + this.fileNameOffset = fileNameOffset; + this.fileNameSize = fileNameSize; + this.signInfoOffset = signInfoOffset; + this.signInfoSize = signInfoSize; + } + + public int getFileNameOffset() { + return fileNameOffset; + } + + public int getFileNameSize() { + return fileNameSize; + } + + public int getSignInfoOffset() { + return signInfoOffset; + } + + public int getSignInfoSize() { + return signInfoSize; + } + + /** + * increase file name offset + * + * @param incOffset increase value + */ + public void increaseFileNameOffset(int incOffset) { + this.fileNameOffset += incOffset; + } + + /** + * increase sign info offset + * + * @param incOffset increase value + */ + public void increaseSignInfoOffset(int incOffset) { + this.signInfoOffset += incOffset; + } + + /** + * Constructor for SignedFilePos by byte array + * + * @param bytes Byte array representation of SignedFilePos + * @return a newly created SignedFilePos object + */ + public static SignedFilePos fromByteArray(byte[] bytes) { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + int inFileNameOffset = bf.getInt(); + int inFileNameSize = bf.getInt(); + int inSignInfoOffset = bf.getInt(); + int inSignInfoSize = bf.getInt(); + return new SignedFilePos(inFileNameOffset, inFileNameSize, inSignInfoOffset, inSignInfoSize); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, "SignedFilePos: fileNameOffset, Size[%d, %d], signInfoOffset, Size[%d, %d]", + this.fileNameOffset, this.fileNameSize, this.signInfoOffset, this.signInfoSize); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignException.java new file mode 100644 index 0000000000000000000000000000000000000000..8263bf508fa60ecf16d5b408adf3c2482000e6db --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignException.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.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/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/FsVerityDigestException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/FsVerityDigestException.java new file mode 100644 index 0000000000000000000000000000000000000000..5ac1210bce1aaf1ef0a3cf8273dca12f0afa236a --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/FsVerityDigestException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.exception; + +/** + * Fail to 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/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/VerifyCodeSignException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/VerifyCodeSignException.java new file mode 100644 index 0000000000000000000000000000000000000000..36d28623a55e49399ec3ab83ea2f78f1e0ca59dd --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/VerifyCodeSignException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.exception; + +/** + * Exception occurs when the required parameters are missed + * + * @since 2023/06/05 + */ +public class VerifyCodeSignException extends Exception { + private static final long serialVersionUID = -8922730964374794468L; + + /** + * Exception occurs when the required parameters are not entered + * + * @param message msg + */ + public VerifyCodeSignException(String message) { + super(message); + } + + /** + * Exception occurs when the required parameters are not entered + * + * @param message msg + * @param cause cause + */ + public VerifyCodeSignException(String message, Throwable cause) { + super(message, cause); + } +} + diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java new file mode 100644 index 0000000000000000000000000000000000000000..19c01730378f2ee057bb35716abd236067135ed1 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Format of FsVerity descriptor + * uint8 version + * uint8 hashAlgorithm + * uint8 log2BlockSize + * uint8 saltSize + * uint8[4] 0 + * le64 dataSize + * uint8[64] rootHash + * uint8[32] salt + * uint32 flags + * uint8[4] 0 + * uint64 treeOffset + * uint8[128] 0 + * + * @since 2023/06/05 + */ +public class FsVerityDescriptor { + /** + * fs-verity version, must be 1 + */ + public static final byte VERSION = 1; + + /** + * Indicating merkle tree offset is set in fs-verity descriptor + */ + public static final int FLAG_STORE_MERKLE_TREE_OFFSET = 0x1; + + /** + * Indicating fs-verity descriptor type + */ + public static final int FS_VERITY_DESCRIPTOR_TYPE = 0x1; + + 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 RESERVED_SIZE_AFTER_FLAGS = 4; + + private long fileSize; + + private byte hashAlgorithm; + + private byte log2BlockSize; + + private byte[] salt; + + private byte[] rawRootHash; + + private int flags; + + private long merkleTreeOffset; + + private FsVerityDescriptor(Builder builder) { + this.fileSize = builder.fileSize; + this.hashAlgorithm = builder.hashAlgorithm; + this.log2BlockSize = builder.log2BlockSize; + this.salt = builder.salt; + this.rawRootHash = builder.rawRootHash; + this.flags = builder.flags; + this.merkleTreeOffset = builder.merkleTreeOffset; + } + + /** + * Get FsVerity descriptor bytes + * + * @return bytes of descriptor + * @throws FsVerityDigestException if error + */ + public byte[] toByteArray() 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); + buffer.putInt(flags); + writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_FLAGS); + buffer.putLong(merkleTreeOffset); + 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 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); + } + + /** + * Builder of FsVerityDescriptor class + */ + public static class Builder { + private long fileSize; + + private byte hashAlgorithm; + + private byte log2BlockSize; + + private byte[] salt; + + private byte[] rawRootHash; + + private int flags; + + private long merkleTreeOffset; + + public Builder setFileSize(long fileSize) { + this.fileSize = fileSize; + return this; + } + + public Builder setHashAlgorithm(byte hashAlgorithm) { + this.hashAlgorithm = hashAlgorithm; + return this; + } + + public Builder setLog2BlockSize(byte log2BlockSize) { + this.log2BlockSize = log2BlockSize; + return this; + } + + public Builder setSalt(byte[] salt) { + this.salt = salt; + return this; + } + + public Builder setRawRootHash(byte[] rawRootHash) { + this.rawRootHash = rawRootHash; + return this; + } + + public Builder setFlags(int flags) { + this.flags = flags; + return this; + } + + public Builder setMerkleTreeOffset(long merkleTreeOffset) { + this.merkleTreeOffset = merkleTreeOffset; + return this; + } + + public FsVerityDescriptor build() { + return new FsVerityDescriptor(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDigest.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDigest.java new file mode 100644 index 0000000000000000000000000000000000000000..cff3617c9f01f933191cca4c09fd2fd5c66cafbf --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDigest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.fsverity; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +/** + * Format of FsVerity digest + * int8[8] magic "FSVerity" + * le16 digestAlgorithm sha256 = 1, sha512 = 2 + * le16 digestSize + * uint8[] digest + * + * @since 2023/06/05 + */ +public class FsVerityDigest { + 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/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..8e0ba460e3e9e8289ab7bdf44a32ab89845f892f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.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 LOG_2_OF_FSVERITY_HASH_PAGE_SIZE = 12; + + /** + * salt for hashing one page + */ + protected byte[] salt = null; + + private byte[] fsVerityDigest = null; + + private byte[] treeBytes = null; + + private byte[] rootHash = null; + + /** + * generate merkle tree of given input + * + * @param inputStream input stream for generate 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 (MerkleTreeBuilder builder = new MerkleTreeBuilder()) { + merkleTree = builder.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 generate FsVerity digest + * @param size total size of input stream + * @param fsvTreeOffset merkle tree raw bytes offset based on the start of file + * @throws FsVerityDigestException if error + */ + public void generateFsVerityDigest(InputStream inputStream, long size, long fsvTreeOffset) + 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); + } + int flags = fsvTreeOffset == 0 ? 0 : FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET; + byte[] fsVerityDescriptor = new FsVerityDescriptor.Builder().setFileSize(size) + .setHashAlgorithm(FS_VERITY_HASH_ALGORITHM.getId()).setLog2BlockSize(LOG_2_OF_FSVERITY_HASH_PAGE_SIZE) + .setSalt(salt).setRawRootHash(merkleTree.rootHash).setFlags(flags).setMerkleTreeOffset(fsvTreeOffset) + .build().toByteArray(); + 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; + rootHash = merkleTree.rootHash; + } + + /** + * 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; + } + + /** + * Get merkle tree rootHash in bytes + * + * @return bytes of merkle tree rootHash + */ + public byte[] getRootHash() { + return rootHash; + } + + public byte[] getSalt() { + return salt; + } + + /** + * Returns byte size of salt + * + * @return byte size of salt + */ + public int getSaltSize() { + return this.salt == null ? 0 : this.salt.length; + } + + /** + * Returns the id of fs-verity hash algorithm + * + * @return fs-verity hash algorithm id + */ + public static byte getFsVerityHashAlgorithm() { + return FS_VERITY_HASH_ALGORITHM.getId(); + } + + /** + * Returns the log2 of size of data and tree blocks + * + * @return log2 of size of data and tree blocks + */ + public static byte getLog2BlockSize() { + return LOG_2_OF_FSVERITY_HASH_PAGE_SIZE; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityHashAlgorithm.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityHashAlgorithm.java new file mode 100644 index 0000000000000000000000000000000000000000..7d188413c321fe89efa35b064dfc8fd8597f7d47 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityHashAlgorithm.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.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/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTree.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTree.java new file mode 100644 index 0000000000000000000000000000000000000000..eebfdd2813cb6a445240be1f3cf4315c9bfc1e00 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTree.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.fsverity; + +/** + * Merkle 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/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTreeBuilder.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTreeBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..cbc198e9aa532b9a8cf62d9433040a47c16ff4ea --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTreeBuilder.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.utils.DigestUtils; + +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/05 + */ +public class MerkleTreeBuilder implements AutoCloseable { + 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 hash data + * + * @param inputStream input stream for generating merkle tree + * @param size total size of input stream + * @param outputBuffer hash data + * @throws IOException if error + */ + private void transInputStreamToHashData(InputStream inputStream, long size, ByteBuffer outputBuffer) + 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); + int chunks = (int) getChunkCount(size, CHUNK_SIZE); + byte[][] hashes = new byte[chunks][]; + long readOffset = 0L; + Phaser tasks = new Phaser(1); + 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 byteBuffer = ByteBuffer.allocate(fullChunkSize); + byte[] buffer = new byte[CHUNK_SIZE]; + int num; + int offset = 0; + int len = CHUNK_SIZE; + while ((num = inputStream.read(buffer, 0, len)) > 0) { + byteBuffer.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 errorLHJ."); + } + byteBuffer.flip(); + int readChunkIndex = (int) getFullChunkSize(MAX_READ_SIZE, CHUNK_SIZE, i); + runHashTask(hashes, tasks, byteBuffer, readChunkIndex); + readOffset += readSize; + } + tasks.arriveAndAwaitAdvance(); + for (byte[] hash : hashes) { + outputBuffer.put(hash, 0, hash.length); + } + } + + /** + * 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; + } + + 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 generate merkle tree + * @param size total size of input stream + * @param fsVerityHashAlgorithm hash algorithm for FsVerity + * @return merkle tree + * @throws IOException if error + * @throws NoSuchAlgorithmException 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 generate 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]; + ByteBuffer hashBuffer = slice(outputBuffer, inputDataOffsetBegin, inputDataOffsetEnd); + transInputStreamToHashData(inputStream, size, hashBuffer); + dataRoundupChunkSize(hashBuffer, size, 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 chunks 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/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..2d7591a8bf603dde97e0c30472d52c907a24ad65 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.utils.CmsUtils; +import com.ohos.hapsigntool.codesigning.utils.DigestUtils; +import com.ohos.hapsigntool.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.Pair; +import com.ohos.hapsigntool.hap.sign.ContentDigestAlgorithm; +import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm; + +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.Time; +import org.bouncycastle.asn1.pkcs.Attribute; +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.SignatureException; +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, SignerConfig 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.getFirst(), + new ContentInfo(PKCSObjectIdentifiers.data, null), createBerSetFromLst(signConfig.getCertificates()), + createBerSetFromLst(signConfig.getX509CRLs()), pairDigestAndSignInfo.getSecond()); + return encodingUnsignedData(content, signedData); + } + + private Pair getSignInfo(byte[] content, SignerConfig signConfig) throws CodeSignException { + ASN1EncodableVector signInfoVector = new ASN1EncodableVector(); + ASN1EncodableVector digestVector = new ASN1EncodableVector(); + for (SignatureAlgorithm signAlgorithm : signConfig.getSignatureAlgorithms()) { + 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(SignatureAlgorithm signAlgorithm, byte[] unsignedDataDigest, + SignerConfig signConfig) throws CodeSignException { + ContentDigestAlgorithm hashAlgorithm = signAlgorithm.getContentDigestAlgorithm(); + byte[] digest = computeDigest(unsignedDataDigest, hashAlgorithm.name()); + ASN1Set authed = getPKCS9Attributes(digest); + byte[] codeAuthed = getEncoded(authed); + Pair signPair = signAlgorithm.getSignatureAlgAndParams(); + byte[] signBytes = signConfig.getSigner().getSignature(codeAuthed, signPair.getFirst(), signPair.getSecond()); + if (signBytes == null) { + throw new CodeSignException("get signature failed"); + } + if (signConfig.getCertificates().isEmpty()) { + throw new CodeSignException("No certificates configured for sign"); + } + X509Certificate cert = signConfig.getCertificates().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.getDigestAlgorithm()), authed, + SIGN_ALG_ID_FINDER.find(signPair.getFirst()), 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.getFirst()); + signature.initVerify(publicKey); + if (signPair.getSecond() != null) { + signature.setParameter(signPair.getSecond()); + } + signature.update(authed); + if (!signature.verify(signBytes)) { + throw new CodeSignException("Signature verify failed"); + } + return true; + } catch (InvalidKeyException | 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.getFirst() + + " could not be verified using the public key in the certificate", e); + } catch (InvalidAlgorithmParameterException e) { + LOGGER.error("The generated signature " + signPair.getSecond() + + " 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/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java new file mode 100644 index 0000000000000000000000000000000000000000..bba3f2063f33e4c5720ae5ff59f9c2665e8a73db --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.sign; + +import org.bouncycastle.util.Strings; + +import java.util.Locale; +import java.util.zip.ZipEntry; + +/** + * Central directory structure + * further reference to Zip Format + * + * @since 2023/09/14 + */ +public class CentralDirectory { + /** + * Byte size of all fields before "compression method" in central directory structure + */ + public static final int BYTE_SIZE_BEFORE_COMPRESSION_METHOD = 10; + + /** + * Byte size of all fields between "compression method" and "file comment length" in central directory structure + */ + public static final int BYTE_SIZE_BETWEEN_COMPRESSION_MODE_AND_FILE_SIZE = 16; + + /** + * Byte size of all fields between "file comment length" and + * "relative offset of local header" in central directory structure + */ + public static final int BYTE_SIZE_BETWEEN_FILE_COMMENT_LENGTH_AND_LOCHDR_RELATIVE_OFFSET = 8; + + private final char compressionMethod; + + private final char fileNameLength; + + private final char extraFieldLength; + + private final char fileCommentLength; + + private final int relativeOffsetOfLocalHeader; + + private final byte[] fileName; + + public CentralDirectory(Builder builder) { + this.compressionMethod = builder.compressionMethod; + this.fileNameLength = builder.fileNameLength; + this.extraFieldLength = builder.extraFieldLength; + this.fileCommentLength = builder.fileCommentLength; + this.relativeOffsetOfLocalHeader = builder.relativeOffsetOfLocalHeader; + this.fileName = builder.fileName; + } + + /** + * Return true if entry is an executable file, i.e. abc or so + * + * @return true if entry is an executable file + */ + public boolean isCodeFile() { + return this.getFileName().endsWith(".abc") || this.getFileName().endsWith(".so"); + } + + /** + * Return true if zip entry is uncompressed + * + * @return true if zip entry is uncompressed + */ + public boolean isUncompressed() { + return this.compressionMethod == ZipEntry.STORED; + } + + public String getFileName() { + return Strings.fromByteArray(this.fileName); + } + + public int getRelativeOffsetOfLocalHeader() { + return relativeOffsetOfLocalHeader; + } + + /** + * Sum byte size of three variable fields: file name, extra field, file comment + * + * @return Sum byte size of three variable fields + */ + public char getFileNameLength() { + return fileNameLength; + } + + public char getExtraFieldLength() { + return extraFieldLength; + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, + "CentralDirectory:compressionMode(%d), fileName(%s), relativeOffsetOfLocalHeader(%d), " + + "fileNameLength(%d), extraFieldLength(%d), fileCommentLength(%d)", (int) this.compressionMethod, + this.getFileName(), this.relativeOffsetOfLocalHeader, (int) this.fileNameLength, + (int) this.extraFieldLength, (int) this.fileCommentLength); + } + + public static class Builder { + private char compressionMethod; + + private char fileNameLength; + + private char extraFieldLength; + + private char fileCommentLength; + + private int relativeOffsetOfLocalHeader; + + private byte[] fileName; + + public Builder setCompressionMethod(char compressionMethod) { + this.compressionMethod = compressionMethod; + return this; + } + + public Builder setFileNameLength(char fileNameLength) { + this.fileNameLength = fileNameLength; + return this; + } + + public Builder setExtraFieldLength(char extraFieldLength) { + this.extraFieldLength = extraFieldLength; + return this; + } + + public Builder setFileCommentLength(char fileCommentLength) { + this.fileCommentLength = fileCommentLength; + return this; + } + + public Builder setRelativeOffsetOfLocalHeader(int relativeOffsetOfLocalHeader) { + this.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader; + return this; + } + + public Builder setFileName(byte[] fileName) { + this.fileName = fileName; + return this; + } + + public CentralDirectory build() { + return new CentralDirectory(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java new file mode 100644 index 0000000000000000000000000000000000000000..7804c4b29647fe7c1e50913d41183156e9a5a5a2 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.ElfSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.Extension; +import com.ohos.hapsigntool.codesigning.datastructure.FsVerityInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.MerkleTreeExtension; +import com.ohos.hapsigntool.codesigning.datastructure.SignInfo; +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator; +import com.ohos.hapsigntool.codesigning.utils.HapUtils; +import com.ohos.hapsigntool.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.Pair; +import com.ohos.hapsigntool.hap.exception.HapFormatException; +import com.ohos.hapsigntool.signer.LocalSigner; +import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput; +import com.ohos.hapsigntool.zip.ZipDataInput; +import com.ohos.hapsigntool.zip.ZipFileInfo; +import com.ohos.hapsigntool.zip.ZipUtils; + +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.io.InputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * core functions of code signing + * + * @since 2023/06/05 + */ +public class CodeSigning { + /** + * Only hap and hsp bundle supports code signing, concatenate then with '/' for verification usage + */ + public static final String SUPPORT_FILE_FORM = "hap/hsp"; + + /** + * Only elf file supports bin code signing, concatenate then with '/' for verification usage + */ + public static final String SUPPORT_BIN_FILE_FORM = "elf"; + + /** + * Defined entry name of hap file + */ + public static final String HAP_SIGNATURE_ENTRY_NAME = "Hap"; + + private static final Logger LOGGER = LogManager.getLogger(CodeSigning.class); + + private static final String NATIVE_LIB_AN_SUFFIX = ".an"; + + private static final String NATIVE_LIB_SO_SUFFIX = ".so"; + + private final List extractedNativeLibSuffixs = new ArrayList<>(); + + private final SignerConfig signConfig; + + private CodeSignBlock codeSignBlock; + + private long timestamp = 0L; + + /** + * provide code sign functions to sign a hap + * + * @param signConfig configuration of sign + */ + public CodeSigning(SignerConfig signConfig) { + this.signConfig = signConfig; + } + + /** + * Sign the given elf file, and pack all signature into output file + * + * @param input file to sign + * @param offset position of codesign block based on start of the file + * @param inForm file's format + * @return byte array of code sign block + * @throws CodeSignException code signing exception + * @throws IOException io error + * @throws FsVerityDigestException computing FsVerity digest error + */ + public byte[] getElfCodeSignBlock(File input, long offset, String inForm) + throws CodeSignException, FsVerityDigestException, IOException { + if (!SUPPORT_BIN_FILE_FORM.contains(inForm)) { + throw new CodeSignException("file's format is unsupported"); + } + ElfSignBlock signBlock = new ElfSignBlock.Builder().build(); + long fileSize = input.length(); + long fsvTreeOffset = signBlock.computeMerkleTreeOffset(offset); + // add fs-verify info + signBlock.addFsVerityInfo(FsVerityDescriptor.VERSION, FsVerityGenerator.getFsVerityHashAlgorithm(), + FsVerityGenerator.getLog2BlockSize()); + try (FileInputStream inputStream = new FileInputStream(input)) { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset); + byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest(); + byte[] signature = generateSignature(fsVerityDigest); + // add sign info + signBlock.addSignInfo(fsVerityGenerator.getSaltSize(), FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET, + fileSize, fsVerityGenerator.getSalt(), signature); + // add merkle tree info + signBlock.addMerkleTreeInfo(fsVerityGenerator.getTreeBytes(), fsvTreeOffset, + fsVerityGenerator.getRootHash()); + LOGGER.info("Sign successfully."); + return signBlock.toByteArray(); + } + } + + /** + * Sign the given hap file, and pack all signature into output file + * + * @param input file to sign + * @param offset position of codesign block based on start of the file + * @param inForm file's format + * @return byte array of code sign block + * @throws CodeSignException code signing exception + * @throws IOException io error + * @throws HapFormatException hap format invalid + * @throws FsVerityDigestException computing FsVerity digest error + */ + public byte[] getCodeSignBlock(File input, long offset, String inForm) + throws CodeSignException, IOException, HapFormatException, FsVerityDigestException { + LOGGER.info("Start to sign code."); + if (SUPPORT_BIN_FILE_FORM.contains(inForm)) { + return getElfCodeSignBlock(input, offset, inForm); + } + if (!SUPPORT_FILE_FORM.contains(inForm)) { + throw new CodeSignException("file's format is unsupported"); + } + long dataSize = computeDataSize(input); + timestamp = System.currentTimeMillis(); + // generate CodeSignBlock + this.codeSignBlock = new CodeSignBlock(); + // compute merkle tree offset, replace with computeMerkleTreeOffset if fs-verity descriptor supports + long fsvTreeOffset = this.codeSignBlock.computeMerkleTreeOffset(offset); + // update fs-verity segment + FsVerityInfoSegment fsVerityInfoSegment = new FsVerityInfoSegment(FsVerityDescriptor.VERSION, + FsVerityGenerator.getFsVerityHashAlgorithm(), FsVerityGenerator.getLog2BlockSize()); + this.codeSignBlock.setFsVerityInfoSegment(fsVerityInfoSegment); + + LOGGER.debug("Sign hap."); + FileInputStream inputStream = new FileInputStream(input); + Pair hapSignInfoAndMerkleTreeBytesPair = signFile(inputStream, dataSize, true, fsvTreeOffset); + // update hap segment in CodeSignBlock + this.codeSignBlock.getHapInfoSegment().setSignInfo(hapSignInfoAndMerkleTreeBytesPair.getFirst()); + // Insert merkle tree bytes into code sign block + this.codeSignBlock.addOneMerkleTree(HAP_SIGNATURE_ENTRY_NAME, hapSignInfoAndMerkleTreeBytesPair.getSecond()); + + // update native lib info segment in CodeSignBlock + signNativeLibs(input); + + // last update codeSignBlock before generating its byte array representation + updateCodeSignBlock(this.codeSignBlock); + + // complete code sign block byte array here + byte[] generated = this.codeSignBlock.generateCodeSignBlockByte(fsvTreeOffset); + LOGGER.info("Sign successfully."); + return generated; + } + + private long computeDataSize(File file) throws IOException, HapFormatException { + // parse central directory + RandomAccessFile outputHap = new RandomAccessFile(file, "rw"); + ZipDataInput outputHapIn = new RandomAccessFileZipDataInput(outputHap); + ZipFileInfo zipInfo = ZipUtils.findZipInfo(outputHapIn); + long centralDirectoryOffset = zipInfo.getCentralDirectoryOffset(); + int centralDirectorySize = zipInfo.getCentralDirectorySize(); + int centralDirectoryEntryCount = zipInfo.getCentralDirectoryEntryCount(); + // centralDirectoryOffset is where all data ends, including abc/so/an and resources + FileInputStream input = new FileInputStream(file); + input.skip(centralDirectoryOffset); + byte[] centralDirectoryBuffer = new byte[centralDirectorySize]; + input.read(centralDirectoryBuffer); + List cdList = parseCentralDirectory(centralDirectoryBuffer, centralDirectoryEntryCount); + long dataSize = 0L; + for (CentralDirectory entry : cdList) { + if (!(entry.isCodeFile() && entry.isUncompressed())) { + // if the first file is not uncompressed abc or so, set dataSize to zero + if (entry.getRelativeOffsetOfLocalHeader() == 0) { + dataSize = 0; + break; + } + // the first entry which is not abc/so/an is found, return its data offset + dataSize = entry.getRelativeOffsetOfLocalHeader() + JarFile.LOCHDR + entry.getFileNameLength() + + entry.getExtraFieldLength(); + break; + } + } + if ((dataSize % CodeSignBlock.PAGE_SIZE_4K) != 0) { + throw new HapFormatException( + String.format(Locale.ROOT, "Invalid dataSize(%d), not a multiple of 4096", dataSize)); + } + return dataSize; + } + + private void signNativeLibs(File input) throws IOException, FsVerityDigestException, CodeSignException { + // 'an' libs are always signed + extractedNativeLibSuffixs.add(NATIVE_LIB_AN_SUFFIX); + if (HapUtils.checkCompressNativeLibs(input)) { + LOGGER.info("compressNativeLibs equals true, sign so libs as well."); + // sign so libs only if compressNativeLibs equals true + extractedNativeLibSuffixs.add(NATIVE_LIB_SO_SUFFIX); + } + + // sign native files + JarFile inputJar = new JarFile(input, false); + List entryNames = getNativeEntriesFromHap(inputJar); + if (entryNames.isEmpty()) { + LOGGER.info("No native libs."); + return; + } + List> nativeLibInfoList = signFilesFromJar(entryNames, inputJar); + // update SoInfoSegment in CodeSignBlock + this.codeSignBlock.getSoInfoSegment().setSoInfoList(nativeLibInfoList); + } + + /** + * 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 : extractedNativeLibSuffixs) { + 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 + * @return sign info and merkle tree of each file + * @throws IOException io error + * @throws FsVerityDigestException computing FsVerity digest error + * @throws CodeSignException sign error + */ + private List> signFilesFromJar(List entryNames, JarFile hap) + throws IOException, FsVerityDigestException, CodeSignException { + List> nativeLibInfoList = new ArrayList<>(); + 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(); + // We don't store merkle tree in code signing of native libs + // Therefore, the second value of pair returned is ignored + Pair pairSignInfoAndMerkleTreeBytes = signFile(inputStream, fileSize, false, 0); + nativeLibInfoList.add(Pair.create(name, pairSignInfoAndMerkleTreeBytes.getFirst())); + } + } + return nativeLibInfoList; + } + + /** + * Sign a file from input stream + * + * @param inputStream input stream of a file + * @param fileSize size of the file + * @param storeTree whether to store merkle tree in signed info + * @param fsvTreeOffset merkle tree raw bytes offset based on the start of file + * @return pair of signature and tree + * @throws FsVerityDigestException computing FsVerity Digest error + * @throws CodeSignException signing error + */ + public Pair signFile(InputStream inputStream, long fileSize, boolean storeTree, + long fsvTreeOffset) throws FsVerityDigestException, CodeSignException { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset); + byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest(); + byte[] signature = generateSignature(fsVerityDigest); + int flags = 0; + if (storeTree) { + flags = SignInfo.FLAG_MERKLE_TREE_INCLUDED; + } + SignInfo signInfo = new SignInfo(fsVerityGenerator.getSaltSize(), flags, fileSize, fsVerityGenerator.getSalt(), + signature); + // if store merkle tree in sign info + if (storeTree) { + int merkleTreeSize = fsVerityGenerator.getTreeBytes() == null ? 0 : fsVerityGenerator.getTreeBytes().length; + Extension merkleTreeExtension = new MerkleTreeExtension(merkleTreeSize, fsvTreeOffset, + fsVerityGenerator.getRootHash()); + signInfo.addExtension(merkleTreeExtension); + } + return Pair.create(signInfo, fsVerityGenerator.getTreeBytes()); + } + + private byte[] generateSignature(byte[] signedData) throws CodeSignException { + // signConfig is created by SignerFactory + if (!(signConfig.getSigner() instanceof LocalSigner)) { + if (signConfig.getCertificates().isEmpty()) { + throw new CodeSignException("No certificates configured for sign"); + } + } + return SignedDataGenerator.BC.generateSignedData(signedData, signConfig); + } + + /** + * At here, segment header, fsverity info/hap/so info segment, merkle tree + * segment should all be generated. + * code sign block size, segment number, offset is not updated. + * Try to update whatever could be updated here. + * + * @param codeSignBlock CodeSignBlock + */ + private void updateCodeSignBlock(CodeSignBlock codeSignBlock) { + // construct segment header list + codeSignBlock.setSegmentHeaders(); + // Compute and set segment number + codeSignBlock.setSegmentNum(); + // update code sign block header flag + codeSignBlock.setCodeSignBlockFlag(); + // compute segment offset + codeSignBlock.computeSegmentOffset(); + } + + private List parseCentralDirectory(byte[] buffer, int count) { + List cdList = new ArrayList<>(); + ByteBuffer cdBuffer = ByteBuffer.allocate(buffer.length).order(ByteOrder.LITTLE_ENDIAN); + cdBuffer.put(buffer); + cdBuffer.rewind(); + for (int i = 0; i < count; i++) { + byte[] bytesBeforeCompressionMethod = new byte[CentralDirectory.BYTE_SIZE_BEFORE_COMPRESSION_METHOD]; + cdBuffer.get(bytesBeforeCompressionMethod); + char compressionMode = cdBuffer.getChar(); + CentralDirectory.Builder builder = new CentralDirectory.Builder().setCompressionMethod(compressionMode); + byte[] bytesBetweenCmprMethodAndFileNameLength + = new byte[CentralDirectory.BYTE_SIZE_BETWEEN_COMPRESSION_MODE_AND_FILE_SIZE]; + cdBuffer.get(bytesBetweenCmprMethodAndFileNameLength); + char fileNameLength = cdBuffer.getChar(); + char extraFieldLength = cdBuffer.getChar(); + char fileCommentLength = cdBuffer.getChar(); + byte[] attributes + = new byte[CentralDirectory.BYTE_SIZE_BETWEEN_FILE_COMMENT_LENGTH_AND_LOCHDR_RELATIVE_OFFSET]; + cdBuffer.get(attributes); + int locHdrOffset = cdBuffer.getInt(); + builder.setFileNameLength(fileNameLength).setExtraFieldLength(extraFieldLength) + .setFileCommentLength(fileCommentLength).setRelativeOffsetOfLocalHeader(locHdrOffset); + byte[] fileNameBuffer = new byte[fileNameLength]; + cdBuffer.get(fileNameBuffer); + if (extraFieldLength != 0) { + cdBuffer.get(new byte[extraFieldLength]); + } + if (fileCommentLength != 0) { + cdBuffer.get(new byte[fileCommentLength]); + } + CentralDirectory cd = builder.setFileName(fileNameBuffer).build(); + cdList.add(cd); + } + + return cdList; + } + + private void printErrorLog(Exception exception) { + if (exception != null) { + LOGGER.error("Code signing error: {}", exception.getMessage(), exception); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..03d1b2bb4b52d5ccb24ef31c2369e5aa37cd0368 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.hap.config.SignerConfig; + +/** + * Signed data generator interface + * + * @since 2023/06/05 + */ +@FunctionalInterface +public interface SignedDataGenerator { + /** + * Create a BcSignedDataGenerator instance + */ + SignedDataGenerator BC = new BcSignedDataGenerator(); + + /** + * Generate signature data with specific content and sign configuration. + * + * @param content unsigned file digest content. + * @param signerConfig sign configurations. + * @return signed data. + * @throws CodeSignException if error. + */ + byte[] generateSignedData(byte[] content, SignerConfig signerConfig) throws CodeSignException; +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java new file mode 100644 index 0000000000000000000000000000000000000000..1e18fa6b4f5ca795aa9fa2a7e8434d91f4376ec7 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlockHeader; +import com.ohos.hapsigntool.codesigning.datastructure.ElfSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.Extension; +import com.ohos.hapsigntool.codesigning.datastructure.FsVerityInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.HapInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.MerkleTreeExtension; +import com.ohos.hapsigntool.codesigning.datastructure.NativeLibInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.SegmentHeader; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator; +import com.ohos.hapsigntool.codesigning.utils.CmsUtils; +import com.ohos.hapsigntool.hap.entity.Pair; + +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.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Verify code signature given a file with code sign block + * + * @since 2023/09/08 + */ +public class VerifyCodeSignature { + private static final Logger LOGGER = LogManager.getLogger(VerifyCodeSignature.class); + + 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); + } + + /** + * Verify a signed elf's signature + * + * @param file signed elf file + * @param offset start position of code sign block based on the start of the elf file + * @param length byte size of code sign block + * @param fileFormat elf or hqf or hsp, etc. + * @return true if signature verify succeed and false otherwise + * @throws IOException If an input or output exception occurred + * @throws VerifyCodeSignException parsing result invalid + * @throws FsVerityDigestException if fs-verity digest generation failed + * @throws CMSException if signature verify failed + */ + public static boolean verifyElf(File file, long offset, long length, String fileFormat) + throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException { + if (!CodeSigning.SUPPORT_BIN_FILE_FORM.contains(fileFormat)) { + LOGGER.info("Not elf file, skip code signing verify"); + return true; + } + // 1) parse sign block to ElfCodeSignBlock object + ElfSignBlock elfSignBlock; + try (FileInputStream signedElf = new FileInputStream(file)) { + byte[] codeSignBlockBytes = new byte[(int) length]; + signedElf.skip(offset); + signedElf.read(codeSignBlockBytes); + elfSignBlock = ElfSignBlock.fromByteArray(codeSignBlockBytes, offset); + } + // 2) verify file data + try (FileInputStream signedElf = new FileInputStream(file)) { + verifySingleFile(signedElf, elfSignBlock.getDataSize(), elfSignBlock.getSignature(), + elfSignBlock.getTreeOffset(), elfSignBlock.getMerkleTreeData()); + } + return true; + } + + /** + * Verify a signed hap's signature + * + * @param file signed hap file + * @param offset start position of code sign block based on the start of the hap file + * @param length byte size of code sign block + * @param fileFormat hap or hqf or hsp, etc. + * @return true if signature verify succeed and false otherwise + * @throws IOException If an input or output exception occurred + * @throws VerifyCodeSignException parsing result invalid + * @throws FsVerityDigestException if fs-verity digest generation failed + * @throws CMSException if signature verify failed + */ + public static boolean verifyHap(File file, long offset, long length, String fileFormat) + throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException { + if (!CodeSigning.SUPPORT_FILE_FORM.contains(fileFormat)) { + LOGGER.info("Not hap or hsp file, skip code signing verify"); + return true; + } + CodeSignBlock csb = generateCodeSignBlock(file, offset, length); + // 2) verify hap + try (FileInputStream hap = new FileInputStream(file)) { + long dataSize = csb.getHapInfoSegment().getSignInfo().getDataSize(); + byte[] signature = csb.getHapInfoSegment().getSignInfo().getSignature(); + Extension extension = csb.getHapInfoSegment() + .getSignInfo() + .getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); + MerkleTreeExtension mte = new MerkleTreeExtension(0, 0, null); + if (extension instanceof MerkleTreeExtension) { + mte = (MerkleTreeExtension) extension; + } + // temporary: merkle tree offset set to zero, change to merkleTreeOffset + verifySingleFile(hap, dataSize, signature, mte.getMerkleTreeOffset(), + csb.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME)); + } + // 3) verify native libs + try (JarFile inputJar = new JarFile(file, false)) { + for (int i = 0; i < csb.getSoInfoSegment().getSectionNum(); i++) { + String entryName = csb.getSoInfoSegment().getFileNameList().get(i); + byte[] entrySig = csb.getSoInfoSegment().getSignInfoList().get(i).getSignature(); + JarEntry entry = inputJar.getJarEntry(entryName); + if (entry.getSize() != csb.getSoInfoSegment().getSignInfoList().get(i).getDataSize()) { + throw new VerifyCodeSignException( + String.format(Locale.ROOT, "Invalid dataSize of native lib %s", entryName)); + } + InputStream entryInputStream = inputJar.getInputStream(entry); + // temporary merkleTreeOffset 0 + verifySingleFile(entryInputStream, entry.getSize(), entrySig, 0, null); + } + } + return true; + } + + private static CodeSignBlock generateCodeSignBlock(File file, long offset, long length) + throws IOException, VerifyCodeSignException { + CodeSignBlock csb = new CodeSignBlock(); + // 1) parse sign block to CodeSignBlock object + try (FileInputStream signedHap = new FileInputStream(file)) { + int fileReadOffset = 0; + // 0) skip data part, but fileReadOffset remains at start(0) + signedHap.skip(offset); + // 1) parse codeSignBlockHeader + byte[] codeSignBlockHeaderByteArray = new byte[CodeSignBlockHeader.size()]; + fileReadOffset += signedHap.read(codeSignBlockHeaderByteArray); + csb.setCodeSignBlockHeader(CodeSignBlockHeader.fromByteArray(codeSignBlockHeaderByteArray)); + if (csb.getCodeSignBlockHeader().getBlockSize() != length) { + throw new VerifyCodeSignException("Invalid code Sign block size of setCodeSignBlockHeader"); + } + // 2) parse segment headers + for (int i = 0; i < csb.getCodeSignBlockHeader().getSegmentNum(); i++) { + byte[] segmentHeaderByteArray = new byte[SegmentHeader.SEGMENT_HEADER_LENGTH]; + fileReadOffset += signedHap.read(segmentHeaderByteArray); + csb.addToSegmentList(SegmentHeader.fromByteArray(segmentHeaderByteArray)); + } + // compute merkle tree offset by alignment, based on file start + long computedTreeOffset = getAlignmentAddr(CodeSignBlock.PAGE_SIZE_4K, fileReadOffset + offset); + // skip zero padding before merkle tree, adds zero padding length to fileReadOffset + fileReadOffset += signedHap.skip(computedTreeOffset - offset - fileReadOffset); + parseMerkleTree(csb, fileReadOffset, signedHap, computedTreeOffset); + } + return csb; + } + + private static void parseMerkleTree(CodeSignBlock csb, int readOffset, FileInputStream signedHap, + long computedTreeOffset) throws VerifyCodeSignException, IOException { + // check segment offset and segment size + byte[] merkleTreeBytes = new byte[0]; + int fileReadOffset = readOffset; + for (SegmentHeader segmentHeader : csb.getSegmentHeaderList()) { + if (fileReadOffset > segmentHeader.getSegmentOffset()) { + throw new VerifyCodeSignException("Invaild offset of merkle tree and segment header"); + } + // get merkle tree bytes + if (fileReadOffset < segmentHeader.getSegmentOffset()) { + merkleTreeBytes = new byte[segmentHeader.getSegmentOffset() - fileReadOffset]; + fileReadOffset += signedHap.read(merkleTreeBytes); + } + byte[] sh = new byte[segmentHeader.getSegmentSize()]; + fileReadOffset += signedHap.read(sh); + if (segmentHeader.getType() == SegmentHeader.CSB_FSVERITY_INFO_SEG) { + // 3) parse fs-verity info segment + csb.setFsVerityInfoSegment(FsVerityInfoSegment.fromByteArray(sh)); + } else if (segmentHeader.getType() == SegmentHeader.CSB_HAP_META_SEG) { + // 4) parse hap info segment + csb.setHapInfoSegment(HapInfoSegment.fromByteArray(sh)); + } else if (segmentHeader.getType() == SegmentHeader.CSB_NATIVE_LIB_INFO_SEG) { + // 5) parse so info segment + csb.setSoInfoSegment(NativeLibInfoSegment.fromByteArray(sh)); + } + } + if (fileReadOffset != csb.getCodeSignBlockHeader().getBlockSize()) { + throw new VerifyCodeSignException("Invalid blockSize of getCodeSignBlockHeader"); + } + // parse merkle tree + Extension extension = csb.getHapInfoSegment() + .getSignInfo() + .getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); + if (extension == null) { + throw new VerifyCodeSignException("Missing merkleTreeExtension in verifycation"); + } + if (extension instanceof MerkleTreeExtension) { + MerkleTreeExtension mte = (MerkleTreeExtension) extension; + if (computedTreeOffset != mte.getMerkleTreeOffset()) { + throw new VerifyCodeSignException("Invalid merkle tree offset"); + } + if (merkleTreeBytes.length != mte.getMerkleTreeSize()) { + throw new VerifyCodeSignException("Invalid merkle tree size"); + } + csb.addOneMerkleTree(CodeSigning.HAP_SIGNATURE_ENTRY_NAME, merkleTreeBytes); + } + } + + private static long getAlignmentAddr(long alignment, long input) { + long residual = input % alignment; + if (residual == 0) { + return input; + } else { + return input + (alignment - residual); + } + } + + /** + * Verifies the signature of a given file with its signature in pkcs#7 format + * + * @param input file being verified in InputStream representation + * @param length size of signed data in the file + * @param signature byte array of signature in pkcs#7 format + * @param merkleTreeOffset merkle tree offset based on file start + * @param inMerkleTreeBytes merkle tree raw bytes + * @throws FsVerityDigestException if fs-verity digest generation failed + * @throws CMSException if signature verify failed + * @throws VerifyCodeSignException parsing code sign block error + */ + public static void verifySingleFile(InputStream input, long length, byte[] signature, long merkleTreeOffset, + byte[] inMerkleTreeBytes) throws FsVerityDigestException, CMSException, VerifyCodeSignException { + Pair pairResult = generateFsVerityDigest(input, length, merkleTreeOffset); + byte[] generatedMerkleTreeBytes = pairResult.getSecond(); + if (generatedMerkleTreeBytes == null) { + generatedMerkleTreeBytes = new byte[0]; + } + // For native libs, inMerkleTreeBytes is null, skip check here + if ((inMerkleTreeBytes != null) && !Arrays.equals(inMerkleTreeBytes, generatedMerkleTreeBytes)) { + throw new VerifyCodeSignException("verify merkle tree bytes failed"); + } + CmsUtils.verifySignDataWithUnsignedDataDigest(pairResult.getFirst(), signature); + } + + private static Pair generateFsVerityDigest(InputStream inputStream, long size, + long merkleTreeOffset) throws FsVerityDigestException { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, size, merkleTreeOffset); + return Pair.create(fsVerityGenerator.getFsVerityDigest(), fsVerityGenerator.getTreeBytes()); + } + + /** + * Get entry name of all native files in hap + * + * @param hap the given hap + * @return list of entry name + */ + private static 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 static boolean isNativeFile(String entryName) { + for (String suffix : EXTRACTED_NATIVE_LIB_SUFFIXS) { + if (entryName.endsWith(suffix)) { + return true; + } + } + return false; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..df4b11a7d18f1de22400abd3c26d36f17946ec53 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.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 isCollectionValid(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); + isCollectionValid(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/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..aafbaf577149d08d65b1f4052d54ed1e5836bd0b --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.utils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Digest util class + * + * @since 2023/06/05 + */ +public class DigestUtils { + /** + * 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/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..d3c9ab4f57926de83038c014a43404c786791a0c --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.utils; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +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 { + 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/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..816ea38c1138737404e0bdadab2e196c5b8d3cfd --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023-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.hapsigntool.codesigning.utils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * InputStream util class + * + * @since 2023/08/10 + */ +public class InputStreamUtils { + 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/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java index ba283115a5b10dda774221383ee316b0730d446b..cd10e9009f867d986a822e18f2bc4324ea348d3e 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Huawei Device Co., Ltd. + * Copyright (c) 2022-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 @@ -15,6 +15,9 @@ package com.ohos.hapsigntool.hap.entity; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + /** * define class of hap signature sub-block head */ @@ -55,4 +58,22 @@ public class HwBlockHead { (byte) (offset & 0xff) }; } + + /** + * get serialization of HwBlockHead + * + * @param type type of signature block + * @param tag tags of signature block + * @param length the length of block data + * @param offset Byte offset of the data block relative to the start position of the signature block + * @return Byte array after serialization of HwBlockHead + */ + public static byte[] getBlockHeadLittleEndian(char type, char tag, short length, int offset) { + ByteBuffer bf = ByteBuffer.allocate(HwBlockHead.BLOCK_LEN).order(ByteOrder.LITTLE_ENDIAN); + bf.put((byte) (type)); + bf.put((byte) (tag)); + bf.putShort(length); + bf.putInt(offset); + return bf.array(); + } } \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java index 6584253be06e08e60d4453b73e2680644787b073..cc8c17f7568a856f12a3cd66eb58ccaa5f35e2e1 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -18,6 +18,8 @@ package com.ohos.hapsigntool.hap.entity; import com.ohos.hapsigntool.utils.ByteArrayUtils; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; /** * define class of hap signature block head @@ -32,7 +34,7 @@ public class HwSignHead { private static final char[] MAGIC = "hw signed app ".toCharArray(); // 16Bytes-Magic private static final char[] VERSION = "1000".toCharArray(); // 4-Bytes, version is 1.0.0.0 - private static final int NUM_OF_BLOCK = 2; // number of sub-block + public static final int NUM_OF_BLOCK = 2; // number of sub-block private static final int RESERVE_LENGTH = 4; private char[] reserve = new char[RESERVE_LENGTH]; @@ -42,8 +44,7 @@ public class HwSignHead { * @param subBlockSize the total size of all sub-blocks * @return Byte array after serialization of HwSignHead */ - public byte[] getSignHead(int subBlockSize) { - int size = subBlockSize; // total size of sub-block + public byte[] getSignHead(int subBlockSize, int subBlockNum) { byte[] signHead = new byte[SIGN_HEAD_LEN]; int start = 0; try { @@ -55,11 +56,11 @@ public class HwSignHead { if (start < 0) { throw new IOException(); } - start = ByteArrayUtils.insertIntToByteArray(signHead, start, size); + start = ByteArrayUtils.insertIntToByteArray(signHead, start, subBlockSize); if (start < 0) { throw new IOException(); } - start = ByteArrayUtils.insertIntToByteArray(signHead, start, NUM_OF_BLOCK); + start = ByteArrayUtils.insertIntToByteArray(signHead, start, subBlockNum); if (start < 0) { throw new IOException(); } @@ -72,4 +73,26 @@ public class HwSignHead { } return signHead; } + + /** + * get serialization of HwSignHead + * + * @param subBlockSize the total size of all sub-blocks + * @return Byte array after serialization of HwSignHead + */ + public byte[] getSignHeadLittleEndian(int subBlockSize, int subBlockNum) { + ByteBuffer bf = ByteBuffer.allocate(SIGN_HEAD_LEN).order(ByteOrder.LITTLE_ENDIAN); + for (char c : MAGIC) { + bf.put((byte) c); + } + for (char c : VERSION) { + bf.put((byte) c); + } + bf.putInt(subBlockSize); + bf.putInt(subBlockNum); + for (char c : reserve) { + bf.put((byte) c); + } + return bf.array(); + } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignBlockData.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignBlockData.java new file mode 100644 index 0000000000000000000000000000000000000000..0fd402110241deb07424f2f5f1dabdb9e344a8fd --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignBlockData.java @@ -0,0 +1,93 @@ +/* + * 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.hapsigntool.hap.entity; + +import com.ohos.hapsigntool.utils.FileUtils; + +public class SignBlockData { + private char type; + private byte[] blockHead; + private byte[] signData; + private String signFile; + private long len; + private boolean isByte; + + public char getType() { + return type; + } + + public void setType(char type) { + this.type = type; + } + + public byte[] getBlockHead() { + return blockHead; + } + + public void setBlockHead(byte[] blockHead) { + this.blockHead = blockHead; + } + + public byte[] getSignData() { + return signData; + } + + public void setSignData(byte[] signData) { + this.signData = signData; + } + + public String getSignFile() { + return signFile; + } + + public void setSignFile(String signFile) { + this.signFile = signFile; + } + + public long getLen() { + return len; + } + + public void setLen(long len) { + this.len = len; + } + + public boolean isByte() { + return isByte; + } + + public void setByte(boolean aByte) { + isByte = aByte; + } + + public SignBlockData() { + + } + + public SignBlockData(byte[] signData, char type) { + this.signData = signData; + this.type = type; + this.len = signData == null ? 0 : signData.length; + this.isByte = true; + } + + public SignBlockData(String signFile, char type) { + this.signFile = signFile; + this.type = type; + this.len = FileUtils.getFileLen(signFile); + this.isByte = false; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java index 731abb15719b00e6f84d2dc039efbc807455c522..f7e460c384ab5e2e374afe80a4174903301b43c6 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -89,7 +89,7 @@ public class LocalJKSSignProvider extends SignProvider { String[] paramFileds = { ParamConstants.PARAM_LOCAL_JKS_KEYSTORE, ParamConstants.PARAM_LOCAL_JKS_KEYSTORE_CODE, - ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE + ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE, }; Set paramSet = ParamProcessUtil.initParamField(paramFileds); @@ -103,7 +103,7 @@ public class LocalJKSSignProvider extends SignProvider { } } } - + checkCodeSign(); checkPublicKeyPath(); } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java index e5da2793d25edba4ad9865b44d7f2785c1a6523c..227ed9fe07faf9c2f0b14ea1dfbb43854e67e728 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -20,9 +20,13 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; import com.ohos.hapsigntool.error.CustomException; import com.ohos.hapsigntool.hap.config.SignerConfig; import com.ohos.hapsigntool.hap.entity.SigningBlock; +import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.exception.InvalidParamsException; import com.ohos.hapsigntool.hap.exception.MissingParamsException; import com.ohos.hapsigntool.hap.exception.ProfileException; @@ -30,6 +34,7 @@ import com.ohos.hapsigntool.hap.exception.SignatureException; import com.ohos.hapsigntool.hap.exception.VerifyCertificateChainException; import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.sign.SignBin; +import com.ohos.hapsigntool.hap.sign.SignElf; import com.ohos.hapsigntool.hap.sign.SignHap; import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm; import com.ohos.hapsigntool.hap.verify.VerifyUtils; @@ -60,6 +65,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; @@ -73,6 +79,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.TimeZone; import java.util.Optional; @@ -261,6 +268,39 @@ public abstract class SignProvider { return true; } + /** + * sign elf file + * + * @param options parameters used to sign elf file + * @return true, if sign successfully. + */ + public boolean signElf(Options options) { + Security.addProvider(new BouncyCastleProvider()); + List publicCert = null; + SignerConfig signerConfig; + try { + publicCert = getX509Certificates(options); + + // Get x509 CRL + Optional crl = getCrl(); + + // Create signer configs, which contains public cert and crl info. + signerConfig = createSignerConfigs(publicCert, crl, options); + } catch (InvalidKeyException | InvalidParamsException | MissingParamsException | ProfileException e) { + LOGGER.error("create signer configs failed.", e); + printErrorLogWithoutStack(e); + return false; + } + + /* 6. make signed file into output file. */ + if (!SignElf.sign(signerConfig, signParams)) { + LOGGER.error("hap-sign-tool: error: Sign elf internal failed."); + return false; + } + LOGGER.info("Sign success"); + return true; + } + /** * sign hap file * @@ -279,8 +319,9 @@ public abstract class SignProvider { checkCompatibleVersion(); File input = new File(signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE)); output = new File(signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE)); + String suffix = getFileSuffix(output); if (input.getCanonicalPath().equals(output.getCanonicalPath())) { - tmpOutput = File.createTempFile("signedHap", ".hap"); + tmpOutput = File.createTempFile("signedHap", "." + suffix); isPathOverlap = true; } else { tmpOutput = output; @@ -306,6 +347,8 @@ public abstract class SignProvider { signerConfig.setCompatibleVersion(Integer.parseInt( signParams.get(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION))); ZipDataInput[] contents = {beforeCentralDir, centralDirectory, eocd}; + + appendCodeSignBlock(signerConfig, tmpOutput, suffix, centralDirectoryOffset); byte[] signingBlock = SignHap.sign(contents, signerConfig, optionalBlocks); long newCentralDirectoryOffset = centralDirectoryOffset + signingBlock.length; ZipUtils.setCentralDirectoryOffset(eocdBuffer, newCentralDirectoryOffset); @@ -315,7 +358,8 @@ public abstract class SignProvider { isRet = true; } } catch (IOException | InvalidKeyException | HapFormatException | MissingParamsException - | InvalidParamsException | ProfileException | NumberFormatException | CustomException e) { + | InvalidParamsException | ProfileException | NumberFormatException | CustomException + | FsVerityDigestException | CodeSignException e) { printErrorLogWithoutStack(e); } catch (SignatureException e) { printErrorLog(e); @@ -323,6 +367,54 @@ public abstract class SignProvider { return doAfterSign(isRet, isPathOverlap, tmpOutput, output); } + /** + * append code signBlock + * + * @param signerConfig signerConfig + * @param tmpOutput temp output file + * @param suffix suffix + * @param centralDirectoryOffset central directory offset + * @throws FsVerityDigestException FsVerity digest on error + * @throws CodeSignException code sign on error + * @throws IOException IO error + * @throws HapFormatException hap format on error + */ + private void appendCodeSignBlock(SignerConfig signerConfig, File tmpOutput, String suffix, + long centralDirectoryOffset) + throws FsVerityDigestException, CodeSignException, IOException, HapFormatException { + if (signParams.get(ParamConstants.PARAM_CODE_SIGN) + .equals(ParamConstants.CodeSignFlag.CODE_SIGNED.getCodeSignFlag())){ + int codeSignOffset = (int) + (centralDirectoryOffset + ((4 + 4 + 4) * (optionalBlocks.size() + 2) + (4 + 4 + 4))); + // create CodeSigning Object + CodeSigning codeSigning = new CodeSigning(signerConfig); + byte[] codeSignArray=codeSigning.getCodeSignBlock(tmpOutput,codeSignOffset,suffix); + ByteBuffer result = ByteBuffer.allocate(codeSignArray.length + (4 + 4 + 4)); + result.order(ByteOrder.LITTLE_ENDIAN); + result.putInt(HapUtils.HAP_CODE_SIGN_BLOCK_ID); // type + result.putInt(codeSignArray.length); // length + result.putInt(codeSignOffset); // offset + result.put(codeSignArray); + SigningBlock propertyBlock = new SigningBlock(HapUtils.HAP_PROPERTY_BLOCK_ID, result.array()); + optionalBlocks.add(0,propertyBlock); + } + } + + /** + * obtain file name suffix + * + * @param output + * @return suffix + * @throws HapFormatException hap format error + */ + private String getFileSuffix(File output) throws HapFormatException { + String[] fileNameArray = output.getName().split("\\."); + if (fileNameArray.length < ParamConstants.FILE_NAME_MIN_LENGTH) { + throw new HapFormatException("hap format error :" + output); + } + return fileNameArray[fileNameArray.length - 1]; + } + /** * Load certificate chain from input parameters * @@ -392,15 +484,15 @@ public abstract class SignProvider { * @param alignment alignment * @throws IOException io error */ - private void copyFileAndAlignment(File input, File tmpOutput, int alignment) throws IOException { + private void copyFileAndAlignment(File input, File tmpOutput, int alignment) + throws IOException, HapFormatException { try (JarFile inputJar = new JarFile(input, false); FileOutputStream outputFile = new FileOutputStream(tmpOutput); JarOutputStream outputJar = new JarOutputStream(outputFile)) { long timestamp = TIMESTAMP; timestamp -= TimeZone.getDefault().getOffset(timestamp); outputJar.setLevel(COMPRESSION_MODE); - List entryNames = SignHap.getEntryNamesFromHap(inputJar); - SignHap.copyFiles(entryNames, inputJar, outputJar, timestamp, alignment); + SignHap.copyFiles(inputJar, outputJar, timestamp, alignment); } } @@ -576,7 +668,9 @@ public abstract class SignProvider { ParamConstants.PARAM_REMOTE_SERVER, ParamConstants.PARAM_BASIC_PROFILE_SIGNED, ParamConstants.PARAM_LOCAL_PUBLIC_CERT, - ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION + ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION, + ParamConstants.PARAM_CODE_SIGN, + ParamConstants.PARAM_IN_FORM }; Set paramSet = ParamProcessUtil.initParamField(paramFileds); @@ -588,10 +682,28 @@ public abstract class SignProvider { if (!signParams.containsKey(ParamConstants.PARAM_BASIC_PROFILE_SIGNED)) { signParams.put(ParamConstants.PARAM_BASIC_PROFILE_SIGNED, "1"); } + checkCodeSign(); checkSignatureAlg(); checkSignAlignment(); } + /** + * Check code sign, if param do not have code sign default "1". + * + * @throws InvalidParamsException invalid param + */ + protected void checkCodeSign() throws InvalidParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_CODE_SIGN)) { + signParams.put(ParamConstants.PARAM_CODE_SIGN, ParamConstants.CodeSignFlag.CODE_SIGNED.getCodeSignFlag()); + return; + } + String codeSign = signParams.get(ParamConstants.PARAM_CODE_SIGN); + if (!codeSign.equals(ParamConstants.CodeSignFlag.CODE_SIGNED.getCodeSignFlag()) + && !codeSign.equals(ParamConstants.CodeSignFlag.CODE_UNSIGNED.getCodeSignFlag())) { + throw new InvalidParamsException("Invalid parameter: " + ParamConstants.PARAM_CODE_SIGN); + } + } + /** * Check compatible version, if param do not have compatible version default 9. * diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java index bc94668de46bccd15069f025448f3fe36159d1da..7ad13e40b4fd1155db9934b9edcf59f798daf429 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java @@ -97,7 +97,7 @@ public class SignBin { long profileDataLen = FileUtils.getFileLen(profileFile); if (!checkBinAndProfileLengthIsValid(binFileLen, profileDataLen)) { LOGGER.error("file length is invalid, binFileLen: " + binFileLen - + " profileDataLen: " + profileDataLen); + + " profileDataLen: " + profileDataLen); throw new IOException(); } @@ -108,7 +108,7 @@ public class SignBin { } char isSigned = SignatureBlockTypes.getProfileBlockTypes(profileSigned); byte[] proBlockByte = - HwBlockHead.getBlockHead(isSigned, SignatureBlockTags.DEFAULT, (short) profileDataLen, (int) offset); + HwBlockHead.getBlockHead(isSigned, SignatureBlockTags.DEFAULT, (short) profileDataLen, (int) offset); offset += profileDataLen; if (isLongOverflowInteger(offset)) { @@ -126,7 +126,7 @@ public class SignBin { } private static boolean writeSignedBin(String inputFile, byte[] proBlockByte, byte[] signBlockByte, - String profileFile, String outputFile) { + String profileFile, String outputFile) { try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile); DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);) { // 1. write the input file to the output file. @@ -171,7 +171,7 @@ public class SignBin { return false; } HwSignHead signHeadData = new HwSignHead(); - byte[] signHeadByte = signHeadData.getSignHead((int) size); + byte[] signHeadByte = signHeadData.getSignHead((int) size, HwSignHead.NUM_OF_BLOCK); if (signHeadByte == null) { LOGGER.error("Failed to get sign head data."); return false; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java new file mode 100644 index 0000000000000000000000000000000000000000..e517f862fe2599894138ccbe2541851bb2f9d5fb --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java @@ -0,0 +1,252 @@ +/* + * 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.hapsigntool.hap.sign; + +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; +import com.ohos.hapsigntool.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.HwBlockHead; +import com.ohos.hapsigntool.hap.entity.HwSignHead; +import com.ohos.hapsigntool.hap.entity.SignBlockData; +import com.ohos.hapsigntool.hap.entity.SignContentInfo; +import com.ohos.hapsigntool.hap.entity.SignatureBlockTags; +import com.ohos.hapsigntool.hap.entity.SignatureBlockTypes; +import com.ohos.hapsigntool.hap.exception.HapFormatException; +import com.ohos.hapsigntool.hap.exception.SignatureException; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.HashUtils; +import com.ohos.hapsigntool.utils.ParamConstants; +import com.ohos.hapsigntool.utils.ParamProcessUtil; +import com.ohos.hapsigntool.utils.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * LiteOS bin file Signature signer. + * + * @since 2021/12/21 + */ +public class SignElf { + /** + * Constructor of Method + */ + private SignElf() { + } + + private static final Logger LOGGER = LogManager.getLogger(SignElf.class); + + private static final String CODESIGN_ON = "1"; + + private static final char CODESIGN_BLOCK_TYPE = 3; + + private static int blockNum = 0; + + /** + * Sign the bin file. + * + * @param signerConfig Config of the bin file to be signed. + * @param signParams The input parameters of sign bin. + * @return true if sign successfully; false otherwise. + */ + public static boolean sign(SignerConfig signerConfig, Map signParams) { + boolean result = false; + /* 1. Make block head, write to output file. */ + String inputFile = signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE); + String outputFile = signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE); + String profileFile = signParams.get(ParamConstants.PARAM_BASIC_PROFILE); + String profileSigned = signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED); + if (!writeBlockDataToFile(signerConfig, inputFile, outputFile, profileFile, profileSigned, signParams)) { + LOGGER.error("The block head data made failed."); + ParamProcessUtil.delDir(new File(outputFile)); + return false; + } + LOGGER.info("The block head data made success."); + + /* 2. Make sign data, and write to output file */ + if (!writeSignHeadDataToOutputFile(inputFile, outputFile, blockNum)) { + LOGGER.error("The sign head data made failed."); + ParamProcessUtil.delDir(new File(outputFile)); + } else { + result = true; + } + return result; + } + + private static boolean writeBlockDataToFile(SignerConfig signerConfig, + String inputFile, String outputFile, String profileFile, String profileSigned, Map signParams) { + try { + List signDataList = new ArrayList<>(); + + long binFileLen = FileUtils.getFileLen(inputFile); + if (binFileLen == -1) { + LOGGER.error("file length is invalid, bin file len: " + binFileLen); + throw new IOException(); + } + // 1. generate sign data + if (!StringUtils.isEmpty(signParams.get(ParamConstants.PARAM_BASIC_PROFILE))) { + signDataList.add(generateProfileSignByte(profileFile, profileSigned)); + } + blockNum = signDataList.size(); + SignBlockData codeSign = generateCodeSignByte(signerConfig, signParams, inputFile, blockNum, binFileLen); + if (codeSign != null) { + signDataList.add(0, codeSign); + } + blockNum = signDataList.size(); + // 2. use sign data generate offset and sign block head + generateSignBlockHead(signDataList); + + return writeSignedElf(inputFile, signDataList, outputFile); + } catch (IOException e) { + LOGGER.error("writeBlockDataToFile failed.", e); + return false; + } catch (FsVerityDigestException | CodeSignException | HapFormatException e) { + throw new RuntimeException(e); + } + } + + private static boolean writeSignedElf(String inputFile, List signBlockList, String outputFile) { + try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile); + DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream)) { + // 1. write the input file to the output file. + if (!FileUtils.writeFileToDos(inputFile, dataOutputStream)) { + LOGGER.error("Failed to write information of input file: " + inputFile + + " to outputFile: " + outputFile); + throw new IOException(); + } + + // 2. write block head to the output file. + for (SignBlockData signBlockData : signBlockList) { + if (!FileUtils.writeByteToDos(signBlockData.getBlockHead(), dataOutputStream)) { + LOGGER.error("Failed to write Block Head to output file: " + outputFile); + throw new IOException(); + } + } + + // 3. write block data to the output file. + for (SignBlockData signBlockData : signBlockList) { + boolean writeFlag; + if (signBlockData.isByte()) { + writeFlag = FileUtils.writeByteToDos(signBlockData.getSignData(), dataOutputStream); + } else { + writeFlag = FileUtils.writeFileToDos(signBlockData.getSignFile(), dataOutputStream); + } + + if (!writeFlag) { + LOGGER.error("Failed to write Block Data to output file: " + outputFile); + throw new IOException(); + } + } + } catch (IOException e) { + LOGGER.error("writeSignedBin failed.", e); + return false; + } + return true; + } + + private static void generateSignBlockHead(List signDataList) + throws IOException { + long offset = (long) HwBlockHead.getBlockLen() * signDataList.size(); + + for (int i = 0; i < signDataList.size(); i++) { + SignBlockData signBlockData = signDataList.get(i); + + signBlockData.setBlockHead(HwBlockHead.getBlockHeadLittleEndian(signBlockData.getType(), SignatureBlockTags.DEFAULT, + (short) signBlockData.getLen(), (int) offset)); + offset += signBlockData.getLen(); + if (isLongOverflowInteger(offset)) { + LOGGER.error("The sign block " + i + "offset is overflow integer, offset: " + offset); + throw new IOException(); + } + } + } + + private static SignBlockData generateProfileSignByte(String profileFile, String profileSigned) throws IOException { + long profileDataLen = FileUtils.getFileLen(profileFile); + + if (profileDataLen == -1 || isLongOverflowShort(profileDataLen)) { + LOGGER.error("file length is invalid, profileDataLen: " + profileDataLen); + throw new IOException(); + } + + char isSigned = SignatureBlockTypes.getProfileBlockTypes(profileSigned); + return new SignBlockData(profileFile, isSigned); + } + + private static SignBlockData generateCodeSignByte(SignerConfig signerConfig, Map signParams, String inputFile, + int blockNum, long binFileLen) throws IOException, FsVerityDigestException, CodeSignException, HapFormatException { + if (CODESIGN_ON.equals(signParams.get(ParamConstants.PARAM_CODE_SIGN))) { + CodeSigning codeSigning = new CodeSigning(signerConfig); + long offset = binFileLen + (long) HwBlockHead.getBlockLen() * blockNum; + byte[] codesignData = codeSigning.getCodeSignBlock(new File(inputFile), offset, signParams.get(ParamConstants.PARAM_IN_FORM)); + return new SignBlockData(codesignData, CODESIGN_BLOCK_TYPE); + } + return null; + } + + private static boolean writeSignHeadDataToOutputFile(String inputFile, String outputFile, int blockNum) { + long size = FileUtils.getFileLen(outputFile) - FileUtils.getFileLen(inputFile); + if (isLongOverflowInteger(size)) { + LOGGER.error("File size is Overflow integer range."); + return false; + } + HwSignHead signHeadData = new HwSignHead(); + byte[] signHeadByte = signHeadData.getSignHeadLittleEndian((int) size, blockNum); + if (signHeadByte == null) { + LOGGER.error("Failed to get sign head data."); + return false; + } + return FileUtils.writeByteToOutFile(signHeadByte, outputFile); + } + + private static boolean writeSignDataToOutputFile(SignerConfig signerConfig, String outputFile, String signAlg) { + String alg = ParamProcessUtil.getSignatureAlgorithm(signAlg).getContentDigestAlgorithm().getDigestAlgorithm(); + byte[] data = HashUtils.getFileDigest(outputFile, alg); + if (data == null) { + LOGGER.error("getFileDigest failed."); + return false; + } + byte[] outputChunk = null; + SignContentInfo contentInfo = null; + try { + contentInfo = new SignContentInfo(); + contentInfo.addContentHashData( + (char) 0, SignatureBlockTags.HASH_ROOT_4K, (short) HashUtils.getHashAlgsId(alg), data.length, data); + byte[] dig = contentInfo.getByteContent(); + outputChunk = Pkcs7Generator.BC.generateSignedData(dig, signerConfig); + return FileUtils.writeByteToOutFile(outputChunk, outputFile); + } catch (SignatureException e) { + LOGGER.error("Sign hap Lite failed.", e); + } + return false; + } + + private static boolean isLongOverflowInteger(long num) { + return (num - (num & 0xffffffffL)) != 0; + } + + private static boolean isLongOverflowShort(long num) { + return (num - (num & 0xffffL)) != 0; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java index eede0eaab64946ffd2011a6bd7bfcc4e20fd9e18..219a5898488fa509cc239a530ab8f65ba4ccaacb 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -19,8 +19,10 @@ import com.ohos.hapsigntool.api.model.Options; import com.ohos.hapsigntool.hap.config.SignerConfig; import com.ohos.hapsigntool.hap.entity.Pair; import com.ohos.hapsigntool.hap.entity.SigningBlock; +import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.exception.SignatureException; import com.ohos.hapsigntool.utils.HapUtils; +import com.ohos.hapsigntool.utils.StringUtils; import com.ohos.hapsigntool.zip.ZipDataInput; import java.io.IOException; @@ -28,17 +30,12 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.DigestException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; + /** * Hap Signature Scheme signer @@ -63,96 +60,167 @@ public abstract class SignHap { return BLOCK_SIZE; } - /** - * Get all entries' name from hap which is opened as a jar-file. - * - * @param hap input hap-file which is opened as a jar-file. - * @return list of entries' names. - */ - public static List getEntryNamesFromHap(JarFile hap) { - List result = new ArrayList(); - for (Enumeration e = hap.entries(); e.hasMoreElements();) { - JarEntry entry = e.nextElement(); - if (!entry.isDirectory()) { - result.add(entry.getName()); - } - } - return result; - } - /** * Copy the jar file and align the storage entries. * - * @param entryNames list of entries' name * @param in input hap-file which is opened as a jar-file. * @param out output stream of jar. * @param timestamp ZIP file timestamps * @param defaultAlignment default value of alignment. * @throws IOException io error. */ - public static void copyFiles(List entryNames, JarFile in, - JarOutputStream out, long timestamp, int defaultAlignment) throws IOException { - Collections.sort(entryNames); + public static void copyFiles(JarFile in, + JarOutputStream out, long timestamp, int defaultAlignment) throws IOException, HapFormatException { + // split compressed and uncompressed + List entryListStored = in.stream() + .filter(jarFile -> jarFile.getMethod() == JarEntry.STORED).collect(Collectors.toList()); + + // uncompressed special files and place in front + entryListStored = storedEntryListOfSort(entryListStored); long offset = INIT_OFFSET_LEN; - for (String name : entryNames) { - JarEntry inEntry = in.getJarEntry(name); - if (inEntry.getMethod() != JarEntry.STORED) { + String lastAlignmentEntryName = ""; + for (JarEntry inEntry : entryListStored) { + String entryName = inEntry.getName(); + if (!(entryName.endsWith(".so")) && !(entryName.endsWith(".abc"))) { + lastAlignmentEntryName = entryName; + break; + } + } + for (JarEntry inEntry : entryListStored) { + if (inEntry == null) { continue; } offset += JarFile.LOCHDR; - JarEntry outEntry = new JarEntry(inEntry); - outEntry.setTime(timestamp); - - outEntry.setComment(null); - outEntry.setExtra(null); - + JarEntry outEntry = getJarEntry(timestamp, inEntry); offset += outEntry.getName().length(); - int alignment = getStoredEntryDataAlignment(name, defaultAlignment); + int alignment = getStoredEntryDataAlignment(inEntry.getName(), defaultAlignment,lastAlignmentEntryName); if (alignment > 0 && (offset % alignment != 0)) { int needed = alignment - (int) (offset % alignment); outEntry.setExtra(new byte[needed]); offset += needed; } + out.putNextEntry(outEntry); + offset = writeOutputStreamAndGetOffset(in, out, inEntry, offset); + } + List entryListNotStored = in.stream() + .filter(jarFile -> jarFile.getMethod() != JarEntry.STORED).collect(Collectors.toList()); + // process byte alignment of the first compressed file + boolean isAlignmentFlag = StringUtils.isEmpty(lastAlignmentEntryName); + if (isAlignmentFlag) { + if (entryListNotStored.isEmpty()) { + throw new HapFormatException("Hap format is error, file missing"); + } + JarEntry firstEntry = entryListNotStored.get(0); + offset += JarFile.LOCHDR; + JarEntry outEntry = getFirstJarEntry(firstEntry, offset, timestamp); out.putNextEntry(outEntry); byte[] buffer = new byte[BUFFER_LENGTH]; - try (InputStream data = in.getInputStream(inEntry)) { - int num; - while ((num = data.read(buffer)) > 0) { - out.write(buffer, 0, num); - offset += num; - } - out.flush(); + writeOutputStream(in, out, firstEntry, buffer); + } + + copyFilesExceptStoredFile(entryListNotStored, in, out, timestamp, isAlignmentFlag); + } + + /** + * uncompressed special files are placed in front + * + * @param entryListStored stored file entry list + * @return List jarEntryList + */ + private static List storedEntryListOfSort(List entryListStored) { + return entryListStored.stream().sorted((entry1, entry2) -> { + String name1 = entry1.getName(); + String name2 = entry2.getName(); + // files ending with .abc or .so are placed before other files + boolean isSpecial1 = name1.endsWith(".abc") || name1.endsWith(".so"); + boolean isSpecial2 = name2.endsWith(".abc") || name2.endsWith(".so"); + if (isSpecial1 && !isSpecial2) { + return -1; + } else if (!isSpecial1 && isSpecial2) { + return 1; + } else { + // if all files are special files or none of them are special files,the files are sorted lexically + return name1.compareTo(name2); } + }).collect(Collectors.toList()); + } + + private static JarEntry getFirstJarEntry(JarEntry firstEntry, long offset, long timestamp) { + long currentOffset = offset; + JarEntry outEntry = getJarEntry(timestamp, firstEntry); + currentOffset += outEntry.getName().length(); + if (currentOffset % STORED_ENTRY_SO_ALIGNMENT != 0) { + int needed = STORED_ENTRY_SO_ALIGNMENT - (int) (currentOffset % STORED_ENTRY_SO_ALIGNMENT); + outEntry.setExtra(new byte[needed]); } + return outEntry; + } - copyFilesExceptStoredFile(entryNames, in, out, timestamp); + /** + * write first not stored entry to outputStream + * + * @param in jar file + * @param out jarOutputStream + * @param firstEntry jarEntry + * @param buffer byte[] + * @throws IOException IOExpcetion + */ + private static void writeOutputStream(JarFile in, JarOutputStream out, JarEntry firstEntry, byte[] buffer) + throws IOException { + try (InputStream data = in.getInputStream(firstEntry)) { + int num; + while ((num = data.read(buffer)) > 0) { + out.write(buffer, 0, num); + } + out.flush(); + } } - private static void copyFilesExceptStoredFile(List entryNames, JarFile in, - JarOutputStream out, long timestamp) throws IOException { + private static long writeOutputStreamAndGetOffset(JarFile in, JarOutputStream out, JarEntry inEntry, long offset) + throws IOException { byte[] buffer = new byte[BUFFER_LENGTH]; + long currentOffset = offset; + try (InputStream data = in.getInputStream(inEntry)) { + int num; + while ((num = data.read(buffer)) > 0) { + out.write(buffer, 0, num); + currentOffset += num; + } + out.flush(); + } + return currentOffset; + } + + private static JarEntry getJarEntry(long timestamp, JarEntry inEntry) { + JarEntry outEntry = new JarEntry(inEntry); + outEntry.setTime(timestamp); - for (String name : entryNames) { - JarEntry inEntry = in.getJarEntry(name); - if (inEntry.getMethod() == JarEntry.STORED) { + outEntry.setComment(null); + outEntry.setExtra(null); + return outEntry; + } + + private static void copyFilesExceptStoredFile(List entryListNotStored, JarFile in, + JarOutputStream out, long timestamp, boolean isAlignmentFlag) throws IOException { + byte[] buffer = new byte[BUFFER_LENGTH]; + int index=0; + if (isAlignmentFlag){ + index=1; + } + for (;index 0) { - out.write(buffer, 0, num); - } - out.flush(); - } + writeOutputStream(in,out,inEntry,buffer); } } @@ -163,11 +231,15 @@ public abstract class SignHap { * @param defaultAlignment default value of alignment. * @return value of alignment. */ - private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment) { + private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment, + String lastAlignmentEntryName) { if (defaultAlignment <= 0) { return 0; } - if (entryName.endsWith(".so")) { + if (!StringUtils.isEmpty(lastAlignmentEntryName) && entryName.equals(lastAlignmentEntryName)) { + return STORED_ENTRY_SO_ALIGNMENT; + } + if (entryName.endsWith(".so") || entryName.endsWith("abc")) { return STORED_ENTRY_SO_ALIGNMENT; } return defaultAlignment; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java index c5c5834cd78020f1436df6da0773e29b6104fc07..288481ad9f7e411869b6cfe9cb4fb9843f440ba8 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -16,6 +16,9 @@ package com.ohos.hapsigntool.hap.verify; import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.sign.VerifyCodeSignature; import com.ohos.hapsigntool.hap.entity.Pair; import com.ohos.hapsigntool.hap.entity.SigningBlock; import com.ohos.hapsigntool.hap.exception.HapFormatException; @@ -32,6 +35,7 @@ import com.ohos.hapsigntool.zip.ZipUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bouncycastle.cms.CMSException; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.util.Arrays; @@ -49,6 +53,8 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * Class of verify hap. @@ -248,6 +254,11 @@ public class VerifyHap { ByteBuffer signatureSchemeBlock = blockPair.getFirst(); List optionalBlocks = blockPair.getSecond(); Collections.reverse(optionalBlocks); + if (!checkCodeSign(hapFilePath, optionalBlocks)) { + String errMsg = "ZIP64 code sign data error"; + return new VerifyResult(false, VerifyResult.RET_CODESIGN_DATA_ERROR, errMsg); + } + long signingBlockOffset = hapSigningBlockAndOffsetInFile.getOffset(); ZipDataInput beforeHapSigningBlock = hapFile.slice(0, signingBlockOffset); ZipDataInput centralDirectoryBlock = hapFile.slice(zipInfo.getCentralDirectoryOffset(), @@ -269,10 +280,62 @@ public class VerifyHap { } catch (HapFormatException e) { LOGGER.error("Verify Hap failed, unsupported format hap.", e); result = new VerifyResult(false, VerifyResult.RET_UNSUPPORTED_FORMAT_ERROR, e.getMessage()); + } catch (FsVerityDigestException e) { + LOGGER.error("Verify Hap failed, fs-verity digest generate failed.", e); + result = new VerifyResult(false, VerifyResult.RET_DIGEST_ERROR, e.getMessage()); + } catch (VerifyCodeSignException e) { + LOGGER.error("Verify Hap failed, code sign block verify failed.", e); + result = new VerifyResult(false, VerifyResult.RET_CODE_SIGN_BLOCK_ERROR, e.getMessage()); + } catch (CMSException e) { + LOGGER.error("Verify Hap failed, code signature verify failed.", e); + result = new VerifyResult(false, VerifyResult.RET_SIGNATURE_ERROR, e.getMessage()); } return result; } + /** + * code sign check + * + * @param hapFilePath hap file path + * @param optionalBlocks optional blocks + * @throws FsVerityDigestException FsVerity digest on error + * @throws IOException IO error + * @throws VerifyCodeSignException verify code sign on error + * @throws CMSException cms on error + * @return true or false + */ + private boolean checkCodeSign(String hapFilePath, List optionalBlocks) + throws FsVerityDigestException, IOException, VerifyCodeSignException, CMSException { + Map map = optionalBlocks.stream() + .collect(Collectors.toMap(SigningBlock::getType, SigningBlock::getValue)); + byte[] propertyBlockArray = map.get(HapUtils.HAP_PROPERTY_BLOCK_ID); + if (propertyBlockArray != null && propertyBlockArray.length > 0) { + String[] fileNameArray = hapFilePath.split("\\."); + if (fileNameArray.length < ParamConstants.FILE_NAME_MIN_LENGTH) { + LOGGER.error("ZIP46 format not supported"); + return false; + } + ByteBuffer byteBuffer = ByteBuffer.wrap(propertyBlockArray); + String suffix = fileNameArray[fileNameArray.length - 1]; + ByteBuffer header = HapUtils.reverseSliceBuffer(byteBuffer, 0, ZIP_HEAD_OF_SUBSIGNING_BLOCK_LENGTH); + int blockOffset = header.getInt(); + int blockLength = header.getInt(); + int blockType = header.getInt(); + if (blockType != HapUtils.HAP_CODE_SIGN_BLOCK_ID) { + LOGGER.error("Verify Hap has no code sign data error!"); + return false; + } + File outputFile = new File(hapFilePath); + boolean isCodeSign = VerifyCodeSignature.verifyHap(outputFile, blockOffset, blockLength, suffix); + if (!isCodeSign) { + LOGGER.error("Verify Hap has no code sign data error!"); + return false; + } + return true; + } + return true; + } + private Pair> getHapSignatureSchemeBlockAndOptionalBlocks(ByteBuffer hapSigningBlock) throws SignatureNotFoundException { try { diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java index 484a23c6eb9f463ba89886f394c4ee4bbb55697e..44049356d55f395321d326fd1fb6275b58a92954 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java @@ -90,6 +90,16 @@ public class VerifyResult { */ public static final int RET_CRL_ERROR = 10011; + /** + * Return code of file code sign data error. + */ + public static final int RET_CODESIGN_DATA_ERROR = 10012; + + /** + * Return code of verify code sign error. + */ + public static final int RET_CODE_SIGN_BLOCK_ERROR = 10013; + private boolean result; private int code; private String message; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java index 4197932fa58bf215deced8c4a50503cb4470156d..ce5b51195f4e625b1ec3672101338a80ee63d849 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -27,9 +27,9 @@ import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; -import java.security.PublicKey; -import java.security.PrivateKey; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java index 65ce94357084dd2d947b5ce4f3614608fa3421c1..422a04d9edd17f368db89f457a436303200a476b 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -32,7 +32,6 @@ import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.operator.ContentSigner; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyPair; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java index 71d691cd11ced2bd27bf9f11abb34abd403afa71..38139a56f4e8819575c569a6264edf25f0fe4e8c 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -54,9 +54,9 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.Iterator; import java.util.List; -import java.util.Date; /** * Signed provision profile verifier. diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertificateUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertificateUtils.java index 96abd929a1d3d6251e329d7f0d835d4fb173c4f2..eda1116fddf2edb74f97d8d4fdd7d60ea011dfe3 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertificateUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertificateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Huawei Device Co., Ltd. + * Copyright (c) 2022-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 @@ -27,8 +27,8 @@ import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java index cc8d917f487594fcac05e88c72c3d915ebec73bd..cd73cf2db0269cbdda598d5c3d25a31b029436fc 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -221,6 +221,9 @@ public final class FileUtils { * @return true, if write successfully. */ public static boolean writeByteToDos(byte[] data, DataOutputStream dos) { + if (data == null) { + return true; + } try { dos.write(data); } catch (IOException e) { diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java index e51a3f59505ded067c6a171757b0e1966c9843db..dc771350ad3d28553b2e28d679964a281fb7c46a 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -73,6 +73,11 @@ public class HapUtils { */ public static final int HAP_PROPERTY_BLOCK_ID = 0x20000003; + /** + * ID of code sign block + */ + public static final int HAP_CODE_SIGN_BLOCK_ID = 0x30000001; + /** * The size of data block used to get digest */ diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java index 3b1dbde6f783da007716b57a82f6bc557d008fa6..6d3345361343a819dad0e11239fe552857676769 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-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 @@ -236,6 +236,21 @@ public class ParamConstants { */ public static final String PARAM_RESIGN_CONFIG_FILE = "resignconfig"; + /** + * sign file type bin or zip or elf + */ + public static final String PARAM_IN_FORM = "inForm"; + + /** + * The code sign params of resign hap + */ + public static final String PARAM_CODE_SIGN = "codesign"; + + /** + * file name split . of min length + */ + public static final int FILE_NAME_MIN_LENGTH = 2; + /** * Enumerated value of whether a profile is signed */ @@ -253,4 +268,19 @@ public class ParamConstants { return signFlag; } } + + public enum CodeSignFlag { + CODE_UNSIGNED("0"), + CODE_SIGNED("1"); + + private String codeSignFlag; + + CodeSignFlag(String codeSignFlag) { + this.codeSignFlag = codeSignFlag; + } + + public String getCodeSignFlag() { + return codeSignFlag; + } + } } \ No newline at end of file diff --git a/tools/commands.config b/tools/commands.config index 559d8c1c93c508bd9a1bb432dad4127d1eac8096..bc116ea9ddff1f457501233a17d6c70d2e85c2aa 100644 --- a/tools/commands.config +++ b/tools/commands.config @@ -86,10 +86,24 @@ 'verify-profile -inFile "app1-profile1.p7b" -outFile', 'verify-profile -inFile "app1-profile-cert-outTime.p7b" -outFile "result.json"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" ', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0"' + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -codesign "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -codesign "1"', + 'verify-app -inFile "app1-signed.hap" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile.p7b"', + 'verify-app -inFile "app1-signed.hap" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile1.p7b"', + 'verify-app -inFile "app1-signed.hap" -outCertChain "app1.cer" -outProfile "app1-profile.p7b"', ], 'case-assert-false': [ 'generate-keypair -keyPwd 123456 -keyAlg ECC -keySize NIST-P-384 -keystoreFile "ohtest.jks" -keystorePwd 123456 -extCfgFile "111.txt"', @@ -340,44 +354,124 @@ 'verify-profile -inFile "app1-profile1-changed.p7b" -outFile "verify-result.json"', 'verify-profile -inFile "app1-profile1.p7b" -outFile "verify-result.js00on"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "remoteSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "remoteSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "remoteSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "rewrw@%$" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "rewrw@%$" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "rewrw@%$" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1-222" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1-222" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1-222" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456789" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456789" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456789" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile1-changed.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile1-changed.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile1-changed.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "notexist\test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "notexist\test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "notexist\test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "HMAC" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "HMAC" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "HMAC" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohnull.p12" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohnull.p12" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohnull.p12" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.txt" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.txt" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.txt" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456789"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456789" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456789" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-profile -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -profileCertFile "profile1_error.pem" -inFile "app1-profile-release.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-profile.p7b" -keyPwd "123456" -keystorePwd "123456"', 'sign-profile -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -profileCertFile "profile1.pem" -inFile "app1-profile-release.json" -keystoreFile "ohtest.jks" -outFile "app1-profile.p7b" -keyPwd "123456" -keystorePwd "123456"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1_error.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1_error.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1_error.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile ', - 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile ' + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile ', + 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "1"', + 'verify-app -inFile "app1-signed.hap" -outCertChain "" -outProfile "app1-profile.p7b"', + 'verify-app -inFile "app1-signed.hap" -outCertChain "app1.cer" -outProfile ""', + 'verify-app -inFile "app1-signed.hap" -outCertChain "" -outProfile ""', + 'verify-app -inFile "app1-signed.hap" -outCertChain "app1.ce7778r" -outProfile "app1-profile1.p7b"', ] } \ No newline at end of file