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..8a1572fa42747aa2bb2c68e92ca570a3e6ec5fb7
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java
@@ -0,0 +1,240 @@
+/*
+ * 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;
+ }
+
+ /**
+ * Create a CodeSignBlockHeader object
+ *
+ * @return a CodeSignBlockHeader object
+ */
+ 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..959f9ed87a911beca73a3e3caf6c108590f68535
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java
@@ -0,0 +1,199 @@
+/*
+ * 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.FsVerityDigestException;
+import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException;
+import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor;
+import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptorWithSign;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * 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[127] 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;
+
+ private int type = MERKLE_TREE_INLINED;
+
+ private int treeLength;
+
+ private byte[] merkleTreeWithPadding;
+
+ private FsVerityDescriptorWithSign descriptorWithSign;
+
+ /**
+ * Constructor of ElfSignBlock
+ *
+ * @param paddingSize padding size before merkle tree
+ * @param merkleTreeData merkle tree data
+ * @param descriptorWithSign FsVerityDescriptorWithSign object
+ */
+ public ElfSignBlock(int paddingSize, byte[] merkleTreeData, FsVerityDescriptorWithSign descriptorWithSign) {
+ byte[] inMerkleTreeData = new byte[0];
+ if (merkleTreeData != null) {
+ inMerkleTreeData = merkleTreeData;
+ }
+ this.treeLength = paddingSize + inMerkleTreeData.length;
+ this.merkleTreeWithPadding = new byte[this.treeLength];
+ System.arraycopy(inMerkleTreeData, 0, merkleTreeWithPadding, paddingSize, inMerkleTreeData.length);
+ this.descriptorWithSign = descriptorWithSign;
+ }
+
+ private ElfSignBlock(int type, int treeLength, byte[] merkleTreeWithPadding,
+ FsVerityDescriptorWithSign descriptorWithSign) {
+ this.type = type;
+ this.treeLength = treeLength;
+ this.merkleTreeWithPadding = merkleTreeWithPadding;
+ this.descriptorWithSign = descriptorWithSign;
+ }
+
+ /**
+ * Return the byte size of code sign block
+ *
+ * @return byte size of code sign block
+ */
+ public int size() {
+ return Integer.BYTES * 2 + merkleTreeWithPadding.length + descriptorWithSign.size();
+ }
+
+ /**
+ * return padding length by the sign block offset
+ *
+ * @param signBlockOffset sign block offset based on the start of file
+ * @return merkle tree raw bytes offset based on the start of file
+ */
+ public static int computeMerkleTreePaddingLength(long signBlockOffset) {
+ return (int) (PAGE_SIZE_4K - (signBlockOffset + Integer.BYTES * 2) % PAGE_SIZE_4K) % PAGE_SIZE_4K;
+ }
+
+ public byte[] getMerkleTreeWithPadding() {
+ return merkleTreeWithPadding;
+ }
+
+ /**
+ * get DataSize
+ *
+ * @return DataSize
+ */
+ public long getDataSize() {
+ return descriptorWithSign.getFsVerityDescriptor().getFileSize();
+ }
+
+ /**
+ * get TreeOffset
+ *
+ * @return TreeOffset
+ */
+ public long getTreeOffset() {
+ return descriptorWithSign.getFsVerityDescriptor().getMerkleTreeOffset();
+ }
+
+ /**
+ * get Signature
+ *
+ * @return Signature
+ */
+ public byte[] getSignature() {
+ return descriptorWithSign.getSignature();
+ }
+
+ /**
+ * Return a string representation of the object
+ *
+ * @return string representation of the object
+ * @throws FsVerityDigestException if error
+ */
+ public byte[] toByteArray() throws FsVerityDigestException {
+ ByteBuffer bf = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN);
+ bf.putInt(type);
+ bf.putInt(merkleTreeWithPadding.length);
+ bf.put(merkleTreeWithPadding);
+ bf.put(descriptorWithSign.toByteArray());
+ return bf.array();
+ }
+
+ /**
+ * Init the ElfSignBlock by a byte array
+ *
+ * @param bytes Byte array representation of a ElfSignBlock object
+ * @return a newly created ElfSignBlock object
+ * @throws VerifyCodeSignException parse result invalid
+ */
+ public static ElfSignBlock fromByteArray(byte[] bytes) throws VerifyCodeSignException {
+ ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN);
+ bf.put(bytes);
+ // after put, rewind is mandatory before get
+ bf.rewind();
+ int inTreeType = bf.getInt();
+ if (MERKLE_TREE_INLINED != inTreeType) {
+ throw new VerifyCodeSignException("Invalid merkle tree type of ElfSignBlock");
+ }
+ int inTreeLength = bf.getInt();
+ byte[] treeWithPadding = new byte[inTreeLength];
+ bf.get(treeWithPadding);
+ 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");
+ }
+ byte[] fsdArray = new byte[FsVerityDescriptor.DESCRIPTOR_SIZE];
+ bf.get(fsdArray);
+ FsVerityDescriptor fsd = FsVerityDescriptor.fromByteArray(fsdArray);
+ if (inFsdLength != fsd.getSignSize() + FsVerityDescriptor.DESCRIPTOR_SIZE) {
+ throw new VerifyCodeSignException("Invalid sign size of ElfSignBlock");
+ }
+ byte[] inSignature = new byte[inFsdLength - FsVerityDescriptor.DESCRIPTOR_SIZE];
+ bf.get(inSignature);
+ FsVerityDescriptorWithSign fsVerityDescriptorWithSign = new FsVerityDescriptorWithSign(inFsdType, inFsdLength,
+ fsd, inSignature);
+ return new ElfSignBlock(inTreeType, inTreeLength, treeWithPadding, fsVerityDescriptorWithSign);
+ }
+}
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..2697ea7af826915595ec20b941f6dec06f82f964
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java
@@ -0,0 +1,142 @@
+/*
+ * 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");
+ }
+ if (bytes.length <= MAGIC_NUM_BYTES) {
+ throw new VerifyCodeSignException("Invalid bytes size 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..e7bcfc49d5db540dc34409db85df2d714e71584a
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java
@@ -0,0 +1,334 @@
+/*
+ * 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;
+ }
+
+ /**
+ * Create a NativeLibInfoSegment object
+ *
+ * @return a NativeLibInfoSegment object
+ */
+ 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..5531b7f837b0b55054bc7a1db9291d2e94f38916
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java
@@ -0,0 +1,342 @@
+/*
+ * 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.exception.VerifyCodeSignException;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Format of FsVerity descriptor
+ * uint8 version
+ * uint8 hashAlgorithm
+ * uint8 log2BlockSize
+ * uint8 saltSize
+ * uint32 signSize
+ * le64 dataSize
+ * uint8[64] rootHash
+ * uint8[32] salt
+ * uint32 flags
+ * uint8[4] 0
+ * uint64 treeOffset
+ * uint8[127] 0
+ * uint8 csVersion
+ *
+ * @since 2023/06/05
+ */
+public class FsVerityDescriptor {
+ /**
+ * fs-verity version, must be 1
+ */
+ public static final byte VERSION = 1;
+
+ /**
+ * page size in bytes
+ */
+ public static final int PAGE_SIZE_4K = 4096;
+
+ /**
+ * 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;
+
+ /**
+ * code sign version
+ */
+ public static final byte CODE_SIGN_VERSION = 0x1;
+
+ /**
+ * FsVerity descriptor size
+ */
+ public static final int DESCRIPTOR_SIZE = 256;
+
+ /**
+ * root hash size
+ */
+ public static final int ROOT_HASH_FILED_SIZE = 64;
+
+ /**
+ * salt size
+ */
+ public static final int SALT_SIZE = 32;
+
+ /**
+ * reserved size
+ */
+ public static final int RESERVED_SIZE_AFTER_FLAGS = 4;
+
+ /**
+ * reserved size
+ */
+ public static final int RESERVED_SIZE_AFTER_TREE_OFFSET = 127;
+
+ private byte version;
+
+ private long fileSize;
+
+ private byte hashAlgorithm;
+
+ private byte log2BlockSize;
+
+ private byte saltSize;
+
+ private int signSize;
+
+ private byte[] salt;
+
+ private byte[] rawRootHash;
+
+ private int flags;
+
+ private long merkleTreeOffset;
+
+ private byte csVersion;
+
+ private FsVerityDescriptor(Builder builder) {
+ this.version = builder.version;
+ this.fileSize = builder.fileSize;
+ this.hashAlgorithm = builder.hashAlgorithm;
+ this.log2BlockSize = builder.log2BlockSize;
+ this.saltSize = builder.saltSize;
+ this.signSize = builder.signSize;
+ this.salt = builder.salt;
+ this.rawRootHash = builder.rawRootHash;
+ this.flags = builder.flags;
+ this.merkleTreeOffset = builder.merkleTreeOffset;
+ this.csVersion = builder.csVersion;
+ }
+
+ public long getFileSize() {
+ return fileSize;
+ }
+
+ public long getMerkleTreeOffset() {
+ return merkleTreeOffset;
+ }
+
+ public int getSignSize() {
+ return signSize;
+ }
+
+ /**
+ * Init the FsVerityDescriptor by a byte array
+ *
+ * @param bytes Byte array representation of a FsVerityDescriptor object
+ * @return a newly created FsVerityDescriptor object
+ * @throws VerifyCodeSignException parse result invalid
+ */
+ public static FsVerityDescriptor fromByteArray(byte[] bytes) throws VerifyCodeSignException {
+ ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN);
+ bf.put(bytes);
+ // after put, rewind is mandatory before get
+ bf.rewind();
+ FsVerityDescriptor.Builder builder = new FsVerityDescriptor.Builder();
+ 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.setVersion(inFsVersion).setHashAlgorithm(inFsHashAlgorithm).setLog2BlockSize(inLog2BlockSize);
+ byte inSaltSize = bf.get();
+ int inSignSize = bf.getInt();
+ long inDataSize = bf.getLong();
+ byte[] inRootHash = new byte[FsVerityDescriptor.ROOT_HASH_FILED_SIZE];
+ bf.get(inRootHash);
+ builder.setSaltSize(inSaltSize).setSignSize(inSignSize).setFileSize(inDataSize).setRawRootHash(inRootHash);
+ byte[] inSalt = new byte[FsVerityDescriptor.SALT_SIZE];
+ 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[FsVerityDescriptor.RESERVED_SIZE_AFTER_TREE_OFFSET]);
+ byte inCsVersion = bf.get();
+ builder.setSalt(inSalt).setFlags(inFlags).setMerkleTreeOffset(inTreeOffset).setCsVersion(inCsVersion);
+ return builder.build();
+ }
+
+ /**
+ * 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 (this.saltSize > SALT_SIZE) {
+ throw new FsVerityDigestException("Salt is too long");
+ }
+ buffer.put(this.saltSize);
+ buffer.putInt(signSize);
+ 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);
+ writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_TREE_OFFSET);
+ buffer.put(csVersion);
+ return buffer.array();
+ }
+
+ /**
+ * Get bytes for generate digest, first byte is CODE_SIGN_VERSION, sign size is 0, last 128 bytes is 0
+ *
+ * @return bytes of descriptor
+ * @throws FsVerityDigestException if error
+ */
+ public byte[] getByteForGenerateDigest() throws FsVerityDigestException {
+ ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+ buffer.put(CODE_SIGN_VERSION);
+ buffer.put(hashAlgorithm);
+ buffer.put(log2BlockSize);
+ if (this.saltSize > SALT_SIZE) {
+ throw new FsVerityDigestException("Salt is too long");
+ }
+ buffer.put(this.saltSize);
+ buffer.putInt(0);
+ 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 byte version = VERSION;
+
+ private long fileSize;
+
+ private byte hashAlgorithm;
+
+ private byte log2BlockSize;
+
+ private byte saltSize;
+
+ private int signSize;
+
+ private byte[] salt;
+
+ private byte[] rawRootHash;
+
+ private int flags;
+
+ private long merkleTreeOffset;
+
+ private byte csVersion;
+
+ public Builder setVersion(byte version) {
+ this.version = version;
+ return this;
+ }
+
+ 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 setSignSize(int signSize) {
+ this.signSize = signSize;
+ return this;
+ }
+
+ public Builder setSaltSize(byte saltSize) {
+ this.saltSize = saltSize;
+ 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 Builder setCsVersion(byte csVersion) {
+ this.csVersion = csVersion;
+ return this;
+ }
+
+ /**
+ * Create a FsVerityDescriptor object
+ *
+ * @return a FsVerityDescriptor object
+ */
+ public FsVerityDescriptor build() {
+ return new FsVerityDescriptor(this);
+ }
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptorWithSign.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptorWithSign.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e3084b55d5b2c09f9ab451f0b540767def7dd15
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptorWithSign.java
@@ -0,0 +1,101 @@
+/*
+ * 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;
+
+/**
+ * fsverity descriptor and signature
+ * 1) u32 type: 0x1 fsverity descriptor
+ * 2) u32 length: fsverity descriptor size
+ * 3) FsVerityDescriptor: fsVerityDescriptor
+ * 4) u8[] signature: signature after signing the data in byte array representation
+ *
+ * @since 2023/09/08
+ */
+public class FsVerityDescriptorWithSign {
+ private int type = FsVerityDescriptor.FS_VERITY_DESCRIPTOR_TYPE;
+
+ private int length;
+
+ private FsVerityDescriptor fsVerityDescriptor;
+
+ private byte[] signature = new byte[0];
+
+ /**
+ * Constructor of FsVerityDescriptorWithSign
+ *
+ * @param fsVerityDescriptor fs-verity descriptor
+ * @param signature signature
+ */
+ public FsVerityDescriptorWithSign(FsVerityDescriptor fsVerityDescriptor, byte[] signature) {
+ this.fsVerityDescriptor = fsVerityDescriptor;
+ if (signature != null) {
+ this.signature = signature;
+ }
+ length = FsVerityDescriptor.DESCRIPTOR_SIZE + this.signature.length;
+ }
+
+ /**
+ * Constructor of FsVerityDescriptorWithSign
+ *
+ * @param type fs-verity descriptor type
+ * @param length fs-verity descriptor with signature size
+ * @param fsVerityDescriptor fs-verity descriptor
+ * @param signature signature
+ */
+ public FsVerityDescriptorWithSign(int type, int length, FsVerityDescriptor fsVerityDescriptor, byte[] signature) {
+ this.type = type;
+ this.length = length;
+ this.fsVerityDescriptor = fsVerityDescriptor;
+ this.signature = signature;
+ }
+
+ /**
+ * Returns byte size of FsVerityDescriptorWithSign
+ *
+ * @return byte size of FsVerityDescriptorWithSign
+ */
+ public int size() {
+ return Integer.BYTES * 2 + FsVerityDescriptor.DESCRIPTOR_SIZE + signature.length;
+ }
+
+ /**
+ * Converts FsVerityDescriptorWithSign to a newly created byte array
+ *
+ * @return Byte array representation of FsVerityDescriptorWithSign
+ * @throws FsVerityDigestException if error
+ */
+ public byte[] toByteArray() throws FsVerityDigestException {
+ ByteBuffer buffer = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(type);
+ buffer.putInt(length);
+ buffer.put(fsVerityDescriptor.toByteArray());
+ buffer.put(signature);
+ return buffer.array();
+ }
+
+ public FsVerityDescriptor getFsVerityDescriptor() {
+ return fsVerityDescriptor;
+ }
+
+ public byte[] getSignature() {
+ return signature;
+ }
+}
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..57501d09d6ab5e056e991a4fcd669340b362fa94
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.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.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;
+ // sign size is 0, cs version is 0
+ FsVerityDescriptor.Builder builder = new FsVerityDescriptor.Builder().setFileSize(size)
+ .setHashAlgorithm(FS_VERITY_HASH_ALGORITHM.getId())
+ .setLog2BlockSize(LOG_2_OF_FSVERITY_HASH_PAGE_SIZE)
+ .setSaltSize((byte) getSaltSize())
+ .setSalt(salt)
+ .setRawRootHash(merkleTree.rootHash)
+ .setFlags(flags)
+ .setMerkleTreeOffset(fsvTreeOffset);
+ byte[] fsVerityDescriptor = builder.build().getByteForGenerateDigest();
+ 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..238f5cb8a16315ee07327a825061ce02c6df730f
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java
@@ -0,0 +1,264 @@
+/*
+ * 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.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERUTF8String;
+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 {
+ /**
+ * OID of the signer identity
+ */
+ public static final String SIGNER_OID = "1.3.6.1.4.1.2011.2.376.1.4.1";
+
+ 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();
+
+ private String ownerID;
+
+ public void setOwnerID(String ownerID) {
+ this.ownerID = ownerID;
+ }
+ @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);
+ // Unsupported certificate revocation, SignedData's _crls is null
+ SignedData signedData = new SignedData(new ASN1Integer(1), pairDigestAndSignInfo.getFirst(),
+ new ContentInfo(PKCSObjectIdentifiers.data, null), createBerSetFromLst(signConfig.getCertificates()),
+ createBerSetFromLst(null), 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);
+ if (ownerID != null) {
+ Attribute ownerIDAttr = new Attribute(new ASN1ObjectIdentifier(SIGNER_OID),
+ new DERSet(new DERUTF8String(ownerID)));
+ table.add(ownerIDAttr);
+ }
+ 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..e664912a75a6ad8abd5bbd0297205dd5db297405
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java
@@ -0,0 +1,176 @@
+/*
+ * 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.utils.FileUtils;
+
+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 long 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 FileUtils.isRunnableFile(this.getFileName());
+ }
+
+ /**
+ * 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 long 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);
+ }
+
+ /**
+ * Builder of CentralDirectory class
+ */
+ public static class Builder {
+ private char compressionMethod;
+
+ private char fileNameLength;
+
+ private char extraFieldLength;
+
+ private char fileCommentLength;
+
+ private long 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(long relativeOffsetOfLocalHeader) {
+ this.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader;
+ return this;
+ }
+
+ public Builder setFileName(byte[] fileName) {
+ this.fileName = fileName;
+ return this;
+ }
+
+ /**
+ * Create a CentralDirectory object
+ *
+ * @return a CentralDirectory object
+ */
+ 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..cb9f3e10013763b8f89dc47e95e24121c2b766dd
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java
@@ -0,0 +1,427 @@
+/*
+ * 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.FsVerityDescriptorWithSign;
+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.hap.exception.ProfileException;
+import com.ohos.hapsigntool.signer.LocalSigner;
+import com.ohos.hapsigntool.utils.FileUtils;
+import com.ohos.hapsigntool.utils.StringUtils;
+import com.ohos.hapsigntool.zip.UnsignedDecimalUtil;
+import com.ohos.hapsigntool.zip.Zip;
+import com.ohos.hapsigntool.zip.ZipEntryHeader;
+import com.ohos.hapsigntool.zip.ZipEntry;
+
+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.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;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 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
+ * @param profileContent profile of the elf
+ * @return byte array of code sign block
+ * @throws CodeSignException code signing exception
+ * @throws IOException io error
+ * @throws FsVerityDigestException computing FsVerity digest error
+ * @throws ProfileException profile of elf is invalid
+ */
+ public byte[] getElfCodeSignBlock(File input, long offset, String inForm, String profileContent)
+ throws CodeSignException, FsVerityDigestException, IOException, ProfileException {
+ if (!SUPPORT_BIN_FILE_FORM.contains(inForm)) {
+ throw new CodeSignException("file's format is unsupported");
+ }
+ long fileSize = input.length();
+ int paddingSize = ElfSignBlock.computeMerkleTreePaddingLength(offset);
+ long fsvTreeOffset = offset + Integer.BYTES * 2 + paddingSize;
+ try (FileInputStream inputStream = new FileInputStream(input)) {
+ FsVerityGenerator fsVerityGenerator = new FsVerityGenerator();
+ fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset);
+ byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest();
+ // ownerID should be DEBUG_LIB_ID while signing ELF
+ String ownerID = (profileContent == null) ? "DEBUG_LIB_ID" : HapUtils.getAppIdentifier(profileContent);
+ byte[] signature = generateSignature(fsVerityDigest, ownerID);
+ // add fs-verify info
+ FsVerityDescriptor.Builder fsdbuilder = new FsVerityDescriptor.Builder().setFileSize(fileSize)
+ .setHashAlgorithm(FsVerityGenerator.getFsVerityHashAlgorithm())
+ .setLog2BlockSize(FsVerityGenerator.getLog2BlockSize())
+ .setSaltSize((byte) fsVerityGenerator.getSaltSize())
+ .setSignSize(signature.length)
+ .setFileSize(fileSize)
+ .setSalt(fsVerityGenerator.getSalt())
+ .setRawRootHash(fsVerityGenerator.getRootHash())
+ .setFlags(FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET)
+ .setMerkleTreeOffset(fsvTreeOffset)
+ .setCsVersion(FsVerityDescriptor.CODE_SIGN_VERSION);
+ FsVerityDescriptorWithSign fsVerityDescriptorWithSign = new FsVerityDescriptorWithSign(fsdbuilder.build(),
+ signature);
+ byte[] treeBytes = fsVerityGenerator.getTreeBytes();
+ ElfSignBlock signBlock = new ElfSignBlock(paddingSize, treeBytes, fsVerityDescriptorWithSign);
+ LOGGER.info("Sign elf 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
+ * @param profileContent profile of the hap
+ * @param zip zip
+ * @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
+ * @throws ProfileException profile of the hap error
+ */
+ public byte[] getCodeSignBlock(File input, long offset, String inForm, String profileContent, Zip zip)
+ throws CodeSignException, IOException, HapFormatException, FsVerityDigestException, ProfileException {
+ LOGGER.info("Start to sign code.");
+ if (!SUPPORT_FILE_FORM.contains(inForm)) {
+ throw new CodeSignException("file's format is unsupported");
+ }
+ long dataSize = computeDataSize(zip);
+ 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.");
+ String ownerID = HapUtils.getAppIdentifier(profileContent);
+
+ try (FileInputStream inputStream = new FileInputStream(input)) {
+ Pair hapSignInfoAndMerkleTreeBytesPair = signFile(inputStream, dataSize, true,
+ fsvTreeOffset, ownerID);
+ // 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, ownerID);
+
+ // 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(Zip zip) throws HapFormatException {
+ long dataSize = 0L;
+ for (ZipEntry entry : zip.getZipEntries()) {
+ ZipEntryHeader zipEntryHeader = entry.getZipEntryData().getZipEntryHeader();
+ if (FileUtils.isRunnableFile(zipEntryHeader.getFileName())
+ && zipEntryHeader.getMethod() == Zip.FILE_UNCOMPRESS_METHOD_FLAG) {
+ continue;
+ }
+ // if the first file is not uncompressed abc or so, set dataSize to zero
+ if (entry.getCentralDirectory().getOffset() == 0) {
+ break;
+ }
+ // the first entry which is not abc/so/an is found, return its data offset
+ dataSize = entry.getCentralDirectory().getOffset() + ZipEntryHeader.HEADER_LENGTH
+ + zipEntryHeader.getFileNameLength() + zipEntryHeader.getExtraLength();
+ 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, String ownerID) throws IOException, FsVerityDigestException,
+ CodeSignException {
+ // 'an' libs are always signed
+ extractedNativeLibSuffixs.add(NATIVE_LIB_AN_SUFFIX);
+ // 'so' libs are always signed
+ extractedNativeLibSuffixs.add(NATIVE_LIB_SO_SUFFIX);
+
+ // sign native files
+ try (JarFile inputJar = new JarFile(input, false)) {
+ List entryNames = getNativeEntriesFromHap(inputJar);
+ if (entryNames.isEmpty()) {
+ LOGGER.info("No native libs.");
+ return;
+ }
+ List> nativeLibInfoList = signFilesFromJar(entryNames, inputJar, ownerID);
+ // 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) {
+ if (StringUtils.isEmpty(entryName)) {
+ return false;
+ }
+ if (entryName.endsWith(NATIVE_LIB_AN_SUFFIX)) {
+ return true;
+ }
+ if (extractedNativeLibSuffixs.contains(NATIVE_LIB_SO_SUFFIX)) {
+ Pattern pattern = FileUtils.SUFFIX_REGEX_MAP.get("so");
+ Matcher matcher = pattern.matcher(entryName);
+ if (matcher.find()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sign specific entries in a hap
+ *
+ * @param entryNames list of entries which need to be signed
+ * @param hap input hap
+ * @param ownerID app-id in signature to identify
+ * @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, String ownerID)
+ 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, ownerID);
+ 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
+ * @param ownerID app-id in signature to identify
+ * @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, String ownerID) throws FsVerityDigestException, CodeSignException {
+ FsVerityGenerator fsVerityGenerator = new FsVerityGenerator();
+ fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset);
+ byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest();
+ byte[] signature = generateSignature(fsVerityDigest, ownerID);
+ 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, String ownerID) throws CodeSignException {
+ // signConfig is created by SignerFactory
+ if ((signConfig.getSigner() instanceof LocalSigner)) {
+ if (signConfig.getCertificates().isEmpty()) {
+ throw new CodeSignException("No certificates configured for sign");
+ }
+ }
+
+ BcSignedDataGenerator bcSignedDataGenerator = new BcSignedDataGenerator();
+ bcSignedDataGenerator.setOwnerID(ownerID);
+ return bcSignedDataGenerator.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);
+ long locHdrOffset = UnsignedDecimalUtil.getUnsignedInt(cdBuffer);
+ 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;
+ }
+}
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..3afd589518fd1478439a37f90fc67eacbd28eef5
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java
@@ -0,0 +1,321 @@
+/*
+ * 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.codesigning.utils.HapUtils;
+import com.ohos.hapsigntool.hap.entity.Pair;
+import com.ohos.hapsigntool.hap.exception.ProfileException;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerInformation;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+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 void checkOwnerID(byte[] signature, String profileOwnerID, String profileType)
+ throws CMSException, VerifyCodeSignException {
+ String ownerID = profileOwnerID;
+ // if profileType is debug, check the app-id in signature, should be null or DEBUG_LIB_ID
+ if ("debug".equals(profileType)) {
+ ownerID = "DEBUG_LIB_ID";
+ }
+
+ CMSSignedData cmsSignedData = new CMSSignedData(signature);
+ Collection signers = cmsSignedData.getSignerInfos().getSigners();
+ Collection results = null;
+ for (SignerInformation signer : signers) {
+ AttributeTable attrTable = signer.getSignedAttributes();
+ Attribute attr = attrTable.get(new ASN1ObjectIdentifier(BcSignedDataGenerator.SIGNER_OID));
+ // if app-id is null, if profileType is debug, it's ok.
+ if (attr == null) {
+ if ("debug".equals(profileType)) {
+ continue;
+ }
+ if (ownerID == null) {
+ continue;
+ } else {
+ throw new VerifyCodeSignException("app-identifier is not in the signature");
+ }
+ }
+ if (ownerID == null) {
+ throw new VerifyCodeSignException("app-identifier in profile is null, but is not null in signature");
+ }
+ // if app-id in signature exists, it should be equal to the app-id in profile.
+ String resultOwnerID = attr.getAttrValues().getObjectAt(0).toString();
+ if (!ownerID.equals(resultOwnerID)) {
+ throw new VerifyCodeSignException("app-identifier in signature is invalid");
+ }
+ }
+ }
+
+ /**
+ * 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.
+ * @param profileContent profile json string
+ * @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
+ * @throws ProfileException if verify profile failed
+ */
+ public static boolean verifyElf(File file, long offset, long length, String fileFormat, String profileContent)
+ throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException, ProfileException {
+ 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);
+ }
+ // 2) verify file data
+ try (FileInputStream signedElf = new FileInputStream(file)) {
+ int paddingSize = ElfSignBlock.computeMerkleTreePaddingLength(offset);
+ byte[] merkleTreeWithPadding = elfSignBlock.getMerkleTreeWithPadding();
+ byte[] merkleTree = Arrays.copyOfRange(merkleTreeWithPadding, paddingSize, merkleTreeWithPadding.length);
+ verifySingleFile(signedElf, elfSignBlock.getDataSize(), elfSignBlock.getSignature(),
+ elfSignBlock.getTreeOffset(), merkleTree);
+ }
+ if (profileContent != null) {
+ Pair pairResult = HapUtils.parseAppIdentifier(profileContent);
+ checkOwnerID(elfSignBlock.getSignature(), pairResult.getFirst(), pairResult.getSecond());
+ }
+ 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.
+ * @param profileContent profile of the hap
+ * @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
+ * @throws ProfileException profile of the hap failed
+ */
+ public static boolean verifyHap(File file, long offset, long length, String fileFormat, String profileContent)
+ throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException, ProfileException {
+ if (!CodeSigning.SUPPORT_FILE_FORM.contains(fileFormat)) {
+ LOGGER.info("Not hap or hsp file, skip code signing verify");
+ return true;
+ }
+ Pair pairResult = HapUtils.parseAppIdentifier(profileContent);
+
+ 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));
+ checkOwnerID(signature, pairResult.getFirst(), pairResult.getSecond());
+ }
+ // 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);
+ checkOwnerID(entrySig, pairResult.getFirst(), pairResult.getSecond());
+ }
+ }
+ 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());
+ }
+}
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..6d92964a6acc31f27f53044e463949cd98b616b5
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java
@@ -0,0 +1,183 @@
+/*
+ * 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 com.google.gson.JsonSyntaxException;
+import com.ohos.hapsigntool.hap.entity.Pair;
+import com.ohos.hapsigntool.hap.exception.ProfileException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * utility for check hap configs
+ *
+ * @since 2023/06/05
+ */
+public class HapUtils {
+ private static final Logger LOGGER = LogManager.getLogger(HapUtils.class);
+
+ private static final String COMPRESS_NATIVE_LIBS_OPTION = "compressNativeLibs";
+
+ private static final List HAP_CONFIG_FILES = new ArrayList<>();
+
+ private static final String HAP_FA_CONFIG_JSON_FILE = "config.json";
+
+ private static final String HAP_STAGE_MODULE_JSON_FILE = "module.json";
+
+ private static final String HAP_DEBUG_OWNER_ID = "DEBUG_LIB_ID";
+
+ private static final int MAX_APP_ID_LEN = 32; // max app-identifier in profile
+
+ 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;
+ }
+
+ /**
+ * get app-id from profile
+ *
+ * @param profileContent the content of profile
+ * @return string value of app-id
+ * @throws ProfileException profile is invalid
+ */
+ public static String getAppIdentifier(String profileContent) throws ProfileException {
+ Pair resultPair = parseAppIdentifier(profileContent);
+ String ownerID = resultPair.getFirst();
+ String profileType = resultPair.getSecond();
+ if ("debug".equals(profileType)) {
+ return HAP_DEBUG_OWNER_ID;
+ } else if ("release".equals(profileType)) {
+ return ownerID;
+ } else {
+ throw new ProfileException("unsupported profile type");
+ }
+ }
+
+ /**
+ * parse app-id and profileType from profile
+ *
+ * @param profileContent the content of profile
+ * @return Pair value of app-id and profileType
+ * @throws ProfileException profile is invalid
+ */
+ public static Pair parseAppIdentifier(String profileContent) throws ProfileException {
+ String ownerID = null;
+ String profileType = null;
+ try {
+ JsonElement parser = JsonParser.parseString(profileContent);
+ JsonObject profileJson = parser.getAsJsonObject();
+ String profileTypeKey = "type";
+ if (!profileJson.has(profileTypeKey)) {
+ throw new ProfileException("profile has no type key");
+ }
+
+ profileType = profileJson.get(profileTypeKey).getAsString();
+ if (profileType == null || profileType.length() == 0) {
+ throw new ProfileException("Get profile type error");
+ }
+
+ String appIdentifier = "app-identifier";
+ String buildInfoMember = "bundle-info";
+ JsonObject buildInfoObject = profileJson.getAsJsonObject(buildInfoMember);
+ if (buildInfoObject == null) {
+ throw new ProfileException("can not find bundle-info");
+ }
+ if (buildInfoObject.has(appIdentifier)) {
+ JsonElement ownerIDElement = buildInfoObject.get(appIdentifier);
+ if (!ownerIDElement.getAsJsonPrimitive().isString()) {
+ throw new ProfileException("value of app-identifier is not string");
+ }
+ ownerID = ownerIDElement.getAsString();
+ if (ownerID.isEmpty() || ownerID.length() > MAX_APP_ID_LEN) {
+ throw new ProfileException("app-id length in profile is invalid");
+ }
+
+ }
+ } catch (JsonSyntaxException | UnsupportedOperationException e) {
+ LOGGER.error(e.getMessage());
+ throw new ProfileException("profile json is invalid");
+ }
+ return Pair.create(ownerID, profileType);
+ }
+}
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/error/ERROR.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ERROR.java
index 286031eef28f9834822ad4c033407b001f43f3fd..2f2627faa8bcc285f2e648dde255108359461151 100644
--- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ERROR.java
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ERROR.java
@@ -88,7 +88,12 @@ public enum ERROR {
/**
* Enum constant IO_CSR_ERROR.
*/
- IO_CSR_ERROR(118);
+ IO_CSR_ERROR(118),
+
+ /**
+ * Enum constant ZIP_ERROR.
+ */
+ ZIP_ERROR(119);
/**
* Field errorCode.
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/VerifyException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/VerifyException.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb204b53f01dafcae06eaa3887ba8040a4638b79
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/VerifyException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.error;
+
+/**
+ * verify exception.
+ *
+ * @since 2023/08/26
+ */
+public class VerifyException extends Exception {
+ /**
+ * Create VerifyException with params.
+ *
+ * @param message Error msg to throw
+ */
+ public VerifyException(String message) {
+ super(message);
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ZipException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ZipException.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ad0dfb29d8ae3c5ce6463094d3064dcebfc02ed
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ZipException.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.error;
+
+import java.io.IOException;
+
+/**
+ * Zip exception for programs.
+ *
+ * @since 2023/12/07
+ */
+public class ZipException extends IOException {
+ /**
+ * new ZipException
+ *
+ * @param message exception message
+ */
+ public ZipException(String message) {
+ super(message);
+ }
+
+ /**
+ * new ZipException
+ *
+ * @param message exception message
+ * @param e exception
+ */
+ public ZipException(String message, Exception e) {
+ super(message, e);
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/ElfBlockData.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/ElfBlockData.java
new file mode 100644
index 0000000000000000000000000000000000000000..b8cdd1ad6ed0b53ae902d9485f8235f7533ac6dc
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/ElfBlockData.java
@@ -0,0 +1,47 @@
+/*
+ * 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
+ *
+ * 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;
+
+/**
+ * Elf block info
+ *
+ * @since 2023/11/20
+ */
+public class ElfBlockData {
+ int blockNum;
+ int blockStart;
+
+ public ElfBlockData(int blockNum, int blockStart) {
+ this.blockNum = blockNum;
+ this.blockStart = blockStart;
+ }
+
+ public int getBlockNum() {
+ return blockNum;
+ }
+
+ public void setBlockNum(int blockNum) {
+ this.blockNum = blockNum;
+ }
+
+ public int getBlockStart() {
+ return blockStart;
+ }
+
+ public void setBlockStart(int blockStart) {
+ this.blockStart = blockStart;
+ }
+}
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..c790e885625a4e9d09e75bd74bd77a96849f5899 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,11 +15,24 @@
package com.ohos.hapsigntool.hap.entity;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
/**
* define class of hap signature sub-block head
+ *
+ * @since 2023/11/07
*/
public class HwBlockHead {
- private static final int BLOCK_LEN = 8; // current block length
+ /**
+ * bin file sign block length is 8 byte
+ */
+ public static final int BLOCK_LEN = 8;
+
+ /**
+ * elf file sign block length is 12 byte
+ */
+ public static final int ELF_BLOCK_LEN = 12;
private static final int BIT_SIZE = 8;
@@ -27,15 +40,26 @@ public class HwBlockHead {
private static final int TRIPLE_BIT_SIZE = 24;
+ /**
+ * get bin block length
+ *
+ * @return return bin block length
+ */
public static int getBlockLen() {
return BLOCK_LEN;
}
- private HwBlockHead() {
+ /**
+ * get elf block length
+ *
+ * @return return elf block length
+ */
+ public static int getElfBlockLen() {
+ return ELF_BLOCK_LEN;
}
/**
- * get serialization of HwBlockHead
+ * get serialization of file type bin BlockHead
*
* @param type type of signature block
* @param tag tags of signature block
@@ -55,4 +79,22 @@ public class HwBlockHead {
(byte) (offset & 0xff)
};
}
+
+ /**
+ * get serialization of file type elf BlockHead
+ *
+ * @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, int length, int offset) {
+ ByteBuffer bf = ByteBuffer.allocate(HwBlockHead.ELF_BLOCK_LEN).order(ByteOrder.LITTLE_ENDIAN);
+ bf.putChar(type);
+ bf.putChar(tag);
+ bf.putInt(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..46ce01fe83e56c0f74f4367349864afdf3f538c9 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
@@ -30,14 +32,29 @@ public class HwSignHead {
*/
public static final int SIGN_HEAD_LEN = 32;
- 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
+ /**
+ * sign hap magic string 16Bytes-Magic
+ */
+ public static final char[] MAGIC = "hw signed app ".toCharArray();
+
+ /**
+ * sign elf magic string 16Bytes-Magic
+ */
+ public static final char[] ELF_MAGIC = "elf sign block ".toCharArray();
+
+ /**
+ * sign block version 4-Bytes, version is 1.0.0.0
+ */
+ public static final char[] VERSION = "1000".toCharArray();
+
private 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];
/**
- * get serialization of HwSignHead
+ * get serialization of HwSignHead file type of bin
*
* @param subBlockSize the total size of all sub-blocks
* @return Byte array after serialization of HwSignHead
@@ -72,4 +89,27 @@ public class HwSignHead {
}
return signHead;
}
+
+ /**
+ * get serialization of HwSignHead file type of elf
+ *
+ * @param subBlockSize the total size of all sub-blocks
+ * @param subBlockNum the sign block num
+ * @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 : ELF_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..2ad9874f082283de6f42c1ab2bee9bd95cf96c27
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignBlockData.java
@@ -0,0 +1,94 @@
+/*
+ * 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.hap.entity;
+
+import com.ohos.hapsigntool.utils.FileUtils;
+
+/**
+ * sign block data
+ *
+ * @since 2023-11-07
+ */
+public class SignBlockData {
+ private char type;
+ private byte[] blockHead;
+ private byte[] signData;
+ private String signFile;
+ private long len;
+ private boolean isByte;
+
+ 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;
+ }
+
+ 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 isByte) {
+ this.isByte = isByte;
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SigningBlock.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SigningBlock.java
index 0794f0d781f781bdeafbf385b88bb149c2f34728..663dd56ee4a81f39bc2fa96a21ee394c43de0cea 100644
--- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SigningBlock.java
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SigningBlock.java
@@ -25,6 +25,14 @@ public class SigningBlock {
private int length;
private byte[] value;
+ private int offset;
+
+ /**
+ * Init Signing Block type and value
+ *
+ * @param type signing type
+ * @param value signing value
+ */
public SigningBlock(int type, byte[] value) {
super();
this.type = type;
@@ -32,6 +40,21 @@ public class SigningBlock {
this.value = value;
}
+ /**
+ * Init Signing Block type and value
+ *
+ * @param type signing type
+ * @param value signing value
+ * @param offset signing block offset
+ */
+ public SigningBlock(int type, byte[] value, int offset) {
+ super();
+ this.type = type;
+ this.length = value.length;
+ this.value = value;
+ this.offset = offset;
+ }
+
public int getType() {
return type;
}
@@ -43,4 +66,8 @@ public class SigningBlock {
public byte[] getValue() {
return value;
}
+
+ public int getOffset() {
+ return offset;
+ }
}
\ No newline at end of file
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..b0e1fa3fb6a0851dddaf2ce08fabb213a01b8cbf 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
@@ -21,6 +21,7 @@ import com.ohos.hapsigntool.hap.exception.MissingParamsException;
import com.ohos.hapsigntool.utils.FileUtils;
import com.ohos.hapsigntool.utils.ParamConstants;
import com.ohos.hapsigntool.utils.ParamProcessUtil;
+
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -87,9 +88,9 @@ public class LocalJKSSignProvider extends SignProvider {
public void checkParams(Options options) throws InvalidParamsException, MissingParamsException {
super.checkParams(options);
String[] paramFileds = {
- ParamConstants.PARAM_LOCAL_JKS_KEYSTORE,
- ParamConstants.PARAM_LOCAL_JKS_KEYSTORE_CODE,
- ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE
+ ParamConstants.PARAM_LOCAL_JKS_KEYSTORE,
+ ParamConstants.PARAM_LOCAL_JKS_KEYSTORE_CODE,
+ ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE
};
Set paramSet = ParamProcessUtil.initParamField(paramFileds);
@@ -103,7 +104,7 @@ public class LocalJKSSignProvider extends SignProvider {
}
}
}
-
+ checkSignCode();
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..3d0a6d494dded3cc29ee61699b6a364f122efb62 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
@@ -15,21 +15,28 @@
package com.ohos.hapsigntool.hap.provider;
+import static com.ohos.hapsigntool.codesigning.sign.CodeSigning.SUPPORT_BIN_FILE_FORM;
+import static com.ohos.hapsigntool.codesigning.sign.CodeSigning.SUPPORT_FILE_FORM;
+
import com.google.gson.JsonElement;
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;
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;
@@ -44,6 +51,7 @@ import com.ohos.hapsigntool.utils.StringUtils;
import com.ohos.hapsigntool.zip.ByteBufferZipDataInput;
import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput;
import com.ohos.hapsigntool.zip.RandomAccessFileZipDataOutput;
+import com.ohos.hapsigntool.zip.Zip;
import com.ohos.hapsigntool.zip.ZipDataInput;
import com.ohos.hapsigntool.zip.ZipDataOutput;
import com.ohos.hapsigntool.zip.ZipFileInfo;
@@ -56,10 +64,10 @@ import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.File;
-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,11 +81,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.TimeZone;
import java.util.Optional;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
+import java.util.Set;
/**
* Sign provider super class
@@ -101,6 +106,7 @@ public abstract class SignProvider {
VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA256_RSA_MGF1);
VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA384_RSA_MGF1);
VALID_SIGN_ALG_NAME.add(ParamConstants.HAP_SIG_ALGORITHM_SHA512_RSA_MGF1);
+ Security.addProvider(new BouncyCastleProvider());
}
static {
@@ -119,6 +125,8 @@ public abstract class SignProvider {
*/
protected Map signParams = new HashMap();
+ private String profileContent;
+
/**
* Read data of optional blocks from file user inputted.
*
@@ -219,7 +227,7 @@ public abstract class SignProvider {
List signatureAlgorithms = new ArrayList();
signatureAlgorithms.add(
- ParamProcessUtil.getSignatureAlgorithm(this.signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG)));
+ ParamProcessUtil.getSignatureAlgorithm(this.signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG)));
signerConfig.setSignatureAlgorithms(signatureAlgorithms);
if (!crl.equals(Optional.empty())) {
@@ -235,7 +243,6 @@ public abstract class SignProvider {
* @return true, if sign successfully.
*/
public boolean signBin(Options options) {
- Security.addProvider(new BouncyCastleProvider());
List publicCert = null;
SignerConfig signerConfig;
try {
@@ -261,6 +268,47 @@ 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) {
+ List publicCert = null;
+ SignerConfig signerConfig = null;
+ 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;
+ }
+
+ if (ParamConstants.ProfileSignFlag.DISABLE_SIGN_CODE.getSignFlag().equals(
+ signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED))) {
+ LOGGER.error("hap-sign-tool: error: Sign elf can not use unsigned profile.");
+ return false;
+ }
+
+ if (profileContent != null) {
+ signParams.put(ParamConstants.PARAM_PROFILE_JSON_CONTENT, profileContent);
+ }
+ /* 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
*
@@ -268,7 +316,6 @@ public abstract class SignProvider {
* @return true, if sign successfully
*/
public boolean sign(Options options) {
- Security.addProvider(new BouncyCastleProvider());
List publicCerts = null;
File output = null;
File tmpOutput = null;
@@ -279,15 +326,16 @@ 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(input);
if (input.getCanonicalPath().equals(output.getCanonicalPath())) {
- tmpOutput = File.createTempFile("signedHap", ".hap");
+ tmpOutput = File.createTempFile("signedHap", "." + suffix);
isPathOverlap = true;
} else {
tmpOutput = output;
}
// copy file and Alignment
int alignment = Integer.parseInt(signParams.get(ParamConstants.PARAM_BASIC_ALIGNMENT));
- copyFileAndAlignment(input, tmpOutput, alignment);
+ Zip zip = copyFileAndAlignment(input, tmpOutput, alignment);
// generate sign block and output signedHap
try (RandomAccessFile outputHap = new RandomAccessFile(tmpOutput, "rw")) {
ZipDataInput outputHapIn = new RandomAccessFileZipDataInput(outputHap);
@@ -295,9 +343,8 @@ public abstract class SignProvider {
long centralDirectoryOffset = zipInfo.getCentralDirectoryOffset();
ZipDataInput beforeCentralDir = outputHapIn.slice(0, centralDirectoryOffset);
ByteBuffer centralDirBuffer =
- outputHapIn.createByteBuffer(centralDirectoryOffset, zipInfo.getCentralDirectorySize());
+ outputHapIn.createByteBuffer(centralDirectoryOffset, zipInfo.getCentralDirectorySize());
ZipDataInput centralDirectory = new ByteBufferZipDataInput(centralDirBuffer);
-
ByteBuffer eocdBuffer = zipInfo.getEocd();
ZipDataInput eocd = new ByteBufferZipDataInput(eocdBuffer);
@@ -306,6 +353,7 @@ 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, zip);
byte[] signingBlock = SignHap.sign(contents, signerConfig, optionalBlocks);
long newCentralDirectoryOffset = centralDirectoryOffset + signingBlock.length;
ZipUtils.setCentralDirectoryOffset(eocdBuffer, newCentralDirectoryOffset);
@@ -314,8 +362,8 @@ public abstract class SignProvider {
outputSignedFile(outputHap, centralDirectoryOffset, signingBlock, centralDirectory, eocdBuffer);
isRet = true;
}
- } catch (IOException | InvalidKeyException | HapFormatException | MissingParamsException
- | InvalidParamsException | ProfileException | NumberFormatException | CustomException e) {
+ } catch (FsVerityDigestException | InvalidKeyException | HapFormatException | MissingParamsException
+|InvalidParamsException |ProfileException |NumberFormatException |CustomException |IOException |CodeSignException e) {
printErrorLogWithoutStack(e);
} catch (SignatureException e) {
printErrorLog(e);
@@ -323,6 +371,60 @@ 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
+ * @param zip zip
+ * @throws FsVerityDigestException FsVerity digest on error
+ * @throws CodeSignException code sign on error
+ * @throws IOException IO error
+ * @throws HapFormatException hap format on error
+ * @throws ProfileException profile of app is invalid
+ */
+ private void appendCodeSignBlock(SignerConfig signerConfig, File tmpOutput, String suffix,
+ long centralDirectoryOffset, Zip zip)
+ throws FsVerityDigestException, CodeSignException, IOException, HapFormatException, ProfileException {
+ if (!SUPPORT_BIN_FILE_FORM.contains(suffix) && !SUPPORT_FILE_FORM.contains(suffix)) {
+ LOGGER.warn("no need to sign code for :" + suffix);
+ return;
+ }
+ if (signParams.get(ParamConstants.PARAM_SIGN_CODE)
+ .equals(ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag())) {
+ // 4 means hap format occupy 4 byte storage location,2 means optional blocks reserve 2 storage location
+ long codeSignOffset = 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, profileContent, zip);
+ 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((int) 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 output file
+ * @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
*
@@ -341,6 +443,10 @@ public abstract class SignProvider {
publicCerts = getPublicCerts();
// 3. load optionalBlocks
loadOptionalBlocks();
+ if ("elf".equals(options.getString(ParamConstants.PARAM_IN_FORM))
+ && StringUtils.isEmpty(options.getString(ParamConstants.PARAM_BASIC_PROFILE))) {
+ return publicCerts;
+ }
checkProfileValid(publicCerts);
return publicCerts;
}
@@ -353,9 +459,9 @@ public abstract class SignProvider {
outputHapOut.write(eocdBuffer);
}
- private boolean doAfterSign(boolean isSuccess, boolean pathOverlap, File tmpOutput, File output) {
+ private boolean doAfterSign(boolean isSuccess, boolean isPathOverlap, File tmpOutput, File output) {
boolean isRet = isSuccess;
- if (isRet && pathOverlap) {
+ if (isRet && isPathOverlap) {
try {
Files.move(tmpOutput.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
@@ -390,18 +496,20 @@ public abstract class SignProvider {
* @param input file input
* @param tmpOutput file tmpOutput
* @param alignment alignment
- * @throws IOException io error
+ * @return zip zip
+ * @throws IOException io error
+ * @throws HapFormatException hap format error
*/
- private void copyFileAndAlignment(File input, File tmpOutput, int alignment) throws IOException {
- 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);
- }
+ private Zip copyFileAndAlignment(File input, File tmpOutput, int alignment)
+ throws IOException, HapFormatException {
+ Zip zip = new Zip(input);
+ zip.alignment(alignment);
+ zip.removeSignBlock();
+ long start = System.currentTimeMillis();
+ zip.toFile(tmpOutput.getCanonicalPath());
+ long end = System.currentTimeMillis();
+ LOGGER.debug("zip to file use {} ms", end - start);
+ return zip;
}
/**
@@ -492,9 +600,8 @@ public abstract class SignProvider {
private void checkProfileValid(List inputCerts) throws ProfileException {
try {
byte[] profile = findProfileFromOptionalBlocks();
- boolean isProfileWithoutSign = ParamConstants.ProfileSignFlag.UNSIGNED_PROFILE.getSignFlag().equals(
+ boolean isProfileWithoutSign = ParamConstants.ProfileSignFlag.DISABLE_SIGN_CODE.getSignFlag().equals(
signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED));
- String content;
if (!isProfileWithoutSign) {
CMSSignedData cmsSignedData = new CMSSignedData(profile);
boolean isVerify = VerifyUtils.verifyCmsSignedData(cmsSignedData);
@@ -505,11 +612,11 @@ public abstract class SignProvider {
if (!(contentObj instanceof byte[])) {
throw new ProfileException("Check profile failed, signed profile content is not byte array!");
}
- content = new String((byte[]) contentObj, StandardCharsets.UTF_8);
+ profileContent = new String((byte[]) contentObj, StandardCharsets.UTF_8);
} else {
- content = new String(profile, StandardCharsets.UTF_8);
+ profileContent = new String(profile, StandardCharsets.UTF_8);
}
- JsonElement parser = JsonParser.parseString(content);
+ JsonElement parser = JsonParser.parseString(profileContent);
JsonObject profileJson = parser.getAsJsonObject();
checkProfileInfo(profileJson, inputCerts);
} catch (CMSException e) {
@@ -536,7 +643,7 @@ public abstract class SignProvider {
throw new ProfileException("Unsupported profile type!");
}
if (!inputCerts.isEmpty() && !checkInputCertMatchWithProfile(inputCerts.get(0), certInProfile)) {
- throw new ProfileException("input certificates do not match with profile!");
+ throw new ProfileException("input certificates do not match with profile!");
}
String cn = getCertificateCN(certInProfile);
LOGGER.info("certificate in profile: {}", cn);
@@ -565,18 +672,20 @@ public abstract class SignProvider {
*/
public void checkParams(Options options) throws MissingParamsException, InvalidParamsException {
String[] paramFileds = {
- ParamConstants.PARAM_BASIC_ALIGNMENT,
- ParamConstants.PARAM_BASIC_SIGANTURE_ALG,
- ParamConstants.PARAM_BASIC_INPUT_FILE,
- ParamConstants.PARAM_BASIC_OUTPUT_FILE,
- ParamConstants.PARAM_BASIC_PRIVATE_KEY,
- ParamConstants.PARAM_BASIC_PROFILE,
- ParamConstants.PARAM_BASIC_PROOF,
- ParamConstants.PARAM_BASIC_PROPERTY,
- ParamConstants.PARAM_REMOTE_SERVER,
- ParamConstants.PARAM_BASIC_PROFILE_SIGNED,
- ParamConstants.PARAM_LOCAL_PUBLIC_CERT,
- ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION
+ ParamConstants.PARAM_BASIC_ALIGNMENT,
+ ParamConstants.PARAM_BASIC_SIGANTURE_ALG,
+ ParamConstants.PARAM_BASIC_INPUT_FILE,
+ ParamConstants.PARAM_BASIC_OUTPUT_FILE,
+ ParamConstants.PARAM_BASIC_PRIVATE_KEY,
+ ParamConstants.PARAM_BASIC_PROFILE,
+ ParamConstants.PARAM_BASIC_PROOF,
+ ParamConstants.PARAM_BASIC_PROPERTY,
+ ParamConstants.PARAM_REMOTE_SERVER,
+ ParamConstants.PARAM_BASIC_PROFILE_SIGNED,
+ ParamConstants.PARAM_LOCAL_PUBLIC_CERT,
+ ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION,
+ ParamConstants.PARAM_SIGN_CODE,
+ ParamConstants.PARAM_IN_FORM
};
Set paramSet = ParamProcessUtil.initParamField(paramFileds);
@@ -588,10 +697,29 @@ public abstract class SignProvider {
if (!signParams.containsKey(ParamConstants.PARAM_BASIC_PROFILE_SIGNED)) {
signParams.put(ParamConstants.PARAM_BASIC_PROFILE_SIGNED, "1");
}
+ checkSignCode();
checkSignatureAlg();
checkSignAlignment();
}
+ /**
+ * Check code sign, if param do not have code sign default "1".
+ *
+ * @throws InvalidParamsException invalid param
+ */
+ protected void checkSignCode() throws InvalidParamsException {
+ if (!signParams.containsKey(ParamConstants.PARAM_SIGN_CODE)) {
+ signParams.put(ParamConstants.PARAM_SIGN_CODE,
+ ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag());
+ return;
+ }
+ String codeSign = signParams.get(ParamConstants.PARAM_SIGN_CODE);
+ if (!codeSign.equals(ParamConstants.SignCodeFlag.ENABLE_SIGN_CODE.getSignCodeFlag())
+ && !codeSign.equals(ParamConstants.SignCodeFlag.DISABLE_SIGN_CODE.getSignCodeFlag())) {
+ throw new InvalidParamsException("Invalid parameter: " + ParamConstants.PARAM_SIGN_CODE);
+ }
+ }
+
/**
* 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..670eea6c335fa89964638c1aced1a3217cebd910 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
@@ -60,6 +60,10 @@ public class SignBin {
public static boolean sign(SignerConfig signerConfig, Map signParams) {
boolean result = false;
/* 1. Make block head, write to output file. */
+ String codesign = signParams.get(ParamConstants.PARAM_SIGN_CODE);
+ if (ParamConstants.ProfileSignFlag.ENABLE_SIGN_CODE.getSignFlag().equals(codesign)) {
+ LOGGER.warn("can not sign bin with codesign");
+ }
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);
@@ -172,7 +176,7 @@ public class SignBin {
}
HwSignHead signHeadData = new HwSignHead();
byte[] signHeadByte = signHeadData.getSignHead((int) size);
- if (signHeadByte == null) {
+ if (signHeadByte == null || signHeadByte.length == 0) {
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..8f990e1d55fa5cda222671cd6adc3e37dd9b3fcd
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java
@@ -0,0 +1,280 @@
+/*
+ * 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.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.SignatureBlockTags;
+import com.ohos.hapsigntool.hap.entity.SignatureBlockTypes;
+import com.ohos.hapsigntool.hap.exception.HapFormatException;
+import com.ohos.hapsigntool.hap.exception.ProfileException;
+import com.ohos.hapsigntool.utils.FileUtils;
+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.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * elf file Signature signer.
+ *
+ * @since 2023/11/21
+ */
+public class SignElf {
+ /**
+ * codesign sign block type
+ */
+ public static final char CODESIGN_BLOCK_TYPE = 3;
+
+ private static final Logger LOGGER = LogManager.getLogger(SignElf.class);
+
+ private static final String CODESIGN_OFF = "0";
+
+ private static int blockNum = 0;
+
+ private static final int PAGE_SIZE = 4096;
+
+ private static final int FILE_BUFFER_BLOCK = 16384;
+
+ /**
+ * Constructor of Method
+ */
+ private SignElf() {
+ }
+
+ /**
+ * Sign the elf file.
+ *
+ * @param signerConfig Config of the elf file to be signed.
+ * @param signParams The input parameters of sign elf.
+ * @return true if sign successfully; false otherwise.
+ */
+ public static boolean sign(SignerConfig signerConfig, Map signParams) {
+ boolean isSuccess = false;
+ /* 1. Make block head, write to output file. */
+ String inputFile = signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE);
+ String tmpFile = alignFileBy4kBytes(inputFile);
+ if (tmpFile == null) {
+ LOGGER.error("copy input File failed");
+ return isSuccess;
+ }
+ String outputFile = signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE);
+ String profileSigned = signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED);
+ if (!writeBlockDataToFile(signerConfig, tmpFile, outputFile, profileSigned, signParams)) {
+ LOGGER.error("The block head data made failed.`");
+ ParamProcessUtil.delDir(new File(outputFile));
+ return isSuccess;
+ }
+ LOGGER.info("The block head data made success.");
+
+ /* 2. Make sign data, and write to output file */
+ if (!writeSignHeadDataToOutputFile(tmpFile, outputFile, blockNum)) {
+ LOGGER.error("The sign head data made failed.");
+ ParamProcessUtil.delDir(new File(outputFile));
+ } else {
+ isSuccess = true;
+ }
+ return isSuccess;
+ }
+
+ private static String alignFileBy4kBytes(String inputFile) {
+ String tmp = "tmpFile" + new Date().getTime();
+ File tmpFile = new File(tmp);
+ try {
+ tmpFile.createNewFile();
+ } catch (IOException e) {
+ LOGGER.error("create tmp file Failed");
+ return null;
+ }
+ try (FileOutputStream output = new FileOutputStream(tmpFile);
+ FileInputStream input = new FileInputStream(inputFile)) {
+ byte[] buffer = new byte[FILE_BUFFER_BLOCK];
+ int read;
+ while ((read = input.read(buffer)) != FileUtils.FILE_END) {
+ output.write(buffer, 0, read);
+ }
+
+ long addLength = PAGE_SIZE - (tmpFile.length() % PAGE_SIZE);
+ if (isLongOverflowInteger(addLength)) {
+ LOGGER.error("File alignment error");
+ return null;
+ }
+ byte[] bytes = new byte[(int) addLength];
+ java.util.Arrays.fill(bytes, (byte) 0);
+ FileUtils.writeByteToOutFile(bytes, tmp);
+ } catch (IOException e) {
+ LOGGER.error("copy inFile Failed");
+ return null;
+ }
+ return tmp;
+ }
+
+ private static boolean writeBlockDataToFile(SignerConfig signerConfig,
+ String inputFile, String outputFile, String profileSigned, Map signParams) {
+ try {
+ String profileFile = signParams.get(ParamConstants.PARAM_BASIC_PROFILE);
+
+ List signDataList = new ArrayList<>();
+
+ long binFileLen = FileUtils.getFileLen(inputFile);
+ if (binFileLen == -1) {
+ LOGGER.error("file length is invalid, elf 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() + 1; // other sign block num + codesign block 1
+ 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 | ProfileException e) {
+ LOGGER.error("codesign failed.", e);
+ return false;
+ }
+ }
+
+ 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 isSuccess;
+ if (signBlockData.isByte()) {
+ isSuccess = FileUtils.writeByteToDos(signBlockData.getSignData(), dataOutputStream);
+ } else {
+ isSuccess = FileUtils.writeFileToDos(signBlockData.getSignFile(), dataOutputStream);
+ }
+
+ if (!isSuccess) {
+ LOGGER.error("Failed to write Block Data to output file: " + outputFile);
+ throw new IOException();
+ }
+ }
+ } catch (IOException e) {
+ LOGGER.error("writeSignedElf failed.", e);
+ return false;
+ }
+ return true;
+ }
+
+ private static void generateSignBlockHead(List signDataList)
+ throws IOException {
+ long offset = (long) HwBlockHead.getElfBlockLen() * signDataList.size();
+
+ for (int i = 0; i < signDataList.size(); i++) {
+ SignBlockData signBlockData = signDataList.get(i);
+
+ signBlockData.setBlockHead(HwBlockHead.getBlockHeadLittleEndian(signBlockData.getType(),
+ SignatureBlockTags.DEFAULT, (int) 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, ProfileException {
+ if (CODESIGN_OFF.equals(signParams.get(ParamConstants.PARAM_SIGN_CODE))) {
+ return null;
+ }
+ CodeSigning codeSigning = new CodeSigning(signerConfig);
+ long offset = binFileLen + (long) HwBlockHead.getElfBlockLen() * blockNum;
+ String profileContent = signParams.get(ParamConstants.PARAM_PROFILE_JSON_CONTENT);
+ byte[] codesignData = codeSigning.getElfCodeSignBlock(new File(inputFile), offset,
+ signParams.get(ParamConstants.PARAM_IN_FORM), profileContent);
+ return new SignBlockData(codesignData, CODESIGN_BLOCK_TYPE);
+ }
+
+ 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 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..3ea5a842ae688887d7e2a1bee4c435ee5a45fe52 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,11 @@ 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.FileUtils;
import com.ohos.hapsigntool.utils.HapUtils;
+import com.ohos.hapsigntool.utils.StringUtils;
import com.ohos.hapsigntool.zip.ZipDataInput;
import java.io.IOException;
@@ -29,8 +32,6 @@ 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;
@@ -39,8 +40,10 @@ import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
+import java.util.stream.Collectors;
/**
+ *
* Hap Signature Scheme signer
*
* @since 2021/12/21
@@ -63,96 +66,168 @@ 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.
+ * @throws HapFormatException hap format 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 (!FileUtils.isRunnableFile(entryName)) {
+ 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 = FileUtils.isRunnableFile(name1);
+ boolean isSpecial2 = FileUtils.isRunnableFile(name2);
+ 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 < entryListNotStored.size(); index++) {
+ JarEntry inEntry = entryListNotStored.get(index);
+ if (inEntry == null || inEntry.getMethod() == JarEntry.STORED) {
continue;
}
- JarEntry outEntry = new JarEntry(name);
+ JarEntry outEntry = new JarEntry(inEntry.getName());
outEntry.setTime(timestamp);
out.putNextEntry(outEntry);
-
- try (InputStream data = in.getInputStream(inEntry);) {
- int num;
- while ((num = data.read(buffer)) > 0) {
- out.write(buffer, 0, num);
- }
- out.flush();
- }
+ writeOutputStream(in, out, inEntry, buffer);
}
}
@@ -161,13 +236,18 @@ public abstract class SignHap {
*
* @param entryName name of entry
* @param defaultAlignment default value of alignment.
+ * @param lastAlignmentEntryName lastAlignmentEntryName
* @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 (FileUtils.isRunnableFile(entryName)) {
return STORED_ENTRY_SO_ALIGNMENT;
}
return defaultAlignment;
@@ -178,7 +258,7 @@ public abstract class SignHap {
List optionalBlocks,
SignerConfig signerConfig,
ZipDataInput[] hapData)
- throws SignatureException {
+ throws SignatureException {
/**
* Compute digests of Hap contents
* Sign the digests and wrap the signature and signer info into the Hap Signing Block
@@ -186,7 +266,7 @@ public abstract class SignHap {
byte[] hapSignatureBytes = null;
try {
Map contentDigests =
- HapUtils.computeDigests(contentDigestAlgorithms, hapData, optionalBlocks);
+ HapUtils.computeDigests(contentDigestAlgorithms, hapData, optionalBlocks);
hapSignatureBytes = generateHapSigningBlock(signerConfig, contentDigests, optionalBlocks);
} catch (DigestException | IOException e) {
throw new SignatureException("Failed to compute digests of HAP", e);
@@ -204,35 +284,29 @@ public abstract class SignHap {
}
private static byte[] generateHapSigningBlock(byte[] hapSignatureSchemeBlock,
- List optionalBlocks, int compatibleVersion) {
+ List optionalBlocks, int compatibleVersion) {
// FORMAT:
// Proof-of-Rotation pairs(optional):
// uint32:type
// uint32:length
// uint32:offset
-
// Property pairs(optional):
// uint32:type
// uint32:length
// uint32:offset
-
// Profile capability pairs(optional):
// uint32:type
// uint32:length
// uint32:offset
-
// length bytes : app signing pairs
// uint32:type
// uint32:length
// uint32:offset
-
// repeated ID-value pairs(reserved extensions):
// length bytes : Proof-of-Rotation values
// length bytes : property values
// length bytes : profile capability values
// length bytes : signature schema values
-
- // uint32: block count
// uint64: size
// uint128: magic
// uint32: version
@@ -240,7 +314,6 @@ public abstract class SignHap {
for (SigningBlock optionalBlock : optionalBlocks) {
optionalBlockSize += optionalBlock.getLength();
}
-
long resultSize =
((OPTIONAL_TYPE_SIZE + OPTIONAL_LENGTH_SIZE + OPTIONAL_OFFSET_SIZE) * (optionalBlocks.size() + 1))
+ optionalBlockSize // optional pair
@@ -256,8 +329,8 @@ public abstract class SignHap {
result.order(ByteOrder.LITTLE_ENDIAN);
Map typeAndOffsetMap = new HashMap();
- int currentOffset = ((OPTIONAL_TYPE_SIZE + OPTIONAL_LENGTH_SIZE +
- OPTIONAL_OFFSET_SIZE) * (optionalBlocks.size() + 1));
+ int currentOffset = ((OPTIONAL_TYPE_SIZE + OPTIONAL_LENGTH_SIZE
+ + OPTIONAL_OFFSET_SIZE) * (optionalBlocks.size() + 1));
int currentOffsetInBlockValue = 0;
int blockValueSizes = (int) (optionalBlockSize + hapSignatureSchemeBlock.length);
byte[] blockValues = new byte[blockValueSizes];
@@ -274,20 +347,12 @@ public abstract class SignHap {
hapSignatureSchemeBlock, 0, blockValues, currentOffsetInBlockValue, hapSignatureSchemeBlock.length);
typeAndOffsetMap.put(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID, currentOffset);
- int offset = 0;
- for (SigningBlock optionalBlock : optionalBlocks) {
- result.putInt(optionalBlock.getType()); // type
- result.putInt(optionalBlock.getLength()); // length
- offset = typeAndOffsetMap.get(optionalBlock.getType());
- result.putInt(offset); // offset
- }
+ extractedResult(optionalBlocks, result, typeAndOffsetMap);
result.putInt(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID); // type
result.putInt(hapSignatureSchemeBlock.length); // length
- offset = typeAndOffsetMap.get(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID);
+ int offset = typeAndOffsetMap.get(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID);
result.putInt(offset); // offset
-
result.put(blockValues);
-
result.putInt(optionalBlocks.size() + 1); // Signing block count
result.putLong(resultSize); // length of hap signing block
result.put(HapUtils.getHapSigningBlockMagic(compatibleVersion)); // magic
@@ -295,6 +360,17 @@ public abstract class SignHap {
return result.array();
}
+ private static void extractedResult(List optionalBlocks, ByteBuffer result,
+ Map typeAndOffsetMap) {
+ int offset;
+ for (SigningBlock optionalBlock : optionalBlocks) {
+ result.putInt(optionalBlock.getType()); // type
+ result.putInt(optionalBlock.getLength()); // length
+ offset = typeAndOffsetMap.get(optionalBlock.getType());
+ result.putInt(offset); // offset
+ }
+ }
+
private static byte[] generateHapSignatureSchemeBlock(
SignerConfig signerConfig, Map contentDigests) throws SignatureException {
byte[] signerBlock = null;
@@ -310,7 +386,7 @@ public abstract class SignHap {
SignerConfig signerConfig, Map contentDigests) throws SignatureException {
String mode = signerConfig.getOptions().getString(Options.MODE);
if (!("remoteSign".equalsIgnoreCase(mode)) && signerConfig.getCertificates().isEmpty()) {
- throw new SignatureException("No certificates configured for signer");
+ throw new SignatureException("No certificates configured for signer");
}
List> digests =
@@ -342,7 +418,7 @@ public abstract class SignHap {
* @throws SignatureException if an error occurs when sign hap file.
*/
public static byte[] sign(ZipDataInput[] contents, SignerConfig signerConfig, List optionalBlocks)
- throws SignatureException {
+ throws SignatureException {
Set contentDigestAlgorithms = new HashSet();
for (SignatureAlgorithm signatureAlgorithm : signerConfig.getSignatureAlgorithms()) {
contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm());
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/HapVerify.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/HapVerify.java
index abfb465b46bd6e8263c11c1b473000eba997d5f6..a889fc9c67586920213c49af039563eb53fb7d38 100644
--- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/HapVerify.java
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/HapVerify.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
@@ -91,8 +91,17 @@ public class HapVerify {
private JcaX509CRLConverter crlConverter = new JcaX509CRLConverter();
- private boolean printCert;
+ private boolean isPrintCert;
+ /**
+ * Init Zip HapVerify
+ *
+ * @param beforeApkSigningBlock beforeApkSigningBlock
+ * @param signatureSchemeBlock signatureSchemeBlock
+ * @param centralDirectoryBlock centralDirectoryBlock
+ * @param eocd eocd
+ * @param optionalBlocks optionalBlocks
+ */
public HapVerify(
ZipDataInput beforeApkSigningBlock,
ByteBuffer signatureSchemeBlock,
@@ -106,6 +115,12 @@ public class HapVerify {
this.optionalBlocks = optionalBlocks;
}
+ /**
+ * init HapVerify
+ */
+ public HapVerify() {
+ }
+
/**
* Verify hap signature.
*
@@ -115,12 +130,22 @@ public class HapVerify {
return parserSigner(signatureSchemeBlock);
}
- public void setPrintCert(boolean printCert) {
- this.printCert = printCert;
+ /**
+ * Verify elf signature.
+ *
+ * @param profile profile byte
+ * @return verify result.
+ */
+ public VerifyResult verifyElfProfile(byte[] profile) {
+ return parserSigner(ByteBuffer.wrap(profile), false);
+ }
+
+ public void setIsPrintCert(boolean isPrintCert) {
+ this.isPrintCert = isPrintCert;
}
private boolean checkCRL(X509CRL crl, List certificates) {
- boolean ret = false;
+ boolean isRet = false;
for (X509Certificate cert : certificates) {
if (!crl.getIssuerDN().getName().equals(cert.getIssuerDN().getName())) {
continue;
@@ -129,12 +154,12 @@ public class HapVerify {
if (entry != null) {
LOGGER.info("cert(subject DN = {}) is revoked by crl (IssuerDN = {})",
cert.getSubjectDN().getName(), crl.getIssuerDN().getName());
- ret = false;
+ isRet = false;
break;
}
- ret = true;
+ isRet = true;
}
- return ret;
+ return isRet;
}
private boolean verifyCRL(X509CRL crl, X509Certificate cert, List certificates)
@@ -152,33 +177,33 @@ public class HapVerify {
}
private boolean verifyCRL(X509CRL crl, List certificates) throws SignatureException {
- boolean revoked = true;
+ boolean isRevoked = true;
for (X509Certificate cert : certificates) {
if (!crl.getIssuerDN().getName().equals(cert.getSubjectDN().getName())) {
continue;
}
if (!verifyCRL(crl, cert, certificates)) {
- revoked = false;
+ isRevoked = false;
}
}
- return revoked;
+ return isRevoked;
}
private void verifyCRLs(List crls, List certificates) throws VerifyHapException {
if (crls == null) {
return;
}
- boolean revoked = true;
+ boolean isRevoked = true;
try {
for (X509CRL crl : crls) {
if (!verifyCRL(crl, certificates)) {
- revoked = false;
+ isRevoked = false;
}
}
} catch (SignatureException e) {
throw new VerifyHapException("Verify CRL error!", e);
}
- if (!revoked) {
+ if (!isRevoked) {
throw new VerifyHapException("Certificate is revoked!");
}
}
@@ -186,8 +211,8 @@ public class HapVerify {
private CMSSignedData verifyCmsSignedData(byte[] signingBlock) throws VerifyHapException {
try {
CMSSignedData cmsSignedData = new CMSSignedData(signingBlock);
- boolean verifyResult = VerifyUtils.verifyCmsSignedData(cmsSignedData);
- if (!verifyResult) {
+ boolean isVerifyResult = VerifyUtils.verifyCmsSignedData(cmsSignedData);
+ if (!isVerifyResult) {
throw new VerifyHapException("Verify PKCS7 cms data failed!");
}
return cmsSignedData;
@@ -197,6 +222,10 @@ public class HapVerify {
}
private VerifyResult parserSigner(ByteBuffer signer) {
+ return parserSigner(signer, true);
+ }
+
+ private VerifyResult parserSigner(ByteBuffer signer, boolean verifyContent) {
byte[] signingBlock = new byte[signer.remaining()];
signer.get(signingBlock);
try {
@@ -204,7 +233,9 @@ public class HapVerify {
List certificates = getCertChain(cmsSignedData);
List crlList = getCrlList(cmsSignedData);
verifyCRLs(crlList, certificates);
- checkContentDigest(cmsSignedData);
+ if (verifyContent) {
+ checkContentDigest(cmsSignedData);
+ }
List signerInfos = getSignerInformations(cmsSignedData);
VerifyResult result = new VerifyResult(true, VerifyResult.RET_SUCCESS, "Verify success");
result.setCrls(crlList);
@@ -214,7 +245,7 @@ public class HapVerify {
result.setOptionalBlocks(optionalBlocks);
return result;
} catch (VerifyHapException e) {
- LOGGER.error("Verify Hap error!", e);
+ LOGGER.error("Verify profile error!", e);
return new VerifyResult(false, VerifyResult.RET_UNKNOWN_ERROR, e.getMessage());
}
}
@@ -238,8 +269,8 @@ public class HapVerify {
throw new VerifyHapException("PKCS cms content is not a byte array!");
}
try {
- boolean checkResult = parserContentinfo(contentBytes);
- if (!checkResult) {
+ boolean isCheckResult = parserContentinfo(contentBytes);
+ if (!isCheckResult) {
throw new VerifyHapException("Hap content digest check failed.");
}
} catch (DigestException | SignatureException | IOException e) {
@@ -254,7 +285,7 @@ public class HapVerify {
if (certificateList == null || certificateList.size() == 0) {
throw new VerifyHapException("Certificate chain is empty!");
}
- if (printCert) {
+ if (isPrintCert) {
for (int i = 0; i < certificateList.size(); i++) {
LOGGER.info("+++++++++++++++++++++++++++certificate #{} +++++++++++++++++++++++++++++++", i);
printCert(certificateList.get(i));
@@ -289,7 +320,7 @@ public class HapVerify {
}
private List certStoreToCertList(Store certificates)
- throws CertificateException {
+ throws CertificateException {
if (certificates == null) {
return Collections.emptyList();
}
@@ -308,7 +339,6 @@ public class HapVerify {
private boolean parserContentinfo(byte[] data)
throws DigestException, SignatureException, IOException {
- boolean result = true;
ByteBuffer digestDatas = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
while (digestDatas.remaining() > 4) {
/**
@@ -344,22 +374,22 @@ public class HapVerify {
Set keySet = digestMap.keySet();
Map actualDigestMap = HapUtils.computeDigests(
keySet, new ZipDataInput[]{beforeApkSigningBlock, centralDirectoryBlock, eocd}, optionalBlocks);
-
+ boolean isResult = true;
for (Entry entry : digestMap.entrySet()) {
ContentDigestAlgorithm digestAlg = entry.getKey();
byte[] exceptDigest = entry.getValue();
byte[] actualDigest = actualDigestMap.get(digestAlg);
if (!Arrays.equals(actualDigest, exceptDigest)) {
- result = false;
+ isResult = false;
LOGGER.error(
- "degist data do not match! DigestAlgorithm: {}, actualDigest: <{}> VS exceptDigest : <{}>",
- digestAlg.getDigestAlgorithm(),
- HapUtils.toHex(actualDigest, ""),
- HapUtils.toHex(exceptDigest, ""));
+ "degist data do not match! DigestAlgorithm: {}, actualDigest: <{}> VS exceptDigest : <{}>",
+ digestAlg.getDigestAlgorithm(),
+ HapUtils.toHex(actualDigest, ""),
+ HapUtils.toHex(exceptDigest, ""));
}
- LOGGER.info("Digest verify result: {}, DigestAlgorithm: {}", result, digestAlg.getDigestAlgorithm());
+ LOGGER.info("Digest verify result: {}, DigestAlgorithm: {}", isResult, digestAlg.getDigestAlgorithm());
}
- return result;
+ return isResult;
}
private void printCert(X509Certificate cert) throws CertificateEncodingException {
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyElf.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyElf.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a1fc80f736f96e236a115287ced1244cd90e884
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyElf.java
@@ -0,0 +1,302 @@
+/*
+ * 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
+ *
+ * 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.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.ElfBlockData;
+import com.ohos.hapsigntool.hap.entity.HwBlockHead;
+import com.ohos.hapsigntool.hap.entity.HwSignHead;
+import com.ohos.hapsigntool.hap.entity.SignatureBlockTypes;
+import com.ohos.hapsigntool.hap.entity.SigningBlock;
+import com.ohos.hapsigntool.hap.exception.ProfileException;
+import com.ohos.hapsigntool.hap.sign.SignElf;
+import com.ohos.hapsigntool.utils.FileUtils;
+import com.ohos.hapsigntool.utils.ParamConstants;
+import com.ohos.hapsigntool.utils.StringUtils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class of verify ELF.
+ *
+ * @since 2023/11/23
+ */
+public class VerifyElf {
+ private static final Logger LOGGER = LogManager.getLogger(VerifyElf.class);
+
+ static {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ private static String getProfileContent(byte[] profile) throws ProfileException {
+ try {
+ CMSSignedData cmsSignedData = new CMSSignedData(profile);
+ if (!VerifyUtils.verifyCmsSignedData(cmsSignedData)) {
+ throw new ProfileException("Verify profile pkcs7 failed! Profile is invalid");
+ }
+ Object contentObj = cmsSignedData.getSignedContent().getContent();
+ if (!(contentObj instanceof byte[])) {
+ throw new ProfileException("Check profile failed, signed profile content is not byte array!");
+ }
+ return new String((byte[]) contentObj, StandardCharsets.UTF_8);
+ } catch (CMSException e) {
+ return new String(profile, StandardCharsets.UTF_8);
+ }
+ }
+
+
+ /**
+ * Check whether parameters are valid
+ *
+ * @param options input parameters used to verify ELF.
+ * @return true, if all parameters are valid.
+ */
+ public boolean checkParams(Options options) {
+ if (!options.containsKey(ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE)) {
+ LOGGER.error("Missing parameter: {}", ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE);
+ return false;
+ }
+ if (!options.containsKey(ParamConstants.PARAM_VERIFY_PROFILE_FILE)) {
+ LOGGER.error("Missing parameter: {}", ParamConstants.PARAM_VERIFY_PROFILE_FILE);
+ return false;
+ }
+ if (!options.containsKey(ParamConstants.PARAM_VERIFY_PROOF_FILE)) {
+ LOGGER.warn("Missing parameter: {}", ParamConstants.PARAM_VERIFY_PROOF_FILE);
+ }
+ return true;
+ }
+
+ /**
+ * verify elf file.
+ *
+ * @param options input parameters used to verify elf.
+ * @return true, if verify successfully.
+ */
+ public boolean verify(Options options) {
+ VerifyResult verifyResult;
+ try {
+ if (!checkParams(options)) {
+ LOGGER.error("Check params failed!");
+ throw new IOException();
+ }
+ String filePath = options.getString(ParamConstants.PARAM_BASIC_INPUT_FILE);
+ if (StringUtils.isEmpty(filePath)) {
+ LOGGER.error("Not found verify file path!");
+ throw new IOException();
+ }
+ File signedFile = new File(filePath);
+ if (!checkSignFile(signedFile)) {
+ LOGGER.error("Check input signature ELF false!");
+ throw new IOException();
+ }
+ verifyResult = verifyElf(filePath);
+ if (!verifyResult.isVerified()) {
+ LOGGER.error("verify: {}", verifyResult.getMessage());
+ throw new IOException();
+ }
+ String outputCertPath = options.getString(ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE);
+ if (verifyResult.getCertificates() != null) {
+ writeCertificate(outputCertPath, verifyResult.getCertificates());
+ }
+ } catch (IOException e) {
+ LOGGER.error("Write certificate chain error", e);
+ return false;
+ }
+
+ String outputProfileFile = options.getString(ParamConstants.PARAM_VERIFY_PROFILE_FILE);
+ try {
+ outputOptionalBlocks(outputProfileFile, verifyResult);
+ } catch (IOException e) {
+ LOGGER.error("Output optional blocks error", e);
+ return false;
+ }
+
+ LOGGER.info("verify: {}", verifyResult.getMessage());
+ return true;
+ }
+
+ private void writeCertificate(String destFile, List certificates) throws IOException {
+ try (JcaPEMWriter writer = new JcaPEMWriter(new FileWriter(destFile))) {
+ for (final X509Certificate cert : certificates) {
+ writer.write(cert.getSubjectDN().toString() + System.lineSeparator());
+ writer.writeObject(cert);
+ }
+ LOGGER.info("Write certificate chain success!");
+ }
+ }
+
+ private void outputOptionalBlocks(String outputProfileFile, VerifyResult verifyResult) throws IOException {
+ byte[] profile = verifyResult.getProfile();
+ if (profile != null) {
+ writeOptionalBytesToFile(profile, outputProfileFile);
+ }
+ }
+
+ private void writeOptionalBytesToFile(byte[] data, String outputFile) throws IOException {
+ if (outputFile == null || outputFile.isEmpty()) {
+ return;
+ }
+ try (OutputStream out = Files.newOutputStream(Paths.get(outputFile))) {
+ out.write(data);
+ out.flush();
+ }
+ }
+
+ private boolean checkSignFile(File signedFile) {
+ try {
+ FileUtils.isValidFile(signedFile);
+ } catch (IOException e) {
+ LOGGER.error("signedFile is invalid.", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Verify elf file.
+ *
+ * @param binFile path of elf file.
+ * @return true, if verify successfully.
+ */
+ public VerifyResult verifyElf(String binFile) {
+ VerifyResult result = new VerifyResult(true, VerifyResult.RET_SUCCESS, "verify signature success");
+ File bin = new File(binFile);
+ try {
+ byte[] bytes = FileUtils.readFile(bin);
+ ElfBlockData elfSignBlockData = getElfSignBlockData(bytes);
+ String profileJson;
+ byte[] profileByte;
+ Map signBlock = getSignBlock(bytes, elfSignBlockData);
+ if (signBlock.containsKey(SignatureBlockTypes.PROFILE_NOSIGNED_BLOCK)) {
+ profileByte = signBlock.get(SignatureBlockTypes.PROFILE_NOSIGNED_BLOCK).getValue();
+ profileJson = new String(profileByte, StandardCharsets.UTF_8);
+ result.setProfile(profileByte);
+ LOGGER.warn("profile is not signed");
+ } else if (signBlock.containsKey(SignatureBlockTypes.PROFILE_SIGNED_BLOCK)) {
+ // verify signed profile
+ SigningBlock profileSign = signBlock.get(SignatureBlockTypes.PROFILE_SIGNED_BLOCK);
+ profileByte = profileSign.getValue();
+ profileJson = getProfileContent(profileByte);
+ result = new HapVerify().verifyElfProfile(profileSign.getValue());
+ result.setProfile(profileByte);
+ LOGGER.info("verify profile success");
+ } else {
+ LOGGER.warn("can not found profile sign block");
+ profileJson = null;
+ }
+
+ if (signBlock.containsKey(SignElf.CODESIGN_BLOCK_TYPE)) {
+ // verify codesign
+ SigningBlock codesign = signBlock.get(SignElf.CODESIGN_BLOCK_TYPE);
+ if (!VerifyCodeSignature.verifyElf(bin, codesign.getOffset(), codesign.getLength(),
+ "elf", profileJson)) {
+ String errMsg = "Verify codesign error!";
+ result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, errMsg);
+ }
+ LOGGER.info("verify codesign success");
+ } else {
+ LOGGER.warn("can not found code sign block");
+ }
+ } catch (IOException e) {
+ LOGGER.error("Verify file has IO error!", e);
+ result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, e.getMessage());
+ } catch (FsVerityDigestException | VerifyCodeSignException e) {
+ LOGGER.error("Verify codesign error!", e);
+ result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, e.getMessage());
+ } catch (CMSException | ProfileException e) {
+ LOGGER.error("Verify profile error!", e);
+ result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, e.getMessage());
+ }
+ return result;
+ }
+
+ private ElfBlockData getElfSignBlockData(byte[] bytes) throws IOException {
+ int offset = bytes.length - HwSignHead.SIGN_HEAD_LEN;
+ byte[] magicByte = readByteArrayOffset(bytes, offset, HwSignHead.ELF_MAGIC.length);
+ offset += HwSignHead.ELF_MAGIC.length;
+ byte[] versionByte = readByteArrayOffset(bytes, offset, HwSignHead.VERSION.length);
+ offset += HwSignHead.VERSION.length;
+ for (int i = 0; i < HwSignHead.ELF_MAGIC.length; i++) {
+ if (HwSignHead.ELF_MAGIC[i] != magicByte[i]) {
+ throw new IOException("elf magic verify failed");
+ }
+ }
+ for (int i = 0; i < HwSignHead.VERSION.length; i++) {
+ if (HwSignHead.VERSION[i] != versionByte[i]) {
+ throw new IOException("elf sign version verify failed");
+ }
+ }
+ int intByteLength = 4;
+ byte[] blockSizeByte = readByteArrayOffset(bytes, offset, intByteLength);
+ offset += intByteLength;
+ byte[] blockNumByte = readByteArrayOffset(bytes, offset, intByteLength);
+ ByteBuffer blockNumBf = ByteBuffer.wrap(blockNumByte).order(ByteOrder.LITTLE_ENDIAN);
+ int blockNum = blockNumBf.getInt();
+
+ ByteBuffer blockSizeBf = ByteBuffer.wrap(blockSizeByte).order(ByteOrder.LITTLE_ENDIAN);
+ int blockSize = blockSizeBf.getInt();
+
+ int blockStart = bytes.length - HwSignHead.SIGN_HEAD_LEN - blockSize;
+ return new ElfBlockData(blockNum, blockStart);
+ }
+
+ private Map getSignBlock(byte[] bytes, ElfBlockData elfBlockData) throws ProfileException {
+ int offset = elfBlockData.getBlockStart();
+
+ Map blockMap = new HashMap<>();
+ for (int i = 0; i < elfBlockData.getBlockNum(); i++) {
+ byte[] blockByte = readByteArrayOffset(bytes, offset, HwBlockHead.ELF_BLOCK_LEN);
+ ByteBuffer blockBuffer = ByteBuffer.wrap(blockByte).order(ByteOrder.LITTLE_ENDIAN);
+ char type = blockBuffer.getChar();
+ char tag = blockBuffer.getChar();
+ int length = blockBuffer.getInt();
+ int blockOffset = blockBuffer.getInt();
+ byte[] value = readByteArrayOffset(bytes, elfBlockData.getBlockStart() + blockOffset, length);
+ blockMap.put(type, new SigningBlock(type, value, elfBlockData.getBlockStart() + blockOffset));
+ offset += HwBlockHead.ELF_BLOCK_LEN;
+ }
+ return blockMap;
+ }
+
+ private byte[] readByteArrayOffset(byte[] bytes, int offset, int length) {
+ byte[] output = new byte[length];
+ System.arraycopy(bytes, offset, output, 0, length);
+ return output;
+ }
+}
\ No newline at end of file
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..56e634bd9b0d06442a5bf00560bdc8bc454595a6 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,9 +16,13 @@
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;
+import com.ohos.hapsigntool.hap.exception.ProfileException;
import com.ohos.hapsigntool.hap.exception.SignatureNotFoundException;
import com.ohos.hapsigntool.utils.FileUtils;
import com.ohos.hapsigntool.utils.HapUtils;
@@ -26,34 +30,41 @@ import com.ohos.hapsigntool.utils.ParamConstants;
import com.ohos.hapsigntool.utils.StringUtils;
import com.ohos.hapsigntool.zip.ByteBufferZipDataInput;
import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput;
+import com.ohos.hapsigntool.zip.UnsignedDecimalUtil;
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 org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.util.Arrays;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
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.Paths;
import java.security.Security;
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.
*
- * @2021/12/23
+ * @since 2021/12/23
*/
public class VerifyHap {
private static final Logger LOGGER = LogManager.getLogger(VerifyHap.class);
@@ -61,20 +72,37 @@ public class VerifyHap {
private static final int ZIP_HEAD_OF_SIGNING_BLOCK_COUNT_OFFSET_REVERSE = 28;
private static final int ZIP_HEAD_OF_SUBSIGNING_BLOCK_LENGTH = 12;
- private final boolean printCert;
+ static {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ private final boolean isPrintCert;
public VerifyHap() {
this(true);
}
- public VerifyHap(boolean printCert) {
- this.printCert = printCert;
+ public VerifyHap(boolean isPrintCert) {
+ this.isPrintCert = isPrintCert;
}
- static {
- Security.addProvider(new BouncyCastleProvider());
+ private static String getProfileContent(byte[] profile) throws ProfileException {
+ try {
+ CMSSignedData cmsSignedData = new CMSSignedData(profile);
+ if (!VerifyUtils.verifyCmsSignedData(cmsSignedData)) {
+ throw new ProfileException("Verify profile pkcs7 failed! Profile is invalid");
+ }
+ Object contentObj = cmsSignedData.getSignedContent().getContent();
+ if (!(contentObj instanceof byte[])) {
+ throw new ProfileException("Check profile failed, signed profile content is not byte array!");
+ }
+ return new String((byte[]) contentObj, StandardCharsets.UTF_8);
+ } catch (CMSException e) {
+ return new String(profile, StandardCharsets.UTF_8);
+ }
}
+
/**
* Check whether parameters are valid
*
@@ -110,7 +138,6 @@ public class VerifyHap {
throw new IOException();
}
String filePath = options.getString(ParamConstants.PARAM_BASIC_INPUT_FILE);
- String outputCertPath = options.getString(ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE);
if (StringUtils.isEmpty(filePath)) {
LOGGER.error("Not found verify file path!");
throw new IOException();
@@ -125,8 +152,10 @@ public class VerifyHap {
LOGGER.error("verify: {}", verifyResult.getMessage());
throw new IOException();
}
-
- writeCertificate(outputCertPath, verifyResult.getCertificates());
+ String outputCertPath = options.getString(ParamConstants.PARAM_VERIFY_CERTCHAIN_FILE);
+ if (verifyResult.getCertificates() != null) {
+ writeCertificate(outputCertPath, verifyResult.getCertificates());
+ }
} catch (IOException e) {
LOGGER.error("Write certificate chain error", e);
return false;
@@ -157,7 +186,7 @@ public class VerifyHap {
}
private void outputOptionalBlocks(String outputProfileFile, String outputProofFile, String outputPropertyFile,
- VerifyResult verifyResult) throws IOException {
+ VerifyResult verifyResult) throws IOException {
List optionalBlocks = verifyResult.getOptionalBlocks();
if (optionalBlocks != null && optionalBlocks.size() > 0) {
for (SigningBlock optionalBlock : optionalBlocks) {
@@ -177,13 +206,17 @@ public class VerifyHap {
}
}
}
+ byte[] profile = verifyResult.getProfile();
+ if (profile != null) {
+ writeOptionalBytesToFile(profile, outputProfileFile);
+ }
}
private void writeOptionalBytesToFile(byte[] data, String outputFile) throws IOException {
if (outputFile == null || outputFile.isEmpty()) {
return;
}
- try (OutputStream out = new FileOutputStream(outputFile)) {
+ try (OutputStream out = Files.newOutputStream(Paths.get(outputFile))) {
out.write(data);
out.flush();
}
@@ -199,31 +232,6 @@ public class VerifyHap {
return true;
}
- /**
- * Verify signature of hap.
- *
- * @param hapFilePath path of hap file
- * @param outCertPath path to output certificate file
- * @param outProvisionFile path to output provision file
- * @return verify result
- */
- public VerifyResult verifyHap(String hapFilePath, String outCertPath, String outProvisionFile) {
- VerifyResult verifyResult = verifyHap(hapFilePath);
- if (!verifyResult.isVerified()) {
- return verifyResult;
- }
- List certificates = verifyResult.getCertificates();
- try {
- writeCertificate(outCertPath, certificates);
- outputOptionalBlocks(outProvisionFile, null, null, verifyResult);
- } catch (IOException e) {
- LOGGER.error("Write certificate chain or profile error", e);
- verifyResult.setResult(false);
- return verifyResult;
- }
- return verifyResult;
- }
-
/**
* Verify hap file.
*
@@ -248,16 +256,12 @@ public class VerifyHap {
ByteBuffer signatureSchemeBlock = blockPair.getFirst();
List optionalBlocks = blockPair.getSecond();
Collections.reverse(optionalBlocks);
- long signingBlockOffset = hapSigningBlockAndOffsetInFile.getOffset();
- ZipDataInput beforeHapSigningBlock = hapFile.slice(0, signingBlockOffset);
- ZipDataInput centralDirectoryBlock = hapFile.slice(zipInfo.getCentralDirectoryOffset(),
- zipInfo.getCentralDirectorySize());
- ByteBuffer eocdBbyteBuffer = zipInfo.getEocd();
- ZipUtils.setCentralDirectoryOffset(eocdBbyteBuffer, signingBlockOffset);
- ZipDataInput eocdBlock = new ByteBufferZipDataInput(eocdBbyteBuffer);
- HapVerify verifyEngine = new HapVerify(beforeHapSigningBlock, signatureSchemeBlock,
- centralDirectoryBlock, eocdBlock, optionalBlocks);
- verifyEngine.setPrintCert(printCert);
+ if (!checkCodeSign(hapFilePath, optionalBlocks)) {
+ String errMsg = "code sign verify failed";
+ return new VerifyResult(false, VerifyResult.RET_CODESIGN_DATA_ERROR, errMsg);
+ }
+ HapVerify verifyEngine = getHapVerify(hapFile, zipInfo, hapSigningBlockAndOffsetInFile,
+ signatureSchemeBlock, optionalBlocks);
result = verifyEngine.verify();
result.setSignBlockVersion(hapSigningBlockAndOffsetInFile.getVersion());
} catch (IOException e) {
@@ -269,19 +273,97 @@ 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());
+ } catch (ProfileException e) {
+ LOGGER.error("Verify Hap failed, parse app-identifier from profile failed, profile is invalid", e);
+ return new VerifyResult(false, VerifyResult.RET_CODE_SIGN_BLOCK_ERROR, e.getMessage());
}
return result;
}
+ private HapVerify getHapVerify(ZipDataInput hapFile, ZipFileInfo zipInfo,
+ HapUtils.HapSignBlockInfo hapSigningBlockAndOffsetInFile,
+ ByteBuffer signatureSchemeBlock, List optionalBlocks) {
+ long signingBlockOffset = hapSigningBlockAndOffsetInFile.getOffset();
+ ZipDataInput beforeHapSigningBlock = hapFile.slice(0, signingBlockOffset);
+ ZipDataInput centralDirectoryBlock = hapFile.slice(zipInfo.getCentralDirectoryOffset(),
+ zipInfo.getCentralDirectorySize());
+ ByteBuffer eocdBbyteBuffer = zipInfo.getEocd();
+ ZipUtils.setCentralDirectoryOffset(eocdBbyteBuffer, signingBlockOffset);
+ ZipDataInput eocdBlock = new ByteBufferZipDataInput(eocdBbyteBuffer);
+ HapVerify verifyEngine = new HapVerify(beforeHapSigningBlock, signatureSchemeBlock,
+ centralDirectoryBlock, eocdBlock, optionalBlocks);
+ verifyEngine.setIsPrintCert(isPrintCert);
+ return verifyEngine;
+ }
+
+ /**
+ * code sign check
+ *
+ * @param hapFilePath hap file path
+ * @param optionalBlocks optional blocks
+ * @return true or false
+ * @throws FsVerityDigestException FsVerity digest on error
+ * @throws IOException IO error
+ * @throws VerifyCodeSignException verify code sign on error
+ * @throws CMSException cms on error
+ * @throws ProfileException profile of the hap error
+ */
+ private boolean checkCodeSign(String hapFilePath, List optionalBlocks)
+ throws FsVerityDigestException, IOException, VerifyCodeSignException, CMSException, ProfileException {
+ 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) {
+ LOGGER.info("trying verify codesign block");
+ String[] fileNameArray = hapFilePath.split("\\.");
+ if (fileNameArray.length < ParamConstants.FILE_NAME_MIN_LENGTH) {
+ LOGGER.error("ZIP64 format not supported");
+ return false;
+ }
+ ByteBuffer byteBuffer = ByteBuffer.wrap(propertyBlockArray);
+ ByteBuffer header = HapUtils.reverseSliceBuffer(byteBuffer, 0, ZIP_HEAD_OF_SUBSIGNING_BLOCK_LENGTH);
+ long blockOffset = UnsignedDecimalUtil.getUnsignedInt(header);
+ 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);
+ byte[] profileArray = map.get(HapUtils.HAP_PROFILE_BLOCK_ID);
+ String profileContent = getProfileContent(profileArray);
+ String suffix = fileNameArray[fileNameArray.length - 1];
+ boolean isCodeSign = VerifyCodeSignature.verifyHap(outputFile, blockOffset, blockLength,
+ suffix, profileContent);
+ if (!isCodeSign) {
+ LOGGER.error("Verify Hap has no code sign data error!");
+ return false;
+ }
+ LOGGER.info("verify codesign success");
+ return true;
+ }
+ LOGGER.info("can not find codesign block");
+ return true;
+ }
+
private Pair> getHapSignatureSchemeBlockAndOptionalBlocks(ByteBuffer hapSigningBlock)
throws SignatureNotFoundException {
try {
ByteBuffer header = HapUtils.reverseSliceBuffer(
- hapSigningBlock,
- hapSigningBlock.capacity() - ZIP_HEAD_OF_SIGNING_BLOCK_LENGTH,
- hapSigningBlock.capacity());
+ hapSigningBlock,
+ hapSigningBlock.capacity() - ZIP_HEAD_OF_SIGNING_BLOCK_LENGTH,
+ hapSigningBlock.capacity());
ByteBuffer value = HapUtils.reverseSliceBuffer(hapSigningBlock, 0,
- hapSigningBlock.capacity() - ZIP_HEAD_OF_SIGNING_BLOCK_LENGTH);
+ hapSigningBlock.capacity() - ZIP_HEAD_OF_SIGNING_BLOCK_LENGTH);
byte[] signatureValueBytes = new byte[value.capacity()];
value.get(signatureValueBytes, 0, signatureValueBytes.length);
@@ -301,8 +383,8 @@ public class VerifyHap {
blockLength = value.getInt();
blockType = value.getInt();
if (blockOffset + blockLength > signatureValueBytes.length) {
- throw new SignatureNotFoundException("block end pos: " + (blockOffset + blockLength) +
- " is larger than block len: " + signatureValueBytes.length);
+ throw new SignatureNotFoundException("block end pos: " + (blockOffset + blockLength)
+ + " is larger than block len: " + signatureValueBytes.length);
}
if (HapUtils.getHapSignatureOptionalBlockIds().contains(blockType)) {
byte[] blockValue = Arrays.copyOfRange(signatureValueBytes, blockOffset, blockOffset + blockLength);
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..ba032e84625eca6fdf6e1c75f217f20462f8ddec 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
@@ -16,6 +16,7 @@
package com.ohos.hapsigntool.hap.verify;
import com.ohos.hapsigntool.hap.entity.SigningBlock;
+
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.util.Store;
@@ -90,7 +91,17 @@ public class VerifyResult {
*/
public static final int RET_CRL_ERROR = 10011;
- private boolean result;
+ /**
+ * 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 isResult;
private int code;
private String message;
@@ -106,6 +117,8 @@ public class VerifyResult {
private int signBlockVersion;
+ private byte[] profile;
+
/**
* Empty constructor
*/
@@ -115,22 +128,22 @@ public class VerifyResult {
/**
* Verify result constructor
*
- * @param result verify result
+ * @param isResult verify result
* @param code error code
* @param message error message
*/
- public VerifyResult(boolean result, int code, String message) {
- this.result = result;
+ public VerifyResult(boolean isResult, int code, String message) {
+ this.isResult = isResult;
this.code = code;
this.message = message;
}
public boolean isVerified() {
- return result;
+ return isResult;
}
- public void setResult(boolean result) {
- this.result = result;
+ public void setIsResult(boolean isResult) {
+ this.isResult = isResult;
}
public int getCode() {
@@ -196,4 +209,12 @@ public class VerifyResult {
public void setSignBlockVersion(int signBlockVersion) {
this.signBlockVersion = signBlockVersion;
}
+
+ public byte[] getProfile() {
+ return profile;
+ }
+
+ public void setProfile(byte[] profile) {
+ this.profile = profile;
+ }
}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/IProvisionVerifier.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/IProvisionVerifier.java
index db420e14af926081391e4cb38f4e4e2b2263cdae..3c88f3cebf24d80b24118b48dedec1d4d504da72 100644
--- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/IProvisionVerifier.java
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/IProvisionVerifier.java
@@ -15,6 +15,7 @@
package com.ohos.hapsigntool.profile;
+import com.ohos.hapsigntool.error.VerifyException;
import com.ohos.hapsigntool.profile.model.VerificationResult;
/**
@@ -29,6 +30,7 @@ public interface IProvisionVerifier {
*
* @param p7b signed p7b content
* @return result
+ * @throws VerifyException verify p7b failed
*/
- VerificationResult verify(byte[] p7b);
+ VerificationResult verify(byte[] p7b) throws VerifyException;
}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/ProfileSignTool.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/ProfileSignTool.java
index cc58c3fb869b9eff68541c0f9f05761573edb06f..a71cbdde570b0798ab8cdef5e5ea48c630ae40a3 100644
--- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/ProfileSignTool.java
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/ProfileSignTool.java
@@ -18,6 +18,7 @@ package com.ohos.hapsigntool.profile;
import com.ohos.hapsigntool.api.LocalizationAdapter;
import com.ohos.hapsigntool.error.CustomException;
import com.ohos.hapsigntool.error.ERROR;
+import com.ohos.hapsigntool.error.VerifyException;
import com.ohos.hapsigntool.profile.model.VerificationResult;
import com.ohos.hapsigntool.signer.ISigner;
import com.ohos.hapsigntool.signer.SignerFactory;
@@ -88,7 +89,12 @@ public final class ProfileSignTool {
ISigner signer = new SignerFactory().getSigner(adapter);
byte[] p7b = signProfile(content, signer, adapter.getSignAlg());
VerifyHelper verifyHelper = new VerifyHelper();
- VerificationResult verificationResult = verifyHelper.verify(p7b);
+ VerificationResult verificationResult = null;
+ try {
+ verificationResult = verifyHelper.verify(p7b);
+ } catch (VerifyException e) {
+ CustomException.throwException(ERROR.VERIFY_ERROR, "Generate Profile Failed! " + e.getMessage());
+ }
ValidateUtils.throwIfNotMatches(verificationResult.isVerifiedPassed(), ERROR.SIGN_ERROR,
verificationResult.getMessage());
return p7b;
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 575d50d2a624e26260991e3a5e94fc164bf885e3..71d691cd11ced2bd27bf9f11abb34abd403afa71 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
@@ -18,6 +18,7 @@ package com.ohos.hapsigntool.profile;
import com.google.gson.JsonObject;
import com.ohos.hapsigntool.error.CustomException;
import com.ohos.hapsigntool.error.ERROR;
+import com.ohos.hapsigntool.error.VerifyException;
import com.ohos.hapsigntool.hap.verify.VerifyUtils;
import com.ohos.hapsigntool.profile.model.VerificationResult;
import com.ohos.hapsigntool.utils.CertChainUtils;
@@ -129,9 +130,10 @@ public class VerifyHelper implements IProvisionVerifier {
*
* @param p7b signed p7b content
* @return result
+ * @throws VerifyException verify p7b failed
*/
@Override
- public VerificationResult verify(byte[] p7b) {
+ public VerificationResult verify(byte[] p7b) throws VerifyException {
VerificationResult result = new VerificationResult();
try {
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertChainUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertChainUtils.java
index 1ddd9dce9bd8a44715d028a809629625fe88b4a8..28f4e3e8b468f6fdec282de5ed72a9c8be0f0dc9 100644
--- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertChainUtils.java
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertChainUtils.java
@@ -17,6 +17,7 @@ package com.ohos.hapsigntool.utils;
import com.ohos.hapsigntool.error.CustomException;
import com.ohos.hapsigntool.error.ERROR;
+import com.ohos.hapsigntool.error.VerifyException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -88,9 +89,10 @@ public class CertChainUtils {
* @param serial serial number
* @param root root cert
* @param signTime signing time
+ * @throws VerifyException verifyException
*/
public static void verifyCertChain(List certificates, X500Principal issuer, BigInteger serial,
- X509Certificate root, Date signTime) {
+ X509Certificate root, Date signTime) throws VerifyException {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
@@ -115,8 +117,7 @@ public class CertChainUtils {
}
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | IOException | CertificateException
| KeyStoreException | CertPathBuilderException | CertPathValidatorException exception) {
- LOGGER.debug(exception.getMessage(), exception);
- CustomException.throwException(ERROR.VERIFY_ERROR, "Failed to verify signature: " + exception.getMessage());
+ throw new VerifyException("Cert chain verify failed! " + exception.getMessage());
}
}
}
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..b61266aeeb79be65d51c146f8f46d65c6e84570f 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
@@ -18,6 +18,7 @@ package com.ohos.hapsigntool.utils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.ohos.hapsigntool.error.ERROR;
+
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -35,6 +36,10 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Common file operation.
@@ -42,27 +47,36 @@ import java.nio.file.Files;
* @since 2021/12/28
*/
public final class FileUtils {
-
/**
* LOGGER.
*/
private static final Logger LOGGER = LogManager.getLogger(FileUtils.class);
+
+ /**
+ * suffix regex map
+ */
+ public static final Map SUFFIX_REGEX_MAP = new HashMap<>();
+
/**
* add GSON static.
*/
public static final Gson GSON = (new GsonBuilder()).disableHtmlEscaping().create();
+
/**
* add GSON_PRETTY_PRINT static.
*/
public static final Gson GSON_PRETTY_PRINT = (new GsonBuilder()).disableHtmlEscaping().setPrettyPrinting().create();
+
/**
* File reader block size
*/
- public static final int FILE_BUFFER_BLOCK = 4096;
+ public static final int FILE_BUFFER_BLOCK = 1024 * 1024;
+
/**
* File end
*/
public static final int FILE_END = -1;
+
/**
* Expected split string length
*/
@@ -70,6 +84,10 @@ public final class FileUtils {
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+ static {
+ SUFFIX_REGEX_MAP.put("so", Pattern.compile("\\.so(\\.[0-9]*){0,3}$"));
+ }
+
private FileUtils() {
}
@@ -96,7 +114,7 @@ public final class FileUtils {
* @throws IOException Read failed
*/
public static byte[] readFile(File file) throws IOException {
- return read(new FileInputStream(file));
+ return read(Files.newInputStream(file.toPath()));
}
/**
@@ -119,6 +137,68 @@ public final class FileUtils {
}
}
+ /**
+ * Read byte from input file.
+ *
+ * @param file input file
+ * @param offset offset
+ * @param length length
+ * @return data bytes
+ */
+ public static byte[] readFileByOffsetAndLength(File file, long offset, long length) throws IOException {
+ try (FileInputStream input = new FileInputStream(file)) {
+ return readInputByOffsetAndLength(input, offset, length);
+ }
+ }
+
+ /**
+ * Read byte from input stream.
+ *
+ * @param input input stream
+ * @param offset offset
+ * @param length length
+ * @return data bytes
+ * @throws IOException read exception
+ */
+ public static byte[] readInputByOffsetAndLength(InputStream input, long offset, long length) throws IOException {
+ input.skip(offset);
+ return readInputByLength(input, length);
+ }
+
+ /**
+ * Read byte from input stream.
+ *
+ * @param input InputStream
+ * @param length length
+ * @return data bytes
+ */
+ public static byte[] readInputByLength(InputStream input, long length) throws IOException {
+ try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
+ if (length > Integer.MAX_VALUE) {
+ throw new IOException("Size cannot be greater than Integer max value: " + length);
+ }
+ writeInputToOutPut(input, output, length);
+ return output.toByteArray();
+ }
+ }
+
+ /**
+ * write input to output by length
+ */
+ private static void writeInputToOutPut(InputStream input, OutputStream output, long length) throws IOException {
+ byte[] buffer = new byte[FILE_BUFFER_BLOCK];
+ long hasReadLen = 0L;
+ while (hasReadLen < length) {
+ int readLen = (int) Math.min(length - hasReadLen, FILE_BUFFER_BLOCK);
+ int len = input.read(buffer, 0, readLen);
+ if (len != readLen) {
+ throw new IOException("read" + hasReadLen + "bytes data less than " + length);
+ }
+ output.write(buffer, 0, len);
+ hasReadLen += len;
+ }
+ }
+
/**
* Out put content to file.
*
@@ -134,6 +214,29 @@ public final class FileUtils {
}
}
+ /**
+ * Write data in file to output stream
+ *
+ * @param inFile input file path.
+ * @param out output file path.
+ * @param offset file read offset
+ * @param size file read size
+ * @return true, if write successfully.
+ */
+ public static boolean appendWriteFileByOffsetToFile(String inFile, FileOutputStream out, long offset, long size) {
+ File inputFile = new File(inFile);
+ try (FileInputStream fis = new FileInputStream(inputFile)) {
+ fis.skip(offset);
+ writeInputToOutPut(fis, out, size);
+ return true;
+ } catch (FileNotFoundException e) {
+ LOGGER.error("Failed to get input stream object.");
+ } catch (IOException e) {
+ LOGGER.error("Failed to read or write data.");
+ }
+ return false;
+ }
+
/**
* Check file exist or not.
*
@@ -221,10 +324,13 @@ 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) {
- LOGGER.error("Faile to write data to output stream.");
+ LOGGER.error("Failed to write data to output stream.");
return false;
}
return true;
@@ -247,14 +353,32 @@ public final class FileUtils {
/**
* Write byte array data to output file.
*
- * @param signHeadByte byte array data.
+ * @param bytes byte array data.
* @param outFile output file path.
* @return true, if write successfully.
*/
- public static boolean writeByteToOutFile(byte[] signHeadByte, String outFile) {
+ public static boolean writeByteToOutFile(byte[] bytes, String outFile) {
try (OutputStream ops = new FileOutputStream(outFile, true)) {
- ops.write(signHeadByte, 0, signHeadByte.length);
- ops.flush();
+ return writeByteToOutFile(bytes, ops);
+ } catch (FileNotFoundException e) {
+ LOGGER.error("Failed to get output stream object, outfile: " + outFile);
+ } catch (IOException e) {
+ LOGGER.error("Failed to write data to ops, outfile: " + outFile);
+ }
+ return false;
+ }
+
+ /**
+ * Write byte array data to output file.
+ *
+ * @param bytes byte array data.
+ * @param outFile output file path.
+ * @return true, if write successfully.
+ */
+ public static boolean writeByteToOutFile(byte[] bytes, OutputStream outFile) {
+ try {
+ outFile.write(bytes, 0, bytes.length);
+ outFile.flush();
return true;
} catch (FileNotFoundException e) {
LOGGER.error("Failed to get output stream object, outfile: " + outFile);
@@ -286,6 +410,7 @@ public final class FileUtils {
/**
* Open an input stream of input file safely.
+ *
* @param file input file.
* @return an input stream of input file
* @throws IOException file is a directory or can't be read.
@@ -366,4 +491,26 @@ public final class FileUtils {
}
}
}
+
+ /**
+ * regex filename
+ *
+ * @param name filename
+ * @return boolean
+ */
+ public static boolean isRunnableFile(String name) {
+ if (StringUtils.isEmpty(name)) {
+ return false;
+ }
+ if (name.endsWith(".an") || name.endsWith(".abc")) {
+ return true;
+ }
+ for (Pattern pattern : SUFFIX_REGEX_MAP.values()) {
+ Matcher matcher = pattern.matcher(name);
+ if (matcher.find()) {
+ return true;
+ }
+ }
+ return false;
+ }
}
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..172f8a75789ce473b2aa459476b2d4cef5f80169 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
@@ -38,12 +38,12 @@ import java.nio.ByteOrder;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.Collections;
/**
* Hap util, parse hap, find signature block.
@@ -73,6 +73,11 @@ public class HapUtils {
*/
public static final int HAP_PROPERTY_BLOCK_ID = 0x20000003;
+ /**
+ * ID of property block
+ */
+ public static final int HAP_CODE_SIGN_BLOCK_ID = 0x30000001;
+
/**
* The size of data block used to get digest
*/
@@ -97,12 +102,12 @@ public class HapUtils {
/**
* int size
*/
- public static final int INT_SIZE = 4;
+ public static final int INT_SIZE = 4;
/**
* block number
*/
- public static final int BLOCK_NUMBER = 1;
+ public static final int BLOCK_NUMBER = 1;
/**
* hap sign schema v2 signature block version
@@ -124,55 +129,67 @@ public class HapUtils {
*/
public static final long HAP_SIG_BLOCK_MAGIC_HI_V2 = 0x3234206b636f6c42L;
- private HapUtils() {
- }
-
/**
- * The set of IDs of optional blocks in hap signature block.
+ * The value of lower 8 bytes of magic word
*/
- private static final Set HAP_SIGNATURE_OPTIONAL_BLOCK_IDS ;
+ public static final long HAP_SIG_BLOCK_MAGIC_LO_V3 = 0x676973207061683cL;
/**
- * Minimum api version for hap sign schema v3.
+ * The value of higher 8 bytes of magic word
*/
- private static final int MIN_COMPATIBLE_VERSION_FOR_SCHEMA_V3 = 8;
+ public static final long HAP_SIG_BLOCK_MAGIC_HI_V3 = 0x3e6b636f6c62206eL;
/**
- * Magic word of hap signature block v2
+ * Size of hap signature block header
*/
- private static final byte[] HAP_SIGNING_BLOCK_MAGIC_V2 =
- new byte[] {0x48, 0x41, 0x50, 0x20, 0x53, 0x69, 0x67, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32};
+ public static final int HAP_SIG_BLOCK_HEADER_SIZE = 32;
/**
- * Magic word of hap signature block
+ * The min size of hap signature block
*/
- private static final byte[] HAP_SIGNING_BLOCK_MAGIC_V3 =
- new byte[] {0x3c, 0x68, 0x61, 0x70, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3e};
+ public static final int HAP_SIG_BLOCK_MIN_SIZE = HAP_SIG_BLOCK_HEADER_SIZE;
/**
- * The value of lower 8 bytes of magic word
+ * The set of IDs of optional blocks in hap signature block.
*/
- public static final long HAP_SIG_BLOCK_MAGIC_LO_V3 = 0x676973207061683cL;
+ private static final Set HAP_SIGNATURE_OPTIONAL_BLOCK_IDS ;
/**
- * The value of higher 8 bytes of magic word
+ * Minimum api version for hap sign schema v3.
*/
- public static final long HAP_SIG_BLOCK_MAGIC_HI_V3 = 0x3e6b636f6c62206eL;
+ private static final int MIN_COMPATIBLE_VERSION_FOR_SCHEMA_V3 = 8;
/**
- * Size of hap signature block header
+ * Magic word of hap signature block v2
*/
- public static final int HAP_SIG_BLOCK_HEADER_SIZE = 32;
+ private static final byte[] HAP_SIGNING_BLOCK_MAGIC_V2 =
+ new byte[] {0x48, 0x41, 0x50, 0x20, 0x53, 0x69, 0x67, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32};
/**
- * The min size of hap signature block
+ * Magic word of hap signature block
*/
- public static final int HAP_SIG_BLOCK_MIN_SIZE = HAP_SIG_BLOCK_HEADER_SIZE;
+ private static final byte[] HAP_SIGNING_BLOCK_MAGIC_V3 =
+ new byte[] {0x3c, 0x68, 0x61, 0x70, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3e};
private static final byte ZIP_FIRST_LEVEL_CHUNK_PREFIX = 0x5a;
private static final byte ZIP_SECOND_LEVEL_CHUNK_PREFIX = (byte) 0xa5;
private static final int DIGEST_PRIFIX_LENGTH = 5;
private static final int BUFFER_LENGTH = 4096;
+ private static final char[] HEX_CHAR_ARRAY = "0123456789ABCDEF".toCharArray();
+
+ /**
+ * The set of IDs of optional blocks in hap signature block.
+ */
+ static {
+ Set blockIds = new HashSet();
+ blockIds.add(HAP_PROOF_OF_ROTATION_BLOCK_ID);
+ blockIds.add(HAP_PROFILE_BLOCK_ID);
+ blockIds.add(HAP_PROPERTY_BLOCK_ID);
+ HAP_SIGNATURE_OPTIONAL_BLOCK_IDS = Collections.unmodifiableSet(blockIds);
+ }
+
+ private HapUtils() {
+ }
/**
* Get HAP_SIGNATURE_OPTIONAL_BLOCK_IDS
@@ -209,17 +226,6 @@ public class HapUtils {
return HAP_SIGN_SCHEME_V2_BLOCK_VERSION;
}
- /**
- * The set of IDs of optional blocks in hap signature block.
- */
- static {
- Set blockIds = new HashSet();
- blockIds.add(HAP_PROOF_OF_ROTATION_BLOCK_ID);
- blockIds.add(HAP_PROFILE_BLOCK_ID);
- blockIds.add(HAP_PROPERTY_BLOCK_ID);
- HAP_SIGNATURE_OPTIONAL_BLOCK_IDS = Collections.unmodifiableSet(blockIds);
- }
-
/**
* Read data from hap file.
*
@@ -229,7 +235,7 @@ public class HapUtils {
*/
public static byte[] readFileToByte(String file) throws IOException {
try (FileInputStream in = new FileInputStream(file);
- ByteArrayOutputStream out = new ByteArrayOutputStream(in.available());) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(in.available());) {
byte[] buf = new byte[BUFFER_LENGTH];
int len = 0;
while ((len = in.read(buf)) != -1) {
@@ -242,8 +248,8 @@ public class HapUtils {
private static long getChunkCount(ZipDataInput[] contents) {
long chunkCount = 0L;
for (ZipDataInput content : contents) {
- chunkCount += ((content.size() + CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES - 1) /
- CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
+ chunkCount += ((content.size() + CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES - 1)
+ / CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
}
return chunkCount;
}
@@ -267,7 +273,7 @@ public class HapUtils {
}
int chunkCount = (int) chunkCountLong;
ContentDigestAlgorithm[] contentDigestAlgorithms = digestAlgorithms.toArray(
- new ContentDigestAlgorithm[digestAlgorithms.size()]);
+ new ContentDigestAlgorithm[digestAlgorithms.size()]);
MessageDigest[] messageDigests = new MessageDigest[contentDigestAlgorithms.length];
int[] digestOutputSizes = new int[contentDigestAlgorithms.length];
byte[][] digestOfChunks = new byte[contentDigestAlgorithms.length][];
@@ -306,7 +312,7 @@ public class HapUtils {
for (int i = 0; i < contentDigestAlgorithms.length; i++) {
int expectedDigestSizeBytes = digestOutputSizes[i];
int actualDigestSizeBytes = messageDigests[i].digest(digestOfChunks[i],
- chunkIndex * expectedDigestSizeBytes + DIGEST_PRIFIX_LENGTH, expectedDigestSizeBytes);
+ chunkIndex * expectedDigestSizeBytes + DIGEST_PRIFIX_LENGTH, expectedDigestSizeBytes);
if (actualDigestSizeBytes != expectedDigestSizeBytes) {
throw new DigestException("Unexpected output size of " + messageDigests[i].getAlgorithm()
+ " digest: " + actualDigestSizeBytes);
@@ -334,7 +340,7 @@ public class HapUtils {
}
private static Map getContentDigestAlgorithmMap(List optionalBlocks,
- ContentDigestAlgorithm[] contentDigestAlgorithms, MessageDigest[] messageDigests, byte[][] digestOfChunks) {
+ ContentDigestAlgorithm[] contentDigestAlgorithms, MessageDigest[] messageDigests, byte[][] digestOfChunks) {
Map result = new HashMap<>(contentDigestAlgorithms.length);
for (int i = 0; i < contentDigestAlgorithms.length; i++) {
messageDigests[i].update(digestOfChunks[i]);
@@ -352,8 +358,6 @@ public class HapUtils {
}
}
- private static final char[] HEX_CHAR_ARRAY = "0123456789ABCDEF".toCharArray();
-
/**
* Slice buffer to target size.
*
@@ -384,7 +388,7 @@ public class HapUtils {
int capacity = source.capacity();
if (startPos < 0 || endPos < startPos || endPos > capacity) {
throw new IllegalArgumentException(
- "startPos: " + startPos + ", endPos: " + endPos + ", capacity: " + capacity);
+ "startPos: " + startPos + ", endPos: " + endPos + ", capacity: " + capacity);
}
int limit = source.limit();
int position = source.position();
@@ -438,7 +442,7 @@ public class HapUtils {
int encodeSize = 0;
encodeSize += INT_SIZE + INT_SIZE;
for (Pair pair : pairList) {
- encodeSize += INT_SIZE+INT_SIZE+INT_SIZE + pair.getSecond().length;
+ encodeSize += INT_SIZE + INT_SIZE + INT_SIZE + pair.getSecond().length;
}
ByteBuffer encodeBytes = ByteBuffer.allocate(encodeSize);
encodeBytes.order(ByteOrder.LITTLE_ENDIAN);
@@ -446,7 +450,7 @@ public class HapUtils {
encodeBytes.putInt(BLOCK_NUMBER); // block number
for (Pair pair : pairList) {
byte[] second = pair.getSecond();
- encodeBytes.putInt(INT_SIZE+INT_SIZE + second.length);
+ encodeBytes.putInt(INT_SIZE + INT_SIZE + second.length);
encodeBytes.putInt(pair.getFirst());
encodeBytes.putInt(second.length);
encodeBytes.put(second);
@@ -507,11 +511,21 @@ public class HapUtils {
long hapSignBlockMagicLo = hapSigningBlockHeader.getLong();
long hapSignBlockMagicHi = hapSigningBlockHeader.getLong();
int version = hapSigningBlockHeader.getInt();
+ long hapSigningBlockOffset = verifySignBlock(hapSigBlockSize,
+ hapSignBlockMagicLo, hapSignBlockMagicHi, version, centralDirectoryStartOffset);
+ ByteBuffer hapSigningBlockByteBuffer = hap.createByteBuffer(hapSigningBlockOffset, (int) hapSigBlockSize)
+ .order(ByteOrder.LITTLE_ENDIAN);
+ LOGGER.info("Find Hap Signing Block success, version: {}, block count: {}", version, blockCount);
+ return new HapSignBlockInfo(hapSigningBlockOffset, version, hapSigningBlockByteBuffer);
+ }
+
+ private static long verifySignBlock(long hapSigBlockSize, long hapSignBlockMagicLo,
+ long hapSignBlockMagicHi, int version, long centralDirectoryStartOffset) throws SignatureNotFoundException {
if (!isVersionAndMagicNumValid(version, hapSignBlockMagicLo, hapSignBlockMagicHi)) {
throw new SignatureNotFoundException("No Hap Signing Block before ZIP Central Directory");
}
- if ((hapSigBlockSize < HAP_SIG_BLOCK_HEADER_SIZE) ||
- (hapSigBlockSize > Integer.MAX_VALUE - SignHap.getBlockSize())) {
+ if ((hapSigBlockSize < HAP_SIG_BLOCK_HEADER_SIZE)
+ || (hapSigBlockSize > Integer.MAX_VALUE - SignHap.getBlockSize())) {
throw new SignatureNotFoundException("Hap Signing Block size out of range: " + hapSigBlockSize);
}
int totalSize = (int) hapSigBlockSize;
@@ -519,10 +533,7 @@ public class HapUtils {
if (hapSigningBlockOffset < 0) {
throw new SignatureNotFoundException("Hap Signing Block offset out of range: " + hapSigningBlockOffset);
}
- ByteBuffer hapSigningBlockByteBuffer = hap.createByteBuffer(hapSigningBlockOffset, totalSize)
- .order(ByteOrder.LITTLE_ENDIAN);
- LOGGER.info("Find Hap Signing Block success, version: {}, block count: {}", version, blockCount);
- return new HapSignBlockInfo(hapSigningBlockOffset, version, hapSigningBlockByteBuffer);
+ return hapSigningBlockOffset;
}
private static boolean isVersionAndMagicNumValid(int version, long hapSignBlockMagicLo, long hapSignBlockMagicHi) {
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..e856abe65d9af2f2033b0febaab2f58ab94f6604 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
@@ -136,6 +136,11 @@ public class ParamConstants {
*/
public static final String PARAM_BASIC_PROFILE = "profileFile";
+ /**
+ * json type content of Hap-file's capability profile
+ */
+ public static final String PARAM_PROFILE_JSON_CONTENT = "profileContent";
+
/**
* Hap-file's proof-of-rotation
*/
@@ -236,12 +241,27 @@ 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_SIGN_CODE = "signCode";
+
+ /**
+ * file name split . of min length
+ */
+ public static final int FILE_NAME_MIN_LENGTH = 2;
+
/**
* Enumerated value of whether a profile is signed
*/
public enum ProfileSignFlag {
- UNSIGNED_PROFILE("0"),
- SIGNED_PROFILE("1");
+ DISABLE_SIGN_CODE("0"),
+ ENABLE_SIGN_CODE("1");
private String signFlag;
@@ -253,4 +273,22 @@ public class ParamConstants {
return signFlag;
}
}
+
+ /**
+ * Enumerated value of whether a code sign is signed.
+ */
+ public enum SignCodeFlag {
+ DISABLE_SIGN_CODE("0"),
+ ENABLE_SIGN_CODE("1");
+
+ private String signCodeFlag;
+
+ SignCodeFlag(String signCodeFlag) {
+ this.signCodeFlag = signCodeFlag;
+ }
+
+ public String getSignCodeFlag() {
+ return signCodeFlag;
+ }
+ }
}
\ No newline at end of file
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/CentralDirectory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/CentralDirectory.java
new file mode 100644
index 0000000000000000000000000000000000000000..04adc9b4260579ab487baa6b3a972068313d03bd
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/CentralDirectory.java
@@ -0,0 +1,409 @@
+/*
+ * 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.zip;
+
+import com.ohos.hapsigntool.error.ZipException;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * resolve zip CentralDirectory data
+ * CentralDirectory format for:
+ * central file header signature 4 bytes (0x02014b50)
+ * version made by 2 bytes
+ * version needed to extract 2 bytes
+ * general purpose bit flag 2 bytes
+ * compression method 2 bytes
+ * last mod file time 2 bytes
+ * last mod file date 2 bytes
+ * crc-32 4 bytes
+ * compressed size 4 bytes
+ * uncompressed size 4 bytes
+ * file name length 2 bytes
+ * extra field length 2 bytes
+ * file comment length 2 bytes
+ * disk number start 2 bytes
+ * internal file attributes 2 bytes
+ * external file attributes 4 bytes
+ * relative offset of local header 4 bytes
+ * file name (variable size)
+ * extra field (variable size)
+ * file comment (variable size)
+ *
+ * @since 2023/12/02
+ */
+public class CentralDirectory {
+ /**
+ * central directory invariable bytes length
+ */
+ public static final int CD_LENGTH = 46;
+
+ /**
+ * 4 bytes , central directory signature
+ */
+ public static final int SIGNATURE = 0x02014b50;
+
+ /**
+ * 2 bytes
+ */
+ private short version;
+
+ /**
+ * 2 bytes
+ */
+ private short versionExtra;
+
+ /**
+ * 2 bytes
+ */
+ private short flag;
+
+ /**
+ * 2 bytes
+ */
+ private short method;
+
+ /**
+ * 2 bytes
+ */
+ private short lastTime;
+
+ /**
+ * 2 bytes
+ */
+ private short lastDate;
+
+ /**
+ * 4 bytes
+ */
+ private int crc32;
+
+ /**
+ * 4 bytes
+ */
+ private long compressedSize;
+
+ /**
+ * 4 bytes
+ */
+ private long unCompressedSize;
+
+ /**
+ * 2 bytes
+ */
+ private int fileNameLength;
+
+ /**
+ * 2 bytes
+ */
+ private int extraLength;
+
+ /**
+ * 2 bytes
+ */
+ private int commentLength;
+
+ /**
+ * 2 bytes
+ */
+ private int diskNumStart;
+
+ /**
+ * 2 bytes
+ */
+ private short internalFile;
+
+ /**
+ * 4 bytes
+ */
+ private int externalFile;
+
+ /**
+ * 4 bytes
+ */
+ private long offset;
+
+ /**
+ * n bytes
+ */
+ private String fileName;
+
+ /**
+ * n bytes
+ */
+ private byte[] extraData;
+
+ /**
+ * n bytes
+ */
+ private byte[] comment;
+
+ private int length;
+
+ /**
+ * get Central Directory
+ *
+ * @param bf ByteBuffer
+ * @return CentralDirectory
+ * @throws ZipException read Central Directory exception
+ */
+ public static CentralDirectory getCentralDirectory(ByteBuffer bf) throws ZipException {
+ CentralDirectory cd = new CentralDirectory();
+ if (bf.getInt() != SIGNATURE) {
+ throw new ZipException("find zip central directory failed");
+ }
+
+ cd.setVersion(bf.getShort());
+ cd.setVersionExtra(bf.getShort());
+ cd.setFlag(bf.getShort());
+ cd.setMethod(bf.getShort());
+ cd.setLastTime(bf.getShort());
+ cd.setLastDate(bf.getShort());
+ cd.setCrc32(bf.getInt());
+ cd.setCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf));
+ cd.setUnCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf));
+ cd.setFileNameLength(UnsignedDecimalUtil.getUnsignedShort(bf));
+ cd.setExtraLength(UnsignedDecimalUtil.getUnsignedShort(bf));
+ cd.setCommentLength(UnsignedDecimalUtil.getUnsignedShort(bf));
+ cd.setDiskNumStart(UnsignedDecimalUtil.getUnsignedShort(bf));
+ cd.setInternalFile(bf.getShort());
+ cd.setExternalFile(bf.getInt());
+ cd.setOffset(UnsignedDecimalUtil.getUnsignedInt(bf));
+ if (cd.getFileNameLength() > 0) {
+ byte[] readFileName = new byte[cd.getFileNameLength()];
+ bf.get(readFileName);
+ cd.setFileName(new String(readFileName, StandardCharsets.UTF_8));
+ }
+ if (cd.getExtraLength() > 0) {
+ byte[] extra = new byte[cd.getExtraLength()];
+ bf.get(extra);
+ cd.setExtraData(extra);
+ }
+ if (cd.getCommentLength() > 0) {
+ byte[] readComment = new byte[cd.getCommentLength()];
+ bf.get(readComment);
+ cd.setComment(readComment);
+ }
+ cd.setLength(CD_LENGTH + cd.getFileNameLength() + cd.getExtraLength() + cd.getCommentLength());
+ return cd;
+ }
+
+ /**
+ * change Central Directory to bytes
+ *
+ * @return bytes
+ */
+ public byte[] toBytes() {
+ ByteBuffer bf = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN);
+ bf.putInt(SIGNATURE);
+ UnsignedDecimalUtil.setUnsignedShort(bf, version);
+ UnsignedDecimalUtil.setUnsignedShort(bf, versionExtra);
+ UnsignedDecimalUtil.setUnsignedShort(bf, flag);
+ UnsignedDecimalUtil.setUnsignedShort(bf, method);
+ UnsignedDecimalUtil.setUnsignedShort(bf, lastTime);
+ UnsignedDecimalUtil.setUnsignedShort(bf, lastDate);
+ UnsignedDecimalUtil.setUnsignedInt(bf, crc32);
+ UnsignedDecimalUtil.setUnsignedInt(bf, compressedSize);
+ UnsignedDecimalUtil.setUnsignedInt(bf, unCompressedSize);
+ UnsignedDecimalUtil.setUnsignedShort(bf, fileNameLength);
+ UnsignedDecimalUtil.setUnsignedShort(bf, extraLength);
+ UnsignedDecimalUtil.setUnsignedShort(bf, commentLength);
+ UnsignedDecimalUtil.setUnsignedShort(bf, diskNumStart);
+ UnsignedDecimalUtil.setUnsignedShort(bf, internalFile);
+ UnsignedDecimalUtil.setUnsignedInt(bf, externalFile);
+ UnsignedDecimalUtil.setUnsignedInt(bf, offset);
+ if (fileNameLength > 0) {
+ bf.put(fileName.getBytes(StandardCharsets.UTF_8));
+ }
+ if (extraLength > 0) {
+ bf.put(extraData);
+ }
+ if (commentLength > 0) {
+ bf.put(extraData);
+ }
+ return bf.array();
+ }
+
+ public static int getCdLength() {
+ return CD_LENGTH;
+ }
+
+ public static int getSIGNATURE() {
+ return SIGNATURE;
+ }
+
+ public short getVersion() {
+ return version;
+ }
+
+ public void setVersion(short version) {
+ this.version = version;
+ }
+
+ public short getVersionExtra() {
+ return versionExtra;
+ }
+
+ public void setVersionExtra(short versionExtra) {
+ this.versionExtra = versionExtra;
+ }
+
+ public short getFlag() {
+ return flag;
+ }
+
+ public void setFlag(short flag) {
+ this.flag = flag;
+ }
+
+ public short getMethod() {
+ return method;
+ }
+
+ public void setMethod(short method) {
+ this.method = method;
+ }
+
+ public short getLastTime() {
+ return lastTime;
+ }
+
+ public void setLastTime(short lastTime) {
+ this.lastTime = lastTime;
+ }
+
+ public short getLastDate() {
+ return lastDate;
+ }
+
+ public void setLastDate(short lastDate) {
+ this.lastDate = lastDate;
+ }
+
+ public int getCrc32() {
+ return crc32;
+ }
+
+ public void setCrc32(int crc32) {
+ this.crc32 = crc32;
+ }
+
+ public long getCompressedSize() {
+ return compressedSize;
+ }
+
+ public void setCompressedSize(long compressedSize) {
+ this.compressedSize = compressedSize;
+ }
+
+ public long getUnCompressedSize() {
+ return unCompressedSize;
+ }
+
+ public void setUnCompressedSize(long unCompressedSize) {
+ this.unCompressedSize = unCompressedSize;
+ }
+
+ public int getFileNameLength() {
+ return fileNameLength;
+ }
+
+ public void setFileNameLength(int fileNameLength) {
+ this.fileNameLength = fileNameLength;
+ }
+
+ public int getExtraLength() {
+ return extraLength;
+ }
+
+ public void setExtraLength(int extraLength) {
+ this.extraLength = extraLength;
+ }
+
+ public int getCommentLength() {
+ return commentLength;
+ }
+
+ public void setCommentLength(int commentLength) {
+ this.commentLength = commentLength;
+ }
+
+ public int getDiskNumStart() {
+ return diskNumStart;
+ }
+
+ public void setDiskNumStart(int diskNumStart) {
+ this.diskNumStart = diskNumStart;
+ }
+
+ public short getInternalFile() {
+ return internalFile;
+ }
+
+ public void setInternalFile(short internalFile) {
+ this.internalFile = internalFile;
+ }
+
+ public int getExternalFile() {
+ return externalFile;
+ }
+
+ public void setExternalFile(int externalFile) {
+ this.externalFile = externalFile;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ public void setOffset(long offset) {
+ this.offset = offset;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+ public byte[] getExtraData() {
+ return extraData;
+ }
+
+ public void setExtraData(byte[] extraData) {
+ this.extraData = extraData;
+ }
+
+ public byte[] getComment() {
+ return comment;
+ }
+
+ public void setComment(byte[] comment) {
+ this.comment = comment;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public void setLength(int length) {
+ this.length = length;
+ }
+}
\ No newline at end of file
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/DataDescriptor.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/DataDescriptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb521bf5cad9dd9074172b11a4138597cd460d17
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/DataDescriptor.java
@@ -0,0 +1,126 @@
+/*
+ * 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.zip;
+
+import com.ohos.hapsigntool.error.ZipException;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * resolve zip DataDescriptor data
+ * DataDescriptor format:
+ * crc-32 4 bytes
+ * compressed size 4 bytes
+ * uncompressed size 4 bytes
+ *
+ * @since 2023/12/02
+ */
+public class DataDescriptor {
+ /**
+ * DataDescriptor invariable bytes length
+ */
+ public static final int DES_LENGTH = 16;
+
+ /**
+ * 4 bytes , DataDescriptor signature
+ */
+ public static final int SIGNATURE = 0x08074b50;
+
+ /**
+ * 4 bytes
+ */
+ private int crc32;
+
+ /**
+ * 4 bytes
+ */
+ private long compressedSize;
+
+ /**
+ * 4 bytes
+ */
+ private long unCompressedSize;
+
+ /**
+ * get Data Descriptor
+ *
+ * @param bytes DataDescriptor bytes
+ * @return DataDescriptor
+ * @throws ZipException read data descriptor exception
+ */
+ public static DataDescriptor getDataDescriptor(byte[] bytes) throws ZipException {
+ if (bytes.length != DES_LENGTH) {
+ throw new ZipException("read Data Descriptor failed");
+ }
+ ByteBuffer bf = ByteBuffer.wrap(bytes);
+ bf.order(ByteOrder.LITTLE_ENDIAN);
+ DataDescriptor data = new DataDescriptor();
+ if (bf.getInt() != SIGNATURE) {
+ throw new ZipException("read Data Descriptor failed");
+ }
+ data.setCrc32(bf.getInt());
+ data.setCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf));
+ data.setUnCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf));
+ return data;
+ }
+
+ /**
+ * change DataDescriptor to bytes
+ *
+ * @return bytes
+ */
+ public byte[] toBytes() {
+ ByteBuffer bf = ByteBuffer.allocate(DES_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
+ bf.putInt(SIGNATURE);
+ bf.putInt(crc32);
+ UnsignedDecimalUtil.setUnsignedInt(bf, compressedSize);
+ UnsignedDecimalUtil.setUnsignedInt(bf, unCompressedSize);
+ return bf.array();
+ }
+
+ public static int getDesLength() {
+ return DES_LENGTH;
+ }
+
+ public static int getSIGNATURE() {
+ return SIGNATURE;
+ }
+
+ public int getCrc32() {
+ return crc32;
+ }
+
+ public void setCrc32(int crc32) {
+ this.crc32 = crc32;
+ }
+
+ public long getCompressedSize() {
+ return compressedSize;
+ }
+
+ public void setCompressedSize(long compressedSize) {
+ this.compressedSize = compressedSize;
+ }
+
+ public long getUnCompressedSize() {
+ return unCompressedSize;
+ }
+
+ public void setUnCompressedSize(long unCompressedSize) {
+ this.unCompressedSize = unCompressedSize;
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EndOfCentralDirectory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EndOfCentralDirectory.java
new file mode 100644
index 0000000000000000000000000000000000000000..92d211b305612c48bfd453e3478ab2ab3d7be970
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EndOfCentralDirectory.java
@@ -0,0 +1,245 @@
+/*
+ * 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.zip;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Optional;
+
+/**
+ * resolve zip EndOfCentralDirectory data
+ * EndOfCentralDirectory format for:
+ * end of central dir signature 4 bytes (0x06054b50)
+ * number of this disk 2 bytes
+ * number of the disk with the
+ * start of the central directory 2 bytes
+ * total number of entries in the
+ * central directory on this disk 2 bytes
+ * total number of entries in
+ * the central directory 2 bytes
+ * size of the central directory 4 bytes
+ * offset of start of central
+ * directory with respect to
+ * the starting disk number 4 bytes
+ * .ZIP file comment length 2 bytes
+ * .ZIP file comment (variable size)
+ *
+ * @since 2023/12/04
+ */
+public class EndOfCentralDirectory {
+ /**
+ * EndOfCentralDirectory invariable bytes length
+ */
+ public static final int EOCD_LENGTH = 22;
+
+ /**
+ * 4 bytes , central directory signature
+ */
+ public static final int SIGNATURE = 0x06054b50;
+
+ /**
+ * 2 bytes
+ */
+ private int diskNum;
+
+ /**
+ * 2 bytes
+ */
+ private int cDStartDiskNum;
+
+ /**
+ * 2 bytes
+ */
+ private int thisDiskCDNum;
+
+ /**
+ * 2 bytes
+ */
+ private int cDTotal;
+
+ /**
+ * 4 bytes
+ */
+ private long cDSize;
+
+ /**
+ * 4 bytes
+ */
+ private long offset;
+
+ /**
+ * 2 bytes
+ */
+ private int commentLength;
+
+ /**
+ * n bytes
+ */
+ private byte[] comment;
+
+ private int length;
+
+ /**
+ * init End Of Central Directory, default offset is 0
+ *
+ * @param bytes End Of Central Directory bytes
+ * @return End Of Central Directory
+ */
+ public static Optional getEOCDByBytes(byte[] bytes) {
+ return getEOCDByBytes(bytes, 0);
+ }
+
+ /**
+ * init End Of Central Directory
+ *
+ * @param bytes End Of Central Directory bytes
+ * @param offset offset
+ * @return End Of Central Directory
+ */
+ public static Optional getEOCDByBytes(byte[] bytes, int offset) {
+ EndOfCentralDirectory eocd = new EndOfCentralDirectory();
+ int remainingDataLen = bytes.length - offset;
+ if (remainingDataLen < EOCD_LENGTH) {
+ return Optional.empty();
+ }
+ ByteBuffer bf = ByteBuffer.wrap(bytes, offset, remainingDataLen);
+ bf.order(ByteOrder.LITTLE_ENDIAN);
+ if (bf.getInt() != SIGNATURE) {
+ return Optional.empty();
+ }
+ eocd.setDiskNum(UnsignedDecimalUtil.getUnsignedShort(bf));
+ eocd.setcDStartDiskNum(UnsignedDecimalUtil.getUnsignedShort(bf));
+ eocd.setThisDiskCDNum(UnsignedDecimalUtil.getUnsignedShort(bf));
+ eocd.setcDTotal(UnsignedDecimalUtil.getUnsignedShort(bf));
+ eocd.setcDSize(UnsignedDecimalUtil.getUnsignedInt(bf));
+ eocd.setOffset(UnsignedDecimalUtil.getUnsignedInt(bf));
+ eocd.setCommentLength(UnsignedDecimalUtil.getUnsignedShort(bf));
+ if (bf.remaining() != eocd.getCommentLength()) {
+ return Optional.empty();
+ }
+ if (eocd.getCommentLength() > 0) {
+ byte[] readComment = new byte[eocd.getCommentLength()];
+ bf.get(readComment);
+ eocd.setComment(readComment);
+ }
+ eocd.setLength(EOCD_LENGTH + eocd.getCommentLength());
+ if (bf.remaining() != 0) {
+ return Optional.empty();
+ }
+ return Optional.of(eocd);
+ }
+
+ /**
+ * change End Of Central Directory to bytes
+ *
+ * @return bytes
+ */
+ public byte[] toBytes() {
+ ByteBuffer bf = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN);
+ bf.putInt(SIGNATURE);
+ UnsignedDecimalUtil.setUnsignedShort(bf, diskNum);
+ UnsignedDecimalUtil.setUnsignedShort(bf, cDStartDiskNum);
+ UnsignedDecimalUtil.setUnsignedShort(bf, thisDiskCDNum);
+ UnsignedDecimalUtil.setUnsignedShort(bf, cDTotal);
+ UnsignedDecimalUtil.setUnsignedInt(bf, cDSize);
+ UnsignedDecimalUtil.setUnsignedInt(bf, offset);
+ UnsignedDecimalUtil.setUnsignedShort(bf, commentLength);
+ if (commentLength > 0) {
+ bf.put(comment);
+ }
+ return bf.array();
+ }
+
+ public static int getEocdLength() {
+ return EOCD_LENGTH;
+ }
+
+ public static int getSIGNATURE() {
+ return SIGNATURE;
+ }
+
+ public int getDiskNum() {
+ return diskNum;
+ }
+
+ public void setDiskNum(int diskNum) {
+ this.diskNum = diskNum;
+ }
+
+ public int getcDStartDiskNum() {
+ return cDStartDiskNum;
+ }
+
+ public void setcDStartDiskNum(int cDStartDiskNum) {
+ this.cDStartDiskNum = cDStartDiskNum;
+ }
+
+ public int getThisDiskCDNum() {
+ return thisDiskCDNum;
+ }
+
+ public void setThisDiskCDNum(int thisDiskCDNum) {
+ this.thisDiskCDNum = thisDiskCDNum;
+ }
+
+ public int getcDTotal() {
+ return cDTotal;
+ }
+
+ public void setcDTotal(int cDTotal) {
+ this.cDTotal = cDTotal;
+ }
+
+ public long getcDSize() {
+ return cDSize;
+ }
+
+ public void setcDSize(long cDSize) {
+ this.cDSize = cDSize;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ public void setOffset(long offset) {
+ this.offset = offset;
+ }
+
+ public int getCommentLength() {
+ return commentLength;
+ }
+
+ public void setCommentLength(int commentLength) {
+ this.commentLength = commentLength;
+ }
+
+ public byte[] getComment() {
+ return comment;
+ }
+
+ public void setComment(byte[] comment) {
+ this.comment = comment;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public void setLength(int length) {
+ this.length = length;
+ }
+}
\ No newline at end of file
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/UnsignedDecimalUtil.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/UnsignedDecimalUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..0625b2fc78f54d04227f2d031f541b0fed694ee3
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/UnsignedDecimalUtil.java
@@ -0,0 +1,91 @@
+/*
+ * 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.zip;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Unsigned Decimal Util
+ *
+ * @since 2023/12/09
+ */
+public class UnsignedDecimalUtil {
+ /**
+ * max unsigned int value
+ */
+ public static final long MAX_UNSIGNED_INT_VALUE = 0xFFFFFFFFL;
+
+ /**
+ * max unsigned int value
+ */
+ public static final int MAX_UNSIGNED_SHORT_VALUE = 0xFFFF;
+
+ private static final int BIT_SIZE = 8;
+
+ private static final int DOUBLE_BIT_SIZE = 16;
+
+ private static final int TRIPLE_BIT_SIZE = 24;
+
+ /**
+ * get unsigned int to long
+ *
+ * @param bf byteBuffer
+ * @return long
+ */
+ public static long getUnsignedInt(ByteBuffer bf) {
+ return bf.getInt() & MAX_UNSIGNED_INT_VALUE;
+ }
+
+ /**
+ * get unsigned short to int
+ *
+ * @param bf byteBuffer
+ * @return int
+ */
+ public static int getUnsignedShort(ByteBuffer bf) {
+ return bf.getShort() & MAX_UNSIGNED_SHORT_VALUE;
+ }
+
+ /**
+ * set long to unsigned int
+ *
+ * @param bf byteBuffer
+ * @param value long
+ */
+ public static void setUnsignedInt(ByteBuffer bf, long value) {
+ byte[] bytes = new byte[] {
+ (byte) (value & 0xFF),
+ (byte) ((value >> BIT_SIZE) & 0xFF),
+ (byte) ((value >> DOUBLE_BIT_SIZE) & 0xFF),
+ (byte) ((value >> TRIPLE_BIT_SIZE) & 0xFF)
+ };
+ bf.put(bytes);
+ }
+
+ /**
+ * set int to unsigned short
+ *
+ * @param bf byteBuffer
+ * @param value int
+ */
+ public static void setUnsignedShort(ByteBuffer bf, int value) {
+ byte[] bytes = new byte[] {
+ (byte) (value & 0xFF),
+ (byte) ((value >> BIT_SIZE) & 0xFF)
+ };
+ bf.put(bytes);
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/Zip.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/Zip.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ab9bdc2ed2b9a31894b6837620bd6b1d93411f9
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/Zip.java
@@ -0,0 +1,364 @@
+/*
+ * 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.zip;
+
+import com.ohos.hapsigntool.error.CustomException;
+import com.ohos.hapsigntool.error.ERROR;
+import com.ohos.hapsigntool.error.ZipException;
+import com.ohos.hapsigntool.utils.FileUtils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * resolve zip data
+ *
+ * @since 2023/12/02
+ */
+public class Zip {
+ private static final Logger LOGGER = LogManager.getLogger(Zip.class);
+
+ /**
+ * file is uncompress file flag
+ */
+ public static final int FILE_UNCOMPRESS_METHOD_FLAG = 0;
+
+ /**
+ * max comment length
+ */
+ public static final int MAX_COMMENT_LENGTH = 65535;
+
+ private List zipEntries;
+
+ private long signingOffset;
+
+ private byte[] signingBlock;
+
+ private long cDOffset;
+
+ private long eOCDOffset;
+
+ private EndOfCentralDirectory endOfCentralDirectory;
+
+ private String file;
+
+ /**
+ * create Zip by file
+ *
+ * @param inputFile file
+ */
+ public Zip(File inputFile) {
+ try {
+ this.file = inputFile.getCanonicalPath();
+ if (!inputFile.exists()) {
+ throw new ZipException("read zip file failed");
+ }
+ long start = System.currentTimeMillis();
+ // 1. get eocd data
+ endOfCentralDirectory = getZipEndOfCentralDirectory(inputFile);
+ cDOffset = endOfCentralDirectory.getOffset();
+ long eocdEnd = System.currentTimeMillis();
+ LOGGER.debug("getZipEndOfCentralDirectory use {} ms", eocdEnd - start);
+ // 2. use eocd's cd offset, get cd data
+ getZipCentralDirectory(inputFile);
+ long cdEnd = System.currentTimeMillis();
+ LOGGER.debug("getZipCentralDirectory use {} ms", cdEnd - start);
+ // 3. use cd's entry offset and file size, get entry data
+ getZipEntries(inputFile);
+ ZipEntry endEntry = zipEntries.get(zipEntries.size() - 1);
+ CentralDirectory endCD = endEntry.getCentralDirectory();
+ ZipEntryData endEntryData = endEntry.getZipEntryData();
+ signingOffset = endCD.getOffset() + endEntryData.getLength();
+ long entryEnd = System.currentTimeMillis();
+ LOGGER.debug("getZipEntries use {} ms", entryEnd - start);
+ // 4. file all data - eocd - cd - entry = sign block
+ signingBlock = getSigningBlock(inputFile);
+ } catch (IOException e) {
+ CustomException.throwException(ERROR.ZIP_ERROR, e.getMessage());
+ }
+ }
+
+ private EndOfCentralDirectory getZipEndOfCentralDirectory(File file) throws IOException {
+ if (file.length() < EndOfCentralDirectory.EOCD_LENGTH) {
+ throw new ZipException("find zip eocd failed");
+ }
+
+ // try to read EOCD without comment
+ int eocdLength = EndOfCentralDirectory.EOCD_LENGTH;
+ eOCDOffset = file.length() - eocdLength;
+ byte[] bytes = FileUtils.readFileByOffsetAndLength(file, eOCDOffset, eocdLength);
+ Optional eocdByBytes = EndOfCentralDirectory.getEOCDByBytes(bytes);
+ if (eocdByBytes.isPresent()) {
+ return eocdByBytes.get();
+ }
+
+ // try to search EOCD with comment
+ long eocdMaxLength = Math.min(EndOfCentralDirectory.EOCD_LENGTH + MAX_COMMENT_LENGTH, file.length());
+ eOCDOffset = file.length() - eocdMaxLength;
+ bytes = FileUtils.readFileByOffsetAndLength(file, eOCDOffset, eocdMaxLength);
+ for (int start = 0; start < eocdMaxLength; start++) {
+ eocdByBytes = EndOfCentralDirectory.getEOCDByBytes(bytes, start);
+ if (eocdByBytes.isPresent()) {
+ eOCDOffset += start;
+ return eocdByBytes.get();
+ }
+ }
+ throw new ZipException("read zip failed: can not find eocd in file");
+ }
+
+ private void getZipCentralDirectory(File file) throws IOException {
+ zipEntries = new ArrayList<>(endOfCentralDirectory.getcDTotal());
+ // read full central directory bytes
+ byte[] cdBytes = FileUtils.readFileByOffsetAndLength(file, cDOffset, endOfCentralDirectory.getcDSize());
+ if (cdBytes.length < CentralDirectory.CD_LENGTH) {
+ throw new ZipException("find zip cd failed");
+ }
+ ByteBuffer bf = ByteBuffer.wrap(cdBytes);
+ bf.order(ByteOrder.LITTLE_ENDIAN);
+ int offset = 0;
+ // one by one format central directory
+ while (offset < cdBytes.length) {
+ CentralDirectory cd = CentralDirectory.getCentralDirectory(bf);
+ ZipEntry entry = new ZipEntry();
+ entry.setCentralDirectory(cd);
+ zipEntries.add(entry);
+ offset += cd.getLength();
+ }
+ if (offset + cDOffset != eOCDOffset) {
+ throw new ZipException("cd end offset not equals to eocd offset, maybe this is a zip64 file");
+ }
+ }
+
+ private byte[] getSigningBlock(File file) throws IOException {
+ long size = cDOffset - signingOffset;
+ if (size < 0) {
+ throw new ZipException("signing offset in front of entry end");
+ }
+ if (size == 0) {
+ return new byte[0];
+ }
+ return FileUtils.readFileByOffsetAndLength(file, signingOffset, size);
+ }
+
+ private void getZipEntries(File file) throws IOException {
+ // use central directory data, find entry data
+ for (ZipEntry entry : zipEntries) {
+ CentralDirectory cd = entry.getCentralDirectory();
+ long offset = cd.getOffset();
+ long unCompressedSize = cd.getUnCompressedSize();
+ long compressedSize = cd.getCompressedSize();
+ long fileSize = cd.getMethod() == FILE_UNCOMPRESS_METHOD_FLAG ? unCompressedSize : compressedSize;
+
+ ZipEntryData zipEntryData = ZipEntryData.getZipEntry(file, offset, fileSize);
+ if (cDOffset - offset < zipEntryData.getLength()) {
+ throw new ZipException("cd offset in front of entry end");
+ }
+ entry.setZipEntryData(zipEntryData);
+ }
+ }
+
+ /**
+ * output zip to zip file
+ *
+ * @param outFile file path
+ */
+ public void toFile(String outFile) {
+ try (FileOutputStream fos = new FileOutputStream(outFile)) {
+ for (ZipEntry entry : zipEntries) {
+ ZipEntryData zipEntryData = entry.getZipEntryData();
+ FileUtils.writeByteToOutFile(zipEntryData.getZipEntryHeader().toBytes(), fos);
+ boolean isSuccess = FileUtils.appendWriteFileByOffsetToFile(file, fos,
+ zipEntryData.getFileOffset(), zipEntryData.getFileSize());
+ if (!isSuccess) {
+ throw new ZipException("write zip data failed");
+ }
+ if (zipEntryData.getDataDescriptor() != null) {
+ FileUtils.writeByteToOutFile(zipEntryData.getDataDescriptor().toBytes(), fos);
+ }
+ }
+ if (signingBlock != null) {
+ FileUtils.writeByteToOutFile(signingBlock, fos);
+ }
+ for (ZipEntry entry : zipEntries) {
+ CentralDirectory cd = entry.getCentralDirectory();
+ FileUtils.writeByteToOutFile(cd.toBytes(), fos);
+ }
+ FileUtils.writeByteToOutFile(endOfCentralDirectory.toBytes(), fos);
+ } catch (IOException e) {
+ CustomException.throwException(ERROR.ZIP_ERROR, e.getMessage());
+ }
+ }
+
+ /**
+ * alignment uncompress entry
+ *
+ * @param alignment int alignment
+ */
+ public void alignment(int alignment) {
+ try {
+ sort();
+ boolean isFirstUnRunnableFile = true;
+ for (ZipEntry entry : zipEntries) {
+ ZipEntryData zipEntryData = entry.getZipEntryData();
+ short method = zipEntryData.getZipEntryHeader().getMethod();
+ if (method != FILE_UNCOMPRESS_METHOD_FLAG && !isFirstUnRunnableFile) {
+ // only align uncompressed entry and the first compress entry.
+ break;
+ }
+ int alignBytes;
+ if (method == FILE_UNCOMPRESS_METHOD_FLAG && FileUtils.isRunnableFile(
+ zipEntryData.getZipEntryHeader().getFileName())) {
+ // .abc and .so file align 4096 byte.
+ alignBytes = 4096;
+ } else if (isFirstUnRunnableFile) {
+ // the first file after runnable file, align 4096 byte.
+ alignBytes = 4096;
+ isFirstUnRunnableFile = false;
+ } else {
+ // normal file align 4 byte.
+ alignBytes = alignment;
+ }
+ int add = entry.alignment(alignBytes);
+ if (add > 0) {
+ resetOffset();
+ }
+ }
+ } catch (ZipException e) {
+ CustomException.throwException(ERROR.ZIP_ERROR, e.getMessage());
+ }
+ }
+
+ /**
+ * remove sign block
+ */
+ public void removeSignBlock() {
+ signingBlock = null;
+ resetOffset();
+ }
+
+ /**
+ * sort uncompress entry in the front.
+ */
+ private void sort() {
+ // sort uncompress file (so, abc, an) - other uncompress file - compress file
+ zipEntries.sort((entry1, entry2) -> {
+ short entry1Method = entry1.getZipEntryData().getZipEntryHeader().getMethod();
+ short entry2Method = entry2.getZipEntryData().getZipEntryHeader().getMethod();
+ String entry1FileName = entry1.getZipEntryData().getZipEntryHeader().getFileName();
+ String entry2FileName = entry2.getZipEntryData().getZipEntryHeader().getFileName();
+ if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG && entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) {
+ boolean isRunnableFile1 = FileUtils.isRunnableFile(entry1FileName);
+ boolean isRunnableFile2 = FileUtils.isRunnableFile(entry2FileName);
+ if (isRunnableFile1 && isRunnableFile2) {
+ return entry1FileName.compareTo(entry2FileName);
+ } else if (isRunnableFile1) {
+ return -1;
+ } else if (isRunnableFile2) {
+ return 1;
+ }
+ } else if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG) {
+ return -1;
+ } else if (entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) {
+ return 1;
+ }
+ return entry1FileName.compareTo(entry2FileName);
+ });
+ resetOffset();
+ }
+
+ private void resetOffset() {
+ long offset = 0L;
+ long cdLength = 0L;
+ for (ZipEntry entry : zipEntries) {
+ entry.getCentralDirectory().setOffset(offset);
+ offset += entry.getZipEntryData().getLength();
+ cdLength += entry.getCentralDirectory().getLength();
+ }
+ if (signingBlock != null) {
+ offset += signingBlock.length;
+ }
+ cDOffset = offset;
+ endOfCentralDirectory.setOffset(offset);
+ endOfCentralDirectory.setcDSize(cdLength);
+ offset += cdLength;
+ eOCDOffset = offset;
+ }
+
+ public List getZipEntries() {
+ return zipEntries;
+ }
+
+ public void setZipEntries(List zipEntries) {
+ this.zipEntries = zipEntries;
+ }
+
+ public long getSigningOffset() {
+ return signingOffset;
+ }
+
+ public void setSigningOffset(long signingOffset) {
+ this.signingOffset = signingOffset;
+ }
+
+ public byte[] getSigningBlock() {
+ return signingBlock;
+ }
+
+ public void setSigningBlock(byte[] signingBlock) {
+ this.signingBlock = signingBlock;
+ }
+
+ public long getCDOffset() {
+ return cDOffset;
+ }
+
+ public void setCDOffset(long cDOffset) {
+ this.cDOffset = cDOffset;
+ }
+
+ public long getEOCDOffset() {
+ return eOCDOffset;
+ }
+
+ public void setEOCDOffset(long eOCDOffset) {
+ this.eOCDOffset = eOCDOffset;
+ }
+
+ public EndOfCentralDirectory getEndOfCentralDirectory() {
+ return endOfCentralDirectory;
+ }
+
+ public void setEndOfCentralDirectory(EndOfCentralDirectory endOfCentralDirectory) {
+ this.endOfCentralDirectory = endOfCentralDirectory;
+ }
+
+ public String getFile() {
+ return file;
+ }
+
+ public void setFile(String file) {
+ this.file = file;
+ }
+}
\ No newline at end of file
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntry.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntry.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff66339ad690b1ddacba48b9ec301cb80c16dc5e
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntry.java
@@ -0,0 +1,118 @@
+/*
+ * 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.zip;
+
+import com.ohos.hapsigntool.error.ZipException;
+
+import java.util.Arrays;
+
+/**
+ * ZipEntry and CentralDirectory data
+ *
+ * @since 2023/12/02
+ */
+public class ZipEntry {
+ private ZipEntryData zipEntryData;
+
+ private CentralDirectory fileEntryIncentralDirectory;
+
+ /**
+ * alignment one entry
+ *
+ * @param alignNum need align bytes length
+ * @return add bytes length
+ * @throws ZipException alignment exception
+ */
+ public int alignment(int alignNum) throws ZipException {
+ // if cd extra len bigger than entry extra len, make cd and entry extra length equals
+ int padding = calZeroPaddingLengthForEntryExtra();
+ int remainder = (int) ((zipEntryData.getZipEntryHeader().getLength()
+ + fileEntryIncentralDirectory.getOffset()) % alignNum);
+
+ if (remainder == 0) {
+ return padding;
+ }
+ int add = alignNum - remainder;
+ int newExtraLength = zipEntryData.getZipEntryHeader().getExtraLength() + add;
+ if (newExtraLength > UnsignedDecimalUtil.MAX_UNSIGNED_SHORT_VALUE) {
+ throw new ZipException("can not align " + zipEntryData.getZipEntryHeader().getFileName());
+ }
+ setEntryHeaderNewExtraLength(newExtraLength);
+ setCenterDirectoryNewExtraLength(newExtraLength);
+
+ return add;
+ }
+
+ private int calZeroPaddingLengthForEntryExtra() throws ZipException {
+ int entryExtraLen = zipEntryData.getZipEntryHeader().getExtraLength();
+ int cdExtraLen = fileEntryIncentralDirectory.getExtraLength();
+ if (cdExtraLen > entryExtraLen) {
+ setEntryHeaderNewExtraLength(cdExtraLen);
+ return cdExtraLen - entryExtraLen;
+ }
+ if (cdExtraLen < entryExtraLen) {
+ setCenterDirectoryNewExtraLength(entryExtraLen);
+ return entryExtraLen - cdExtraLen;
+ }
+ return 0;
+ }
+
+ private void setCenterDirectoryNewExtraLength(int newLength) throws ZipException {
+ byte[] newCDExtra = getAlignmentNewExtra(newLength, fileEntryIncentralDirectory.getExtraData());
+ fileEntryIncentralDirectory.setExtraData(newCDExtra);
+ fileEntryIncentralDirectory.setExtraLength(newLength);
+ fileEntryIncentralDirectory.setLength(CentralDirectory.CD_LENGTH
+ + fileEntryIncentralDirectory.getFileNameLength()
+ + fileEntryIncentralDirectory.getExtraLength() + fileEntryIncentralDirectory.getCommentLength());
+ }
+
+ private void setEntryHeaderNewExtraLength(int newLength) throws ZipException {
+ ZipEntryHeader zipEntryHeader = zipEntryData.getZipEntryHeader();
+ byte[] newExtra = getAlignmentNewExtra(newLength, zipEntryHeader.getExtraData());
+ zipEntryHeader.setExtraData(newExtra);
+ zipEntryHeader.setExtraLength(newLength);
+ zipEntryHeader.setLength(ZipEntryHeader.HEADER_LENGTH + zipEntryHeader.getExtraLength()
+ + zipEntryHeader.getFileNameLength());
+ zipEntryData.setLength(zipEntryHeader.getLength() + zipEntryData.getFileSize()
+ + (zipEntryData.getDataDescriptor() == null ? 0 : DataDescriptor.DES_LENGTH));
+ }
+
+ private byte[] getAlignmentNewExtra(int newLength, byte[] old) throws ZipException {
+ if (old == null) {
+ return new byte[newLength];
+ }
+ if (newLength < old.length) {
+ throw new ZipException("can not align " + zipEntryData.getZipEntryHeader().getFileName());
+ }
+ return Arrays.copyOf(old, newLength);
+ }
+
+ public ZipEntryData getZipEntryData() {
+ return zipEntryData;
+ }
+
+ public void setZipEntryData(ZipEntryData zipEntryData) {
+ this.zipEntryData = zipEntryData;
+ }
+
+ public CentralDirectory getCentralDirectory() {
+ return fileEntryIncentralDirectory;
+ }
+
+ public void setCentralDirectory(CentralDirectory centralDirectory) {
+ this.fileEntryIncentralDirectory = centralDirectory;
+ }
+}
\ No newline at end of file
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryData.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryData.java
new file mode 100644
index 0000000000000000000000000000000000000000..35b19e7ad5833b31343f479a92d321a808507651
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryData.java
@@ -0,0 +1,143 @@
+/*
+ * 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.zip;
+
+import com.ohos.hapsigntool.utils.FileUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * resolve zip ZipEntry data
+ *
+ * @since 2023/12/04
+ */
+public class ZipEntryData {
+ /**
+ * data descriptor has or not mask
+ */
+ public static final short HAS_DATA_DESCRIPTOR_MASK = 0x08;
+
+ /**
+ * data descriptor has or not flag mask
+ */
+ public static final short NOT_HAS_DATA_DESCRIPTOR_FLAG = 0;
+
+ private ZipEntryHeader zipEntryHeader;
+
+ private long fileOffset;
+
+ private long fileSize;
+
+ private DataDescriptor dataDescriptor;
+
+ private long length;
+
+ public ZipEntryHeader getZipEntryHeader() {
+ return zipEntryHeader;
+ }
+
+ /**
+ * init zip entry by file
+ *
+ * @param file zip file
+ * @param entryOffset entry start offset
+ * @param fileSize compress file size
+ * @return zip entry
+ * @throws IOException read zip exception
+ */
+ public static ZipEntryData getZipEntry(File file, long entryOffset, long fileSize)
+ throws IOException {
+ try (FileInputStream input = new FileInputStream(file)) {
+ long offset = entryOffset;
+ // read entry header by file and offset.
+ byte[] headBytes = FileUtils.readInputByOffsetAndLength(input, entryOffset, ZipEntryHeader.HEADER_LENGTH);
+ ZipEntryHeader entryHeader = ZipEntryHeader.getZipEntryHeader(headBytes);
+ offset += ZipEntryHeader.HEADER_LENGTH;
+
+ // read entry file name and extra by offset.
+ if (entryHeader.getFileNameLength() > 0) {
+ byte[] fileNameBytes = FileUtils.readInputByLength(input, entryHeader.getFileNameLength());
+ entryHeader.readFileName(fileNameBytes);
+ offset += entryHeader.getFileNameLength();
+ }
+
+ if (entryHeader.getExtraLength() > 0) {
+ byte[] extraBytes = FileUtils.readInputByLength(input, entryHeader.getExtraLength());
+ entryHeader.readExtra(extraBytes);
+ offset += entryHeader.getExtraLength();
+ }
+
+ // skip file data , save file offset and size.
+ ZipEntryData entry = new ZipEntryData();
+ entry.setFileOffset(offset);
+ entry.setFileSize(fileSize);
+ input.skip(fileSize);
+
+ long entryLength = entryHeader.getLength() + fileSize;
+ short flag = entryHeader.getFlag();
+ // set desc null flag
+ boolean hasDesc = (flag & HAS_DATA_DESCRIPTOR_MASK) != NOT_HAS_DATA_DESCRIPTOR_FLAG;
+ if (hasDesc) {
+ // if entry has data descriptor, read entry data descriptor.
+ byte[] desBytes = FileUtils.readInputByLength(input, DataDescriptor.DES_LENGTH);
+ DataDescriptor dataDesc = DataDescriptor.getDataDescriptor(desBytes);
+ entryLength += DataDescriptor.DES_LENGTH;
+ entry.setDataDescriptor(dataDesc);
+ }
+ entry.setZipEntryHeader(entryHeader);
+ entry.setLength(entryLength);
+ return entry;
+ }
+ }
+
+ public void setZipEntryHeader(ZipEntryHeader zipEntryHeader) {
+ this.zipEntryHeader = zipEntryHeader;
+ }
+
+ public DataDescriptor getDataDescriptor() {
+ return dataDescriptor;
+ }
+
+ public void setDataDescriptor(DataDescriptor dataDescriptor) {
+ this.dataDescriptor = dataDescriptor;
+ }
+
+ public long getFileOffset() {
+ return fileOffset;
+ }
+
+ public void setFileOffset(long fileOffset) {
+ this.fileOffset = fileOffset;
+ }
+
+ public long getFileSize() {
+ return fileSize;
+ }
+
+ public void setFileSize(long fileSize) {
+ this.fileSize = fileSize;
+ }
+
+ public long getLength() {
+ return length;
+ }
+
+ public void setLength(long length) {
+ this.length = length;
+ }
+}
\ No newline at end of file
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c880a6bea5a14cb38fa5da1a9e7a777a0ef890f
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryHeader.java
@@ -0,0 +1,312 @@
+/*
+ * 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.zip;
+
+import com.ohos.hapsigntool.error.ZipException;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * resolve zip ZipEntryHeader data
+ * end of central dir signature 4 bytes (0x06054b50)
+ * number of this disk 2 bytes
+ * number of the disk with the
+ * start of the central directory 2 bytes
+ * total number of entries in the
+ * central directory on this disk 2 bytes
+ * total number of entries in
+ * the central directory 2 bytes
+ * size of the central directory 4 bytes
+ * offset of start of central
+ * directory with respect to
+ * the starting disk number 4 bytes
+ * .ZIP file comment length 2 bytes
+ * .ZIP file comment (variable size)
+ *
+ * @since 2023/12/02
+ */
+public class ZipEntryHeader {
+ /**
+ * ZipEntryHeader invariable bytes length
+ */
+ public static final int HEADER_LENGTH = 30;
+
+ /**
+ * 4 bytes , entry header signature
+ */
+ public static final int SIGNATURE = 0x04034b50;
+
+ /**
+ * 2 bytes
+ */
+ private short version;
+
+ /**
+ * 2 bytes
+ */
+ private short flag;
+
+ /**
+ * 2 bytes
+ */
+ private short method;
+
+ /**
+ * 2 bytes
+ */
+ private short lastTime;
+
+ /**
+ * 2 bytes
+ */
+ private short lastDate;
+
+ /**
+ * 4 bytes
+ */
+ private int crc32;
+
+ /**
+ * 4 bytes
+ */
+ private long compressedSize;
+
+ /**
+ * 4 bytes
+ */
+ private long unCompressedSize;
+
+ /**
+ * 2 bytes
+ */
+ private int fileNameLength;
+
+ /**
+ * 2 bytes
+ */
+ private int extraLength;
+
+ /**
+ * n bytes
+ */
+ private String fileName;
+
+ /**
+ * n bytes
+ */
+ private byte[] extraData;
+
+ private int length;
+
+ /**
+ * get Zip Entry Header
+ *
+ * @param bytes ZipEntryHeader bytes
+ * @return ZipEntryHeader
+ * @throws ZipException read entry header exception
+ */
+ public static ZipEntryHeader getZipEntryHeader(byte[] bytes) throws ZipException {
+ ZipEntryHeader entryHeader = new ZipEntryHeader();
+ ByteBuffer bf = ByteBuffer.wrap(bytes);
+ bf.order(ByteOrder.LITTLE_ENDIAN);
+ if (bf.getInt() != ZipEntryHeader.SIGNATURE) {
+ throw new ZipException("find zip entry head failed");
+ }
+ entryHeader.setVersion(bf.getShort());
+ entryHeader.setFlag(bf.getShort());
+ entryHeader.setMethod(bf.getShort());
+ entryHeader.setLastTime(bf.getShort());
+ entryHeader.setLastDate(bf.getShort());
+ entryHeader.setCrc32(bf.getInt());
+ entryHeader.setCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf));
+ entryHeader.setUnCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf));
+ entryHeader.setFileNameLength(UnsignedDecimalUtil.getUnsignedShort(bf));
+ entryHeader.setExtraLength(UnsignedDecimalUtil.getUnsignedShort(bf));
+ entryHeader.setLength(HEADER_LENGTH + entryHeader.getFileNameLength() + entryHeader.getExtraLength());
+ return entryHeader;
+ }
+
+ /**
+ * set entry header name
+ *
+ * @param bytes name bytes
+ */
+ public void readFileName(byte[] bytes) {
+ ByteBuffer bf = ByteBuffer.wrap(bytes);
+ bf.order(ByteOrder.LITTLE_ENDIAN);
+ if (fileNameLength > 0) {
+ byte[] nameBytes = new byte[fileNameLength];
+ bf.get(nameBytes);
+ this.fileName = new String(nameBytes, StandardCharsets.UTF_8);
+ }
+ }
+
+ /**
+ * set entry header extra
+ *
+ * @param bytes extra bytes
+ */
+ public void readExtra(byte[] bytes) {
+ ByteBuffer bf = ByteBuffer.wrap(bytes);
+ bf.order(ByteOrder.LITTLE_ENDIAN);
+ if (extraLength > 0) {
+ byte[] extra = new byte[extraLength];
+ bf.get(extra);
+ this.extraData = extra;
+ }
+ }
+
+ /**
+ * change Zip Entry Header to bytes
+ *
+ * @return bytes
+ */
+ public byte[] toBytes() {
+ ByteBuffer bf = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN);
+ bf.putInt(SIGNATURE);
+ bf.putShort(version);
+ bf.putShort(flag);
+ bf.putShort(method);
+ bf.putShort(lastTime);
+ bf.putShort(lastDate);
+ bf.putInt(crc32);
+ UnsignedDecimalUtil.setUnsignedInt(bf, compressedSize);
+ UnsignedDecimalUtil.setUnsignedInt(bf, unCompressedSize);
+ UnsignedDecimalUtil.setUnsignedShort(bf, fileNameLength);
+ UnsignedDecimalUtil.setUnsignedShort(bf, extraLength);
+ if (fileNameLength > 0) {
+ bf.put(fileName.getBytes(StandardCharsets.UTF_8));
+ }
+ if (extraLength > 0) {
+ bf.put(extraData);
+ }
+ return bf.array();
+ }
+
+ public static int getHeaderLength() {
+ return HEADER_LENGTH;
+ }
+
+ public static int getSIGNATURE() {
+ return SIGNATURE;
+ }
+
+ public short getVersion() {
+ return version;
+ }
+
+ public void setVersion(short version) {
+ this.version = version;
+ }
+
+ public short getFlag() {
+ return flag;
+ }
+
+ public void setFlag(short flag) {
+ this.flag = flag;
+ }
+
+ public short getMethod() {
+ return method;
+ }
+
+ public void setMethod(short method) {
+ this.method = method;
+ }
+
+ public short getLastTime() {
+ return lastTime;
+ }
+
+ public void setLastTime(short lastTime) {
+ this.lastTime = lastTime;
+ }
+
+ public short getLastDate() {
+ return lastDate;
+ }
+
+ public void setLastDate(short lastDate) {
+ this.lastDate = lastDate;
+ }
+
+ public int getCrc32() {
+ return crc32;
+ }
+
+ public void setCrc32(int crc32) {
+ this.crc32 = crc32;
+ }
+
+ public long getCompressedSize() {
+ return compressedSize;
+ }
+
+ public void setCompressedSize(long compressedSize) {
+ this.compressedSize = compressedSize;
+ }
+
+ public long getUnCompressedSize() {
+ return unCompressedSize;
+ }
+
+ public void setUnCompressedSize(long unCompressedSize) {
+ this.unCompressedSize = unCompressedSize;
+ }
+
+ public int getFileNameLength() {
+ return fileNameLength;
+ }
+
+ public void setFileNameLength(int fileNameLength) {
+ this.fileNameLength = fileNameLength;
+ }
+
+ public int getExtraLength() {
+ return extraLength;
+ }
+
+ public void setExtraLength(int extraLength) {
+ this.extraLength = extraLength;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+ public byte[] getExtraData() {
+ return extraData;
+ }
+
+ public void setExtraData(byte[] extraData) {
+ this.extraData = extraData;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public void setLength(int length) {
+ this.length = length;
+ }
+}
\ No newline at end of file
diff --git a/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/ProfileTest.java b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/ProfileTest.java
index df437f6e4e1de5ee03f0f997578ec9cb3a3a7731..5fc3110c399d28ac3fb7f185526180b2112e2755 100644
--- a/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/ProfileTest.java
+++ b/hapsigntool/hap_sign_tool_lib/src/test/java/com/ohos/hapsigntool/ProfileTest.java
@@ -17,6 +17,9 @@ package com.ohos.hapsigntool;
import com.ohos.hapsigntool.api.LocalizationAdapter;
import com.ohos.hapsigntool.api.model.Options;
+import com.ohos.hapsigntool.error.CustomException;
+import com.ohos.hapsigntool.error.ERROR;
+import com.ohos.hapsigntool.error.VerifyException;
import com.ohos.hapsigntool.key.KeyPairTools;
import com.ohos.hapsigntool.profile.ProfileSignTool;
import com.ohos.hapsigntool.profile.VerifyHelper;
@@ -140,7 +143,12 @@ public class ProfileTest {
byte[] p7b = ProfileSignTool.signProfile(provisionContent, signer, adapter.getSignAlg());
assertNotNull(p7b);
VerifyHelper verifyHelper = new VerifyHelper();
- VerificationResult verificationResult = verifyHelper.verify(p7b);
+ VerificationResult verificationResult = null;
+ try {
+ verificationResult = verifyHelper.verify(p7b);
+ } catch (VerifyException e) {
+ CustomException.throwException(ERROR.VERIFY_ERROR, e.getMessage());
+ }
assertTrue(verificationResult.isVerifiedPassed());
try {
diff --git a/tools/app-sign-srv-ca1.cer b/tools/app-sign-srv-ca1.cer
deleted file mode 100644
index b89ed745b57f35376992a54a8fd0d2ee21acdb39..0000000000000000000000000000000000000000
--- a/tools/app-sign-srv-ca1.cer
+++ /dev/null
@@ -1,14 +0,0 @@
------BEGIN CERTIFICATE-----
-MIICHDCCAcKgAwIBAgIFAM1hgkMwCgYIKoZIzj0EAwIwVTELMAkGA1UEBhMCQ04x
-FDASBgNVBAoMC09wZW5IYXJtb255MR4wHAYDVQQLDBVPcGVuSGFybW9ueSBDb21t
-dW5pdHkxEDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjIwMzA0MDI0NjUxWhcNMjMwMzA0
-MDI0NjUxWjBuMQswCQYDVQQGEwJDTjEUMBIGA1UECgwLT3Blbkhhcm1vbnkxHjAc
-BgNVBAsMFU9wZW5IYXJtb255IENvbW11bml0eTEpMCcGA1UEAwwgQXBwbGljYXRp
-b24gU2lnbmF0dXJlIFNlcnZpY2UgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
-AAQrUbzV/Qa2LjwtTzp1MUpXwN73BNQRluZLCahN9e6IWLNsCifvTNuD+aDhZiA0
-AZ5SpWoY7J1GjwOMmLP7MGEeo2YwZDAdBgNVHQ4EFgQUkWt6dNOoPEKVXkGNtC5Z
-hyWkGdMwHwYDVR0jBBgwFoAUcsPzleL32qFRbaDiIHxr3U7dMo8wEgYDVR0TAQH/
-BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwIDSAAwRQIhAPHT
-mL3jEbeAf1Uo7j0h5kdfwF/wSCoTKyDfljvawkghAiAeidOkKzA+GjSapKkW27Xx
-piaCQxb8O7hL6BGL8M4SZw==
------END CERTIFICATE-----
diff --git a/tools/auto_test.py b/tools/auto_test.py
index f569d86445b0b17263089166527c6f76192dd1b3..3cf38a9a1310ccef3f6a7cbd93d0d6ecc01111e1 100644
--- a/tools/auto_test.py
+++ b/tools/auto_test.py
@@ -298,10 +298,11 @@ def run_target(case, cmd):
if len(error) > 0:
f.writelines(cmd + "\r\n")
for line in error:
- success = False
f.writelines(str(line.strip()) + "\r\n")
- command.wait()
+ code = command.wait()
+ if code != 0:
+ success = False
end = time.time()
case_result['total_cost'] = case_result['total_cost'] + (end - start)
diff --git a/tools/commands.config b/tools/commands.config
index 559d8c1c93c508bd9a1bb432dad4127d1eac8096..f82dfe209dc4d6eb92dca9dbd8ecaea6e7d680f6 100644
--- a/tools/commands.config
+++ b/tools/commands.config
@@ -85,11 +85,33 @@
'verify-profile -inFile "app1-profile1.p7b"',
'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 "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 "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 "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt" -signCode "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-signedcode.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -signCode "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-signedcode.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 "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "1"',
+ 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/elf_unittest.elf" -keystoreFile "ohtest_pass.jks" -outFile "output-elf-codesign-profile" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -signCode "1" -inForm elf',
+ 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/elf_unittest.elf" -keystoreFile "ohtest_pass.jks" -outFile "output-elf-codesign" -keyPwd "123456" -keystorePwd "123456" -signCode "1" -inForm elf',
+ 'verify-app -inFile "app1-signedcode.hap" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile.p7b"',
+ 'verify-app -inFile "output-elf-codesign-profile" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile.p7b" -inForm elf',
+ 'verify-app -inFile "output-elf-codesign" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile.p7b" -inForm elf',
+ '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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "1"',
+ 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/elf_unittest.elf" -keystoreFile "ohtest_pass.jks" -outFile "output-elf-codesign-profile" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -signCode "1" -inForm elf',
+ 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/elf_unittest.elf" -keystoreFile "ohtest_pass.jks" -outFile "output-elf-codesign" -keyPwd "123456" -keystorePwd "123456" -signCode "1" -inForm elf',
+ 'verify-app -inFile "app1-signedcode.hap" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile1.p7b"',
+ 'verify-app -inFile "output-elf-codesign-profile" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile1.p7b" -inForm elf',
+ 'verify-app -inFile "output-elf-codesign" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile1.p7b" -inForm elf'
],
'case-assert-false': [
'generate-keypair -keyPwd 123456 -keyAlg ECC -keySize NIST-P-384 -keystoreFile "ohtest.jks" -keystorePwd 123456 -extCfgFile "111.txt"',
@@ -339,45 +361,126 @@
'verify-profile -outFile "verify-result.json" -inFile "app1-profile1.jks"',
'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" -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"',
- '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-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 -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"',
- '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" -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"',
- '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" -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 "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 "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 "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 "String"',
- '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" -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 "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 "profile.json" -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"',
- '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" -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 "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"',
- '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-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" -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 "ohtest_pass.txt" -outFile "app1-signed.hap" -keyPwd "123456" -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"',
- '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"',
+ '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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456789" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456789" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keystorePwd "123456" -signCode "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-signedcode.hap" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keystorePwd "123456" -signCode "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-signedcode.hap" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456789" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456789" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -signCode "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" -signCode "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" -signCode "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" -signCode "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" -signCode "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-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-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 "app1_error.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.hap" -keyPwd "123456" -keystorePwd "123456" -signCode "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-signedcode.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-unsignedcode.hap" -inForm -profileSigned -extCfgFile -signCode "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-signedcode.hap" -inForm -profileSigned -extCfgFile -signCode "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-signedcode.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-unsignedcode.hap" -inForm -profileSigned -extCfgFile -signCode "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-signedcode.hap" -inForm -profileSigned -extCfgFile -signCode "1"',
+ 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/elf_unittest" -keystoreFile "ohtest_pass.jks" -outFile "output-elf-codesign-profile" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -signCode "1"',
+ 'verify-app -inFile "app1-signedcode.hap" -outCertChain "" -outProfile "app1-profile-error.p7b"',
+ 'verify-app -inFile "app1-signedcode.hap" -outCertChain "app1.cer" -outProfile ""',
+ 'verify-app -inFile "app1-signedcode.hap" -outCertChain "" -outProfile ""',
+ 'verify-app -inFile "app1-signedcode.hap" -outCertChain "app1.ce7778r" -outProfile "app1-profile-error.p7b"',
]
}
\ No newline at end of file
diff --git a/tools/root-ca1.cer b/tools/root-ca1.cer
deleted file mode 100644
index af02c6a4e41249f644f3745f3801f20c2e2cffc9..0000000000000000000000000000000000000000
--- a/tools/root-ca1.cer
+++ /dev/null
@@ -1,13 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIB4TCCAYegAwIBAgIEJlDuCzAKBggqhkjOPQQDAjBVMQswCQYDVQQGEwJDTjEU
-MBIGA1UECgwLT3Blbkhhcm1vbnkxHjAcBgNVBAsMFU9wZW5IYXJtb255IENvbW11
-bml0eTEQMA4GA1UEAwwHUm9vdCBDQTAeFw0yMjAzMDQwMjQ2NDdaFw0yMzAzMDQw
-MjQ2NDdaMFUxCzAJBgNVBAYTAkNOMRQwEgYDVQQKDAtPcGVuSGFybW9ueTEeMBwG
-A1UECwwVT3Blbkhhcm1vbnkgQ29tbXVuaXR5MRAwDgYDVQQDDAdSb290IENBMFkw
-EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0kXaGsjyBd94GqKieIAcoXOjVIx6lPEk
-RlzjHUh7ixAYQtyPo+pBG8VX7l6bMtICj9AdXz9lguaINvrSJyXzm6NFMEMwHQYD
-VR0OBBYEFHLD85Xi99qhUW2g4iB8a91O3TKPMBIGA1UdEwEB/wQIMAYBAf8CAQAw
-DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMCA0gAMEUCIQCrZwpSiqyrGc7L978Q
-cwydhj2zKhOzPsVQxWiJAgWFLwIgac56zDrBGwDegtnN1/FgfNE3od/qiJy5Yf9g
-Bwm84pM=
------END CERTIFICATE-----
diff --git a/tools/test/elf_unittest.elf b/tools/test/elf_unittest.elf
new file mode 100644
index 0000000000000000000000000000000000000000..deffa1c72a2447c57c35616d89c8f459401c45ea
--- /dev/null
+++ b/tools/test/elf_unittest.elf
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
+
+ elf test file
\ No newline at end of file