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..27414edf9bbd4c91021ef866fd3714cfe8df3a90
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java
@@ -0,0 +1,198 @@
+/*
+ * 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[128] reserved
+ * 18) u8 csVersion: code sign version
+ * 19) u8[] signature: signature after signing the data in byte array representation
+ *
+ * @since 2023/09/08
+ */
+public class ElfSignBlock {
+ /**
+ * page size in bytes
+ */
+ public static final int PAGE_SIZE_4K = 4096;
+
+ /**
+ * Type of MerkleTree
+ */
+ public static final int MERKLE_TREE_INLINED = 0x2;
+
+ 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);
+ }
+
+ 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];
+ 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..0a25ba2532b931737894f1372ca2e4643c2bbf15
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.hapsigntool.codesigning.datastructure;
+
+import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Locale;
+
+/**
+ * Hap info segment
+ *
+ * Structure
+ *
+ * 1) u32 magic: magic number
+ *
+ * 2) SignInfo hapSignInfo: data struct of sign info, refer to SignInfo.java
+ *
+ * @since 2023/09/08
+ */
+public class HapInfoSegment {
+ private static final int MAGIC_NUM_BYTES = 4;
+
+ /**
+ * lower 4 bytes of the MD5 result of string "hap info segment" (C1B5 CC66)
+ */
+ private static final int MAGIC_NUM = (0xC1B5 << 16) + 0xCC66;
+
+ private int magic = MAGIC_NUM;
+
+ private SignInfo hapSignInfo;
+
+ /**
+ * Default constructor of HapInfoSegment
+ */
+ public HapInfoSegment() {
+ this(MAGIC_NUM, new SignInfo(0, 0, 0, null, null));
+ }
+
+ /**
+ * Default constructor of HapInfoSegment
+ *
+ * @param magic magic number
+ * @param hapSignInfo hap sign info
+ */
+ public HapInfoSegment(int magic, SignInfo hapSignInfo) {
+ this.magic = magic;
+ this.hapSignInfo = hapSignInfo;
+ }
+
+ public void setSignInfo(SignInfo signInfo) {
+ this.hapSignInfo = signInfo;
+ }
+
+ public SignInfo getSignInfo() {
+ return hapSignInfo;
+ }
+
+ /**
+ * Returns byte size of HapInfoSegment
+ *
+ * @return byte size of HapInfoSegment
+ */
+ public int size() {
+ return MAGIC_NUM_BYTES + hapSignInfo.size();
+ }
+
+ /**
+ * Converts HapInfoSegment to a newly created byte array
+ *
+ * @return Byte array representation of HapInfoSegment
+ */
+ public byte[] toByteArray() {
+ byte[] hapSignInfoByteArray = this.hapSignInfo.toByteArray();
+ // For now, only hap info segment has a merkle tree extension. So info segment
+ // has none extension.
+ ByteBuffer bf = ByteBuffer.allocate(MAGIC_NUM_BYTES + hapSignInfoByteArray.length)
+ .order(ByteOrder.LITTLE_ENDIAN);
+ bf.putInt(magic);
+ bf.put(hapSignInfoByteArray);
+ return bf.array();
+ }
+
+ /**
+ * Init the HapInfoSegment by a byte array
+ *
+ * @param bytes Byte array representation of a HapInfoSegment object
+ * @return a newly created HapInfoSegment object
+ * @throws VerifyCodeSignException parsing result invalid
+ */
+ public static HapInfoSegment fromByteArray(byte[] bytes) throws VerifyCodeSignException {
+ ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN);
+ bf.put(bytes);
+ bf.rewind();
+ int inMagic = bf.getInt();
+ if (inMagic != MAGIC_NUM) {
+ throw new VerifyCodeSignException("Invalid magic number of HapInfoSegment");
+ }
+ byte[] hapSignInfoByteArray = new byte[bytes.length - MAGIC_NUM_BYTES];
+ bf.get(hapSignInfoByteArray);
+ SignInfo inHapSignInfo = SignInfo.fromByteArray(hapSignInfoByteArray);
+ if (inHapSignInfo.getDataSize() % CodeSignBlock.PAGE_SIZE_4K != 0) {
+ throw new VerifyCodeSignException(
+ String.format(Locale.ROOT, "Invalid dataSize number of HapInfoSegment, not a multiple of 4096: %d",
+ inHapSignInfo.getDataSize()));
+ }
+ if (inHapSignInfo.getExtensionNum() != SignInfo.MAX_EXTENSION_NUM) {
+ throw new VerifyCodeSignException("Invalid extensionNum of HapInfoSegment");
+ }
+ if (inHapSignInfo.getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED) == null) {
+ throw new VerifyCodeSignException("No merkle tree extension is found in HapInfoSegment");
+ }
+ return new HapInfoSegment(inMagic, inHapSignInfo);
+ }
+
+ /**
+ * Return a string representation of the object
+ *
+ * @return string representation of the object
+ */
+ public String toString() {
+ return String.format(Locale.ROOT, "HapInfoSegment: magic[%d], signInfo[%s]", this.magic,
+ this.hapSignInfo.toString());
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java
new file mode 100644
index 0000000000000000000000000000000000000000..303d92edf13effedc56dec7605e4b2c3bd5aa908
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.hapsigntool.codesigning.datastructure;
+
+import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Merkle tree extension is a type of Extension to store a merkle tree's information, i.e. size and root hash, ect.
+ *
+ * structure
+ *
+ * 1) u32 type
+ *
+ * 2) u64 merkleTreeSize: the size of merkle tree
+ *
+ * 3) u64 merkleTreeOffset: offset of the merkle tree by the start of the file.
+ *
+ * 4) u8[64] rootHash: merkle tree root hash
+ *
+ * @since 2023/09/08
+ */
+public class MerkleTreeExtension extends Extension {
+ /**
+ * Type of MerkleTreeExtension
+ */
+ public static final int MERKLE_TREE_INLINED = 0x1;
+
+ /**
+ * Byte size of MerkleTreeExtension including merkleTreeSize, offset and root hash.
+ */
+ public static final int MERKLE_TREE_EXTENSION_DATA_SIZE = 80;
+
+ private static final int ROOT_HASH_SIZE = 64;
+
+ private final long merkleTreeSize;
+
+ private long merkleTreeOffset;
+
+ private byte[] rootHash;
+
+ /**
+ * Constructor for MerkleTreeExtension
+ *
+ * @param merkleTreeSize Byte array representation of merkle tree
+ * @param merkleTreeOffset merkle tree offset based on file start
+ * @param rootHash Root hash of the merkle tree
+ */
+ public MerkleTreeExtension(long merkleTreeSize, long merkleTreeOffset, byte[] rootHash) {
+ super(MERKLE_TREE_INLINED, MERKLE_TREE_EXTENSION_DATA_SIZE);
+ this.merkleTreeSize = merkleTreeSize;
+ this.merkleTreeOffset = merkleTreeOffset;
+ if (rootHash == null) {
+ this.rootHash = new byte[ROOT_HASH_SIZE];
+ } else {
+ this.rootHash = Arrays.copyOf(rootHash, ROOT_HASH_SIZE);
+ }
+ }
+
+ @Override
+ public int size() {
+ return Extension.EXTENSION_HEADER_SIZE + MERKLE_TREE_EXTENSION_DATA_SIZE;
+ }
+
+ public long getMerkleTreeSize() {
+ return merkleTreeSize;
+ }
+
+ public long getMerkleTreeOffset() {
+ return merkleTreeOffset;
+ }
+
+ public void setMerkleTreeOffset(long offset) {
+ this.merkleTreeOffset = offset;
+ }
+
+ /**
+ * Converts MerkleTreeExtension to a newly created byte array
+ *
+ * @return Byte array representation of MerkleTreeExtension
+ */
+ @Override
+ public byte[] toByteArray() {
+ ByteBuffer bf = ByteBuffer.allocate(Extension.EXTENSION_HEADER_SIZE + MERKLE_TREE_EXTENSION_DATA_SIZE)
+ .order(ByteOrder.LITTLE_ENDIAN);
+ bf.put(super.toByteArray());
+ bf.putLong(this.merkleTreeSize);
+ bf.putLong(this.merkleTreeOffset);
+ bf.put(this.rootHash);
+ return bf.array();
+ }
+
+ /**
+ * Init the MerkleTreeExtension by a byte array
+ *
+ * @param bytes Byte array representation of a MerkleTreeExtension object
+ * @return a newly created MerkleTreeExtension object
+ * @throws VerifyCodeSignException parsing result invalid
+ */
+ public static MerkleTreeExtension fromByteArray(byte[] bytes) throws VerifyCodeSignException {
+ ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN);
+ bf.put(bytes);
+ bf.rewind();
+ long inMerkleTreeSize = bf.getLong();
+ if (inMerkleTreeSize % CodeSignBlock.PAGE_SIZE_4K != 0) {
+ throw new VerifyCodeSignException("merkleTreeSize is not a multiple of 4096");
+ }
+ long inMerkleTreeOffset = bf.getLong();
+ if (inMerkleTreeOffset % CodeSignBlock.PAGE_SIZE_4K != 0) {
+ throw new VerifyCodeSignException("merkleTreeOffset is not a aligned to 4096");
+ }
+ byte[] inRootHash = new byte[ROOT_HASH_SIZE];
+ bf.get(inRootHash);
+ return new MerkleTreeExtension(inMerkleTreeSize, inMerkleTreeOffset, inRootHash);
+ }
+
+ /**
+ * Return a string representation of the object
+ *
+ * @return string representation of the object
+ */
+ @Override
+ public String toString() {
+ return String.format(Locale.ROOT, "MerkleTreeExtension: merkleTreeSize[%d], merkleTreeOffset[%d],"
+ + " rootHash[%s]", this.merkleTreeSize, this.merkleTreeOffset, Arrays.toString(this.rootHash));
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java
new file mode 100644
index 0000000000000000000000000000000000000000..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..98cdd8964e9783ffa0d705c63f091e2aac93e801
--- /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();
+ int inDataSize = bf.getInt();
+ 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..2d7591a8bf603dde97e0c30472d52c907a24ad65
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.hapsigntool.codesigning.sign;
+
+import com.ohos.hapsigntool.codesigning.exception.CodeSignException;
+import com.ohos.hapsigntool.codesigning.utils.CmsUtils;
+import com.ohos.hapsigntool.codesigning.utils.DigestUtils;
+import com.ohos.hapsigntool.hap.config.SignerConfig;
+import com.ohos.hapsigntool.hap.entity.Pair;
+import com.ohos.hapsigntool.hap.sign.ContentDigestAlgorithm;
+import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.BERSet;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Time;
+import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.SignedData;
+import org.bouncycastle.asn1.pkcs.SignerInfo;
+import org.bouncycastle.cert.jcajce.JcaX509CRLHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * BC implementation
+ *
+ * @since 2023/06/05
+ */
+public class BcSignedDataGenerator implements SignedDataGenerator {
+ private static final Logger LOGGER = LogManager.getLogger(BcSignedDataGenerator.class);
+
+ private static final SignatureAlgorithmIdentifierFinder SIGN_ALG_ID_FINDER
+ = new DefaultSignatureAlgorithmIdentifierFinder();
+
+ private static final DigestAlgorithmIdentifierFinder DIGEST_ALG_ID_FINDER
+ = new DefaultDigestAlgorithmIdentifierFinder();
+
+ @Override
+ public byte[] generateSignedData(byte[] content, SignerConfig signConfig) throws CodeSignException {
+ if (content == null) {
+ throw new CodeSignException("Verity digest is null");
+ }
+ Pair pairDigestAndSignInfo = getSignInfo(content, signConfig);
+ SignedData signedData = new SignedData(new ASN1Integer(1), pairDigestAndSignInfo.getFirst(),
+ new ContentInfo(PKCSObjectIdentifiers.data, null), createBerSetFromLst(signConfig.getCertificates()),
+ createBerSetFromLst(signConfig.getX509CRLs()), pairDigestAndSignInfo.getSecond());
+ return encodingUnsignedData(content, signedData);
+ }
+
+ private Pair getSignInfo(byte[] content, SignerConfig signConfig) throws CodeSignException {
+ ASN1EncodableVector signInfoVector = new ASN1EncodableVector();
+ ASN1EncodableVector digestVector = new ASN1EncodableVector();
+ for (SignatureAlgorithm signAlgorithm : signConfig.getSignatureAlgorithms()) {
+ SignerInfo signInfo = createSignInfo(signAlgorithm, content, signConfig);
+ signInfoVector.add(signInfo);
+ digestVector.add(signInfo.getDigestAlgorithm());
+ LOGGER.info("Create a sign info successfully.");
+ }
+ return Pair.create(new DERSet(digestVector), new DERSet(signInfoVector));
+ }
+
+ private SignerInfo createSignInfo(SignatureAlgorithm signAlgorithm, byte[] unsignedDataDigest,
+ SignerConfig signConfig) throws CodeSignException {
+ ContentDigestAlgorithm hashAlgorithm = signAlgorithm.getContentDigestAlgorithm();
+ byte[] digest = computeDigest(unsignedDataDigest, hashAlgorithm.name());
+ ASN1Set authed = getPKCS9Attributes(digest);
+ byte[] codeAuthed = getEncoded(authed);
+ Pair signPair = signAlgorithm.getSignatureAlgAndParams();
+ byte[] signBytes = signConfig.getSigner().getSignature(codeAuthed, signPair.getFirst(), signPair.getSecond());
+ if (signBytes == null) {
+ throw new CodeSignException("get signature failed");
+ }
+ if (signConfig.getCertificates().isEmpty()) {
+ throw new CodeSignException("No certificates configured for sign");
+ }
+ X509Certificate cert = signConfig.getCertificates().get(0);
+ if (!verifySignFromServer(cert.getPublicKey(), signBytes, signPair, codeAuthed)) {
+ throw new CodeSignException("verifySignatureFromServer failed");
+ }
+ JcaX509CertificateHolder certificateHolder = getJcaX509CertificateHolder(cert);
+ return new SignerInfo(new ASN1Integer(1),
+ new IssuerAndSerialNumber(certificateHolder.getIssuer(), certificateHolder.getSerialNumber()),
+ DIGEST_ALG_ID_FINDER.find(hashAlgorithm.getDigestAlgorithm()), authed,
+ SIGN_ALG_ID_FINDER.find(signPair.getFirst()), new DEROctetString(signBytes), null);
+ }
+
+ private byte[] computeDigest(byte[] unsignedDataDigest, String algorithm) throws CodeSignException {
+ byte[] digest;
+ try {
+ digest = DigestUtils.computeDigest(unsignedDataDigest, algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ throw new CodeSignException("Invalid algorithm" + e.getMessage(), e);
+ }
+ return digest;
+ }
+
+ private byte[] getEncoded(ASN1Set authed) throws CodeSignException {
+ byte[] codeAuthed;
+ try {
+ codeAuthed = authed.getEncoded();
+ } catch (IOException e) {
+ throw new CodeSignException("cannot encode authed", e);
+ }
+ return codeAuthed;
+ }
+
+ private JcaX509CRLHolder getJcaX509CRLHolder(X509CRL crl) throws CodeSignException {
+ JcaX509CRLHolder crlHolder;
+ try {
+ crlHolder = new JcaX509CRLHolder(crl);
+ } catch (CRLException e) {
+ throw new CodeSignException("Create crl failed", e);
+ }
+ return crlHolder;
+ }
+
+ private JcaX509CertificateHolder getJcaX509CertificateHolder(X509Certificate cert) throws CodeSignException {
+ JcaX509CertificateHolder certificateHolder;
+ try {
+ certificateHolder = new JcaX509CertificateHolder(cert);
+ } catch (CertificateEncodingException e) {
+ throw new CodeSignException("Create sign info failed", e);
+ }
+ return certificateHolder;
+ }
+
+ private ASN1Set getPKCS9Attributes(byte[] digest) {
+ ASN1EncodableVector table = new ASN1EncodableVector();
+ Attribute signingTimeAttr = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_signingTime,
+ new DERSet(new Time(new Date())));
+ Attribute contentTypeAttr = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_contentType,
+ new DERSet(PKCSObjectIdentifiers.data));
+ Attribute messageDigestAttr = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_messageDigest,
+ new DERSet(new DEROctetString(digest)));
+ table.add(signingTimeAttr);
+ table.add(contentTypeAttr);
+ table.add(messageDigestAttr);
+ return new DERSet(table);
+ }
+
+ private boolean verifySignFromServer(PublicKey publicKey, byte[] signBytes,
+ Pair signPair, byte[] authed) throws CodeSignException {
+ try {
+ Signature signature = Signature.getInstance(signPair.getFirst());
+ signature.initVerify(publicKey);
+ if (signPair.getSecond() != null) {
+ signature.setParameter(signPair.getSecond());
+ }
+ signature.update(authed);
+ if (!signature.verify(signBytes)) {
+ throw new CodeSignException("Signature verify failed");
+ }
+ return true;
+ } catch (InvalidKeyException | SignatureException e) {
+ LOGGER.error("The generated signature could not be verified " + " using the public key in the certificate",
+ e);
+ } catch (NoSuchAlgorithmException e) {
+ LOGGER.error("The generated signature " + signPair.getFirst()
+ + " could not be verified using the public key in the certificate", e);
+ } catch (InvalidAlgorithmParameterException e) {
+ LOGGER.error("The generated signature " + signPair.getSecond()
+ + " could not be verified using the public key in the certificate", e);
+ }
+ return false;
+ }
+
+ private ASN1Set createBerSetFromLst(List> lists) throws CodeSignException {
+ if (lists == null || lists.size() == 0) {
+ return null;
+ }
+ ASN1EncodableVector vector = new ASN1EncodableVector();
+ for (Object obj : lists) {
+ if (obj instanceof X509CRL) {
+ vector.add(getJcaX509CRLHolder((X509CRL) obj).toASN1Structure());
+ } else if (obj instanceof X509Certificate) {
+ vector.add(getJcaX509CertificateHolder((X509Certificate) obj).toASN1Structure());
+ }
+ }
+ return new BERSet(vector);
+ }
+
+ private byte[] encodingUnsignedData(byte[] unsignedDataDigest, SignedData signedData) throws CodeSignException {
+ byte[] signResult;
+ try {
+ ContentInfo contentInfo = new ContentInfo(PKCSObjectIdentifiers.signedData, signedData);
+ signResult = contentInfo.getEncoded(ASN1Encoding.DER);
+ } catch (IOException e) {
+ throw new CodeSignException("failed to encode unsigned data!", e);
+ }
+ verifySignResult(unsignedDataDigest, signResult);
+ return signResult;
+ }
+
+ private void verifySignResult(byte[] unsignedDataDigest, byte[] signResult) throws CodeSignException {
+ boolean result = false;
+ try {
+ result = CmsUtils.verifySignDataWithUnsignedDataDigest(unsignedDataDigest, signResult);
+ } catch (CMSException e) {
+ throw new CodeSignException("failed to verify signed data and unsigned data digest", e);
+ }
+ if (!result) {
+ throw new CodeSignException("PKCS cms data did not verify");
+ }
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9c8b1524818df186eeea30929bb57083cb40a83
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.hapsigntool.codesigning.sign;
+
+import org.bouncycastle.util.Strings;
+
+import java.util.Locale;
+import java.util.zip.ZipEntry;
+
+/**
+ * Central directory structure
+ * further reference to Zip Format
+ *
+ * @since 2023/09/14
+ */
+public class CentralDirectory {
+ /**
+ * Byte size of all fields before "compression method" in central directory structure
+ */
+ public static final int BYTE_SIZE_BEFORE_COMPRESSION_METHOD = 10;
+
+ /**
+ * Byte size of all fields between "compression method" and "file comment length" in central directory structure
+ */
+ public static final int BYTE_SIZE_BETWEEN_COMPRESSION_MODE_AND_FILE_SIZE = 16;
+
+ /**
+ * Byte size of all fields between "file comment length" and
+ * "relative offset of local header" in central directory structure
+ */
+ public static final int BYTE_SIZE_BETWEEN_FILE_COMMENT_LENGTH_AND_LOCHDR_RELATIVE_OFFSET = 8;
+
+ private final char compressionMethod;
+
+ private final char fileNameLength;
+
+ private final char extraFieldLength;
+
+ private final char fileCommentLength;
+
+ private final int relativeOffsetOfLocalHeader;
+
+ private final byte[] fileName;
+
+ public CentralDirectory(Builder builder) {
+ this.compressionMethod = builder.compressionMethod;
+ this.fileNameLength = builder.fileNameLength;
+ this.extraFieldLength = builder.extraFieldLength;
+ this.fileCommentLength = builder.fileCommentLength;
+ this.relativeOffsetOfLocalHeader = builder.relativeOffsetOfLocalHeader;
+ this.fileName = builder.fileName;
+ }
+
+ /**
+ * Return true if entry is an executable file, i.e. abc or so
+ *
+ * @return true if entry is an executable file
+ */
+ public boolean isCodeFile() {
+ return this.getFileName().endsWith(".abc") || this.getFileName().endsWith(".so");
+ }
+
+ /**
+ * Return true if zip entry is uncompressed
+ *
+ * @return true if zip entry is uncompressed
+ */
+ public boolean isUncompressed() {
+ return this.compressionMethod == ZipEntry.STORED;
+ }
+
+ public String getFileName() {
+ return Strings.fromByteArray(this.fileName);
+ }
+
+ public int getRelativeOffsetOfLocalHeader() {
+ return relativeOffsetOfLocalHeader;
+ }
+
+ /**
+ * Sum byte size of three variable fields: file name, extra field, file comment
+ *
+ * @return Sum byte size of three variable fields
+ */
+ public char getFileNameLength() {
+ return fileNameLength;
+ }
+
+ public char getExtraFieldLength() {
+ return extraFieldLength;
+ }
+
+ /**
+ * Return a string representation of the object
+ *
+ * @return string representation of the object
+ */
+ public String toString() {
+ return String.format(Locale.ROOT,
+ "CentralDirectory:compressionMode(%d), fileName(%s), relativeOffsetOfLocalHeader(%d), "
+ + "fileNameLength(%d), extraFieldLength(%d), fileCommentLength(%d)", (int) this.compressionMethod,
+ this.getFileName(), this.relativeOffsetOfLocalHeader, (int) this.fileNameLength,
+ (int) this.extraFieldLength, (int) this.fileCommentLength);
+ }
+
+ /**
+ * Builder of CentralDirectory class
+ */
+ public static class Builder {
+ private char compressionMethod;
+
+ private char fileNameLength;
+
+ private char extraFieldLength;
+
+ private char fileCommentLength;
+
+ private int relativeOffsetOfLocalHeader;
+
+ private byte[] fileName;
+
+ public Builder setCompressionMethod(char compressionMethod) {
+ this.compressionMethod = compressionMethod;
+ return this;
+ }
+
+ public Builder setFileNameLength(char fileNameLength) {
+ this.fileNameLength = fileNameLength;
+ return this;
+ }
+
+ public Builder setExtraFieldLength(char extraFieldLength) {
+ this.extraFieldLength = extraFieldLength;
+ return this;
+ }
+
+ public Builder setFileCommentLength(char fileCommentLength) {
+ this.fileCommentLength = fileCommentLength;
+ return this;
+ }
+
+ public Builder setRelativeOffsetOfLocalHeader(int relativeOffsetOfLocalHeader) {
+ this.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader;
+ return this;
+ }
+
+ public Builder setFileName(byte[] fileName) {
+ this.fileName = fileName;
+ return this;
+ }
+
+ /**
+ * 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..527655c192a52b097048bc47a55064afcf55cb63
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java
@@ -0,0 +1,422 @@
+/*
+ * 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.signer.LocalSigner;
+import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput;
+import com.ohos.hapsigntool.zip.ZipDataInput;
+import com.ohos.hapsigntool.zip.ZipFileInfo;
+import com.ohos.hapsigntool.zip.ZipUtils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * core functions of code signing
+ *
+ * @since 2023/06/05
+ */
+public class CodeSigning {
+ /**
+ * Only hap and hsp bundle supports code signing, concatenate then with '/' for verification usage
+ */
+ public static final String SUPPORT_FILE_FORM = "hap/hsp";
+
+ /**
+ * Only elf file supports bin code signing, concatenate then with '/' for verification usage
+ */
+ public static final String SUPPORT_BIN_FILE_FORM = "elf";
+
+ /**
+ * Defined entry name of hap file
+ */
+ public static final String HAP_SIGNATURE_ENTRY_NAME = "Hap";
+
+ private static final Logger LOGGER = LogManager.getLogger(CodeSigning.class);
+
+ private static final String NATIVE_LIB_AN_SUFFIX = ".an";
+
+ private static final String NATIVE_LIB_SO_SUFFIX = ".so";
+
+ private final List extractedNativeLibSuffixs = new ArrayList<>();
+
+ private final SignerConfig signConfig;
+
+ private CodeSignBlock codeSignBlock;
+
+ private long timestamp = 0L;
+
+ /**
+ * provide code sign functions to sign a hap
+ *
+ * @param signConfig configuration of sign
+ */
+ public CodeSigning(SignerConfig signConfig) {
+ this.signConfig = signConfig;
+ }
+
+ /**
+ * Sign the given elf file, and pack all signature into output file
+ *
+ * @param input file to sign
+ * @param offset position of codesign block based on start of the file
+ * @param inForm file's format
+ * @return byte array of code sign block
+ * @throws CodeSignException code signing exception
+ * @throws IOException io error
+ * @throws FsVerityDigestException computing FsVerity digest error
+ */
+ public byte[] getElfCodeSignBlock(File input, long offset, String inForm)
+ throws CodeSignException, FsVerityDigestException, IOException {
+ if (!SUPPORT_BIN_FILE_FORM.contains(inForm)) {
+ throw new CodeSignException("file's format is unsupported");
+ }
+ 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();
+ byte[] signature = generateSignature(fsVerityDigest);
+ // 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
+ * @return byte array of code sign block
+ * @throws CodeSignException code signing exception
+ * @throws IOException io error
+ * @throws HapFormatException hap format invalid
+ * @throws FsVerityDigestException computing FsVerity digest error
+ */
+ public byte[] getCodeSignBlock(File input, long offset, String inForm)
+ throws CodeSignException, IOException, HapFormatException, FsVerityDigestException {
+ LOGGER.info("Start to sign code.");
+ if (SUPPORT_BIN_FILE_FORM.contains(inForm)) {
+ return getElfCodeSignBlock(input, offset, inForm);
+ }
+ if (!SUPPORT_FILE_FORM.contains(inForm)) {
+ throw new CodeSignException("file's format is unsupported");
+ }
+ long dataSize = computeDataSize(input);
+ timestamp = System.currentTimeMillis();
+ // generate CodeSignBlock
+ this.codeSignBlock = new CodeSignBlock();
+ // compute merkle tree offset, replace with computeMerkleTreeOffset if fs-verity descriptor supports
+ long fsvTreeOffset = this.codeSignBlock.computeMerkleTreeOffset(offset);
+ // update fs-verity segment
+ FsVerityInfoSegment fsVerityInfoSegment = new FsVerityInfoSegment(FsVerityDescriptor.VERSION,
+ FsVerityGenerator.getFsVerityHashAlgorithm(), FsVerityGenerator.getLog2BlockSize());
+ this.codeSignBlock.setFsVerityInfoSegment(fsVerityInfoSegment);
+
+ LOGGER.debug("Sign hap.");
+ try (FileInputStream inputStream = new FileInputStream(input)) {
+ Pair hapSignInfoAndMerkleTreeBytesPair = signFile(inputStream, dataSize, true,
+ fsvTreeOffset);
+ // update hap segment in CodeSignBlock
+ this.codeSignBlock.getHapInfoSegment().setSignInfo(hapSignInfoAndMerkleTreeBytesPair.getFirst());
+ // Insert merkle tree bytes into code sign block
+ this.codeSignBlock.addOneMerkleTree(HAP_SIGNATURE_ENTRY_NAME,
+ hapSignInfoAndMerkleTreeBytesPair.getSecond());
+ }
+ // update native lib info segment in CodeSignBlock
+ signNativeLibs(input);
+
+ // last update codeSignBlock before generating its byte array representation
+ updateCodeSignBlock(this.codeSignBlock);
+
+ // complete code sign block byte array here
+ byte[] generated = this.codeSignBlock.generateCodeSignBlockByte(fsvTreeOffset);
+ LOGGER.info("Sign successfully.");
+ return generated;
+ }
+
+ private long computeDataSize(File file) throws IOException, HapFormatException {
+ long centralDirectoryOffset;
+ int centralDirectorySize;
+ int centralDirectoryEntryCount;
+ // parse central directory
+ try (RandomAccessFile outputHap = new RandomAccessFile(file, "rw")) {
+ ZipDataInput outputHapIn = new RandomAccessFileZipDataInput(outputHap);
+ ZipFileInfo zipInfo = ZipUtils.findZipInfo(outputHapIn);
+ centralDirectoryOffset = zipInfo.getCentralDirectoryOffset();
+ centralDirectorySize = zipInfo.getCentralDirectorySize();
+ centralDirectoryEntryCount = zipInfo.getCentralDirectoryEntryCount();
+ }
+ // centralDirectoryOffset is where all data ends, including abc/so/an and resources
+ try (FileInputStream input = new FileInputStream(file)) {
+ input.skip(centralDirectoryOffset);
+ byte[] centralDirectoryBuffer = new byte[centralDirectorySize];
+ input.read(centralDirectoryBuffer);
+ List cdList = parseCentralDirectory(centralDirectoryBuffer, centralDirectoryEntryCount);
+ long dataSize = 0L;
+ for (CentralDirectory entry : cdList) {
+ if (entry.isCodeFile() && entry.isUncompressed()) {
+ continue;
+ }
+ // if the first file is not uncompressed abc or so, set dataSize to zero
+ if (entry.getRelativeOffsetOfLocalHeader() == 0) {
+ dataSize = 0;
+ break;
+ }
+ // the first entry which is not abc/so/an is found, return its data offset
+ dataSize = entry.getRelativeOffsetOfLocalHeader() + JarFile.LOCHDR + entry.getFileNameLength()
+ + entry.getExtraFieldLength();
+ break;
+ }
+ if ((dataSize % CodeSignBlock.PAGE_SIZE_4K) != 0) {
+ throw new HapFormatException(
+ String.format(Locale.ROOT, "Invalid dataSize(%d), not a multiple of 4096", dataSize));
+ }
+ return dataSize;
+ }
+ }
+
+ private void signNativeLibs(File input) throws IOException, FsVerityDigestException, CodeSignException {
+ // 'an' libs are always signed
+ extractedNativeLibSuffixs.add(NATIVE_LIB_AN_SUFFIX);
+ if (HapUtils.checkCompressNativeLibs(input)) {
+ LOGGER.info("compressNativeLibs equals true, sign so libs as well.");
+ // sign so libs only if compressNativeLibs equals true
+ extractedNativeLibSuffixs.add(NATIVE_LIB_SO_SUFFIX);
+ }
+
+ // sign native files
+ 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);
+ // update SoInfoSegment in CodeSignBlock
+ this.codeSignBlock.getSoInfoSegment().setSoInfoList(nativeLibInfoList);
+ }
+ }
+
+ /**
+ * Get entry name of all native files in hap
+ *
+ * @param hap the given hap
+ * @return list of entry name
+ */
+ private List getNativeEntriesFromHap(JarFile hap) {
+ List result = new ArrayList<>();
+ for (Enumeration e = hap.entries(); e.hasMoreElements();) {
+ JarEntry entry = e.nextElement();
+ if (!entry.isDirectory()) {
+ if (!isNativeFile(entry.getName())) {
+ continue;
+ }
+ result.add(entry.getName());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Check whether the entry is a native file
+ *
+ * @param entryName the name of entry
+ * @return true if it is a native file, and false otherwise
+ */
+ private boolean isNativeFile(String entryName) {
+ for (String suffix : extractedNativeLibSuffixs) {
+ if (entryName.endsWith(suffix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sign specific entries in a hap
+ *
+ * @param entryNames list of entries which need to be signed
+ * @param hap input hap
+ * @return sign info and merkle tree of each file
+ * @throws IOException io error
+ * @throws FsVerityDigestException computing FsVerity digest error
+ * @throws CodeSignException sign error
+ */
+ private List> signFilesFromJar(List entryNames, JarFile hap)
+ throws IOException, FsVerityDigestException, CodeSignException {
+ List> nativeLibInfoList = new ArrayList<>();
+ for (String name : entryNames) {
+ LOGGER.debug("Sign entry name = " + name);
+ JarEntry inEntry = hap.getJarEntry(name);
+ try (InputStream inputStream = hap.getInputStream(inEntry)) {
+ long fileSize = inEntry.getSize();
+ // We don't store merkle tree in code signing of native libs
+ // Therefore, the second value of pair returned is ignored
+ Pair pairSignInfoAndMerkleTreeBytes = signFile(inputStream, fileSize, false, 0);
+ nativeLibInfoList.add(Pair.create(name, pairSignInfoAndMerkleTreeBytes.getFirst()));
+ }
+ }
+ return nativeLibInfoList;
+ }
+
+ /**
+ * Sign a file from input stream
+ *
+ * @param inputStream input stream of a file
+ * @param fileSize size of the file
+ * @param storeTree whether to store merkle tree in signed info
+ * @param fsvTreeOffset merkle tree raw bytes offset based on the start of file
+ * @return pair of signature and tree
+ * @throws FsVerityDigestException computing FsVerity Digest error
+ * @throws CodeSignException signing error
+ */
+ public Pair signFile(InputStream inputStream, long fileSize, boolean storeTree,
+ long fsvTreeOffset) throws FsVerityDigestException, CodeSignException {
+ FsVerityGenerator fsVerityGenerator = new FsVerityGenerator();
+ fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset);
+ byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest();
+ byte[] signature = generateSignature(fsVerityDigest);
+ int flags = 0;
+ if (storeTree) {
+ flags = SignInfo.FLAG_MERKLE_TREE_INCLUDED;
+ }
+ SignInfo signInfo = new SignInfo(fsVerityGenerator.getSaltSize(), flags, fileSize, fsVerityGenerator.getSalt(),
+ signature);
+ // if store merkle tree in sign info
+ if (storeTree) {
+ int merkleTreeSize = fsVerityGenerator.getTreeBytes() == null ? 0 : fsVerityGenerator.getTreeBytes().length;
+ Extension merkleTreeExtension = new MerkleTreeExtension(merkleTreeSize, fsvTreeOffset,
+ fsVerityGenerator.getRootHash());
+ signInfo.addExtension(merkleTreeExtension);
+ }
+ return Pair.create(signInfo, fsVerityGenerator.getTreeBytes());
+ }
+
+ private byte[] generateSignature(byte[] signedData) throws CodeSignException {
+ // signConfig is created by SignerFactory
+ if (!(signConfig.getSigner() instanceof LocalSigner)) {
+ if (signConfig.getCertificates().isEmpty()) {
+ throw new CodeSignException("No certificates configured for sign");
+ }
+ }
+ return SignedDataGenerator.BC.generateSignedData(signedData, signConfig);
+ }
+
+ /**
+ * At here, segment header, fsverity info/hap/so info segment, merkle tree
+ * segment should all be generated.
+ * code sign block size, segment number, offset is not updated.
+ * Try to update whatever could be updated here.
+ *
+ * @param codeSignBlock CodeSignBlock
+ */
+ private void updateCodeSignBlock(CodeSignBlock codeSignBlock) {
+ // construct segment header list
+ codeSignBlock.setSegmentHeaders();
+ // Compute and set segment number
+ codeSignBlock.setSegmentNum();
+ // update code sign block header flag
+ codeSignBlock.setCodeSignBlockFlag();
+ // compute segment offset
+ codeSignBlock.computeSegmentOffset();
+ }
+
+ private List parseCentralDirectory(byte[] buffer, int count) {
+ List cdList = new ArrayList<>();
+ ByteBuffer cdBuffer = ByteBuffer.allocate(buffer.length).order(ByteOrder.LITTLE_ENDIAN);
+ cdBuffer.put(buffer);
+ cdBuffer.rewind();
+ for (int i = 0; i < count; i++) {
+ byte[] bytesBeforeCompressionMethod = new byte[CentralDirectory.BYTE_SIZE_BEFORE_COMPRESSION_METHOD];
+ cdBuffer.get(bytesBeforeCompressionMethod);
+ char compressionMode = cdBuffer.getChar();
+ CentralDirectory.Builder builder = new CentralDirectory.Builder().setCompressionMethod(compressionMode);
+ byte[] bytesBetweenCmprMethodAndFileNameLength
+ = new byte[CentralDirectory.BYTE_SIZE_BETWEEN_COMPRESSION_MODE_AND_FILE_SIZE];
+ cdBuffer.get(bytesBetweenCmprMethodAndFileNameLength);
+ char fileNameLength = cdBuffer.getChar();
+ char extraFieldLength = cdBuffer.getChar();
+ char fileCommentLength = cdBuffer.getChar();
+ byte[] attributes
+ = new byte[CentralDirectory.BYTE_SIZE_BETWEEN_FILE_COMMENT_LENGTH_AND_LOCHDR_RELATIVE_OFFSET];
+ cdBuffer.get(attributes);
+ int locHdrOffset = cdBuffer.getInt();
+ builder.setFileNameLength(fileNameLength).setExtraFieldLength(extraFieldLength)
+ .setFileCommentLength(fileCommentLength).setRelativeOffsetOfLocalHeader(locHdrOffset);
+ byte[] fileNameBuffer = new byte[fileNameLength];
+ cdBuffer.get(fileNameBuffer);
+ if (extraFieldLength != 0) {
+ cdBuffer.get(new byte[extraFieldLength]);
+ }
+ if (fileCommentLength != 0) {
+ cdBuffer.get(new byte[fileCommentLength]);
+ }
+ CentralDirectory cd = builder.setFileName(fileNameBuffer).build();
+ cdList.add(cd);
+ }
+
+ return cdList;
+ }
+}
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..119a82d4d228ce4ef2407d30454400c7c9954346
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.hapsigntool.codesigning.sign;
+
+import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlock;
+import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlockHeader;
+import com.ohos.hapsigntool.codesigning.datastructure.ElfSignBlock;
+import com.ohos.hapsigntool.codesigning.datastructure.Extension;
+import com.ohos.hapsigntool.codesigning.datastructure.FsVerityInfoSegment;
+import com.ohos.hapsigntool.codesigning.datastructure.HapInfoSegment;
+import com.ohos.hapsigntool.codesigning.datastructure.MerkleTreeExtension;
+import com.ohos.hapsigntool.codesigning.datastructure.NativeLibInfoSegment;
+import com.ohos.hapsigntool.codesigning.datastructure.SegmentHeader;
+import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException;
+import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException;
+import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator;
+import com.ohos.hapsigntool.codesigning.utils.CmsUtils;
+import com.ohos.hapsigntool.hap.entity.Pair;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.cms.CMSException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Verify code signature given a file with code sign block
+ *
+ * @since 2023/09/08
+ */
+public class VerifyCodeSignature {
+ private static final Logger LOGGER = LogManager.getLogger(VerifyCodeSignature.class);
+
+ private static final String NATIVE_LIB_AN_SUFFIX = ".an";
+
+ private static final String NATIVE_LIB_SO_SUFFIX = ".so";
+
+ private static final List EXTRACTED_NATIVE_LIB_SUFFIXS = new ArrayList<>();
+
+ static {
+ EXTRACTED_NATIVE_LIB_SUFFIXS.add(NATIVE_LIB_AN_SUFFIX);
+ EXTRACTED_NATIVE_LIB_SUFFIXS.add(NATIVE_LIB_SO_SUFFIX);
+ }
+
+ /**
+ * Verify a signed elf's signature
+ *
+ * @param file signed elf file
+ * @param offset start position of code sign block based on the start of the elf file
+ * @param length byte size of code sign block
+ * @param fileFormat elf or hqf or hsp, etc.
+ * @return true if signature verify succeed and false otherwise
+ * @throws IOException If an input or output exception occurred
+ * @throws VerifyCodeSignException parsing result invalid
+ * @throws FsVerityDigestException if fs-verity digest generation failed
+ * @throws CMSException if signature verify failed
+ */
+ public static boolean verifyElf(File file, long offset, long length, String fileFormat)
+ throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException {
+ if (!CodeSigning.SUPPORT_BIN_FILE_FORM.contains(fileFormat)) {
+ LOGGER.info("Not elf file, skip code signing verify");
+ return true;
+ }
+ // 1) parse sign block to ElfCodeSignBlock object
+ ElfSignBlock elfSignBlock;
+ try (FileInputStream signedElf = new FileInputStream(file)) {
+ byte[] codeSignBlockBytes = new byte[(int) length];
+ signedElf.skip(offset);
+ signedElf.read(codeSignBlockBytes);
+ elfSignBlock = ElfSignBlock.fromByteArray(codeSignBlockBytes);
+ }
+ // 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);
+ }
+ return true;
+ }
+
+ /**
+ * Verify a signed hap's signature
+ *
+ * @param file signed hap file
+ * @param offset start position of code sign block based on the start of the hap file
+ * @param length byte size of code sign block
+ * @param fileFormat hap or hqf or hsp, etc.
+ * @return true if signature verify succeed and false otherwise
+ * @throws IOException If an input or output exception occurred
+ * @throws VerifyCodeSignException parsing result invalid
+ * @throws FsVerityDigestException if fs-verity digest generation failed
+ * @throws CMSException if signature verify failed
+ */
+ public static boolean verifyHap(File file, long offset, long length, String fileFormat)
+ throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException {
+ if (!CodeSigning.SUPPORT_FILE_FORM.contains(fileFormat)) {
+ LOGGER.info("Not hap or hsp file, skip code signing verify");
+ return true;
+ }
+ CodeSignBlock csb = generateCodeSignBlock(file, offset, length);
+ // 2) verify hap
+ try (FileInputStream hap = new FileInputStream(file)) {
+ long dataSize = csb.getHapInfoSegment().getSignInfo().getDataSize();
+ byte[] signature = csb.getHapInfoSegment().getSignInfo().getSignature();
+ Extension extension = csb.getHapInfoSegment()
+ .getSignInfo()
+ .getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED);
+ MerkleTreeExtension mte = new MerkleTreeExtension(0, 0, null);
+ if (extension instanceof MerkleTreeExtension) {
+ mte = (MerkleTreeExtension) extension;
+ }
+ // temporary: merkle tree offset set to zero, change to merkleTreeOffset
+ verifySingleFile(hap, dataSize, signature, mte.getMerkleTreeOffset(),
+ csb.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME));
+ }
+ // 3) verify native libs
+ try (JarFile inputJar = new JarFile(file, false)) {
+ for (int i = 0; i < csb.getSoInfoSegment().getSectionNum(); i++) {
+ String entryName = csb.getSoInfoSegment().getFileNameList().get(i);
+ byte[] entrySig = csb.getSoInfoSegment().getSignInfoList().get(i).getSignature();
+ JarEntry entry = inputJar.getJarEntry(entryName);
+ if (entry.getSize() != csb.getSoInfoSegment().getSignInfoList().get(i).getDataSize()) {
+ throw new VerifyCodeSignException(
+ String.format(Locale.ROOT, "Invalid dataSize of native lib %s", entryName));
+ }
+ InputStream entryInputStream = inputJar.getInputStream(entry);
+ // temporary merkleTreeOffset 0
+ verifySingleFile(entryInputStream, entry.getSize(), entrySig, 0, null);
+ }
+ }
+ return true;
+ }
+
+ private static CodeSignBlock generateCodeSignBlock(File file, long offset, long length)
+ throws IOException, VerifyCodeSignException {
+ CodeSignBlock csb = new CodeSignBlock();
+ // 1) parse sign block to CodeSignBlock object
+ try (FileInputStream signedHap = new FileInputStream(file)) {
+ int fileReadOffset = 0;
+ // 0) skip data part, but fileReadOffset remains at start(0)
+ signedHap.skip(offset);
+ // 1) parse codeSignBlockHeader
+ byte[] codeSignBlockHeaderByteArray = new byte[CodeSignBlockHeader.size()];
+ fileReadOffset += signedHap.read(codeSignBlockHeaderByteArray);
+ csb.setCodeSignBlockHeader(CodeSignBlockHeader.fromByteArray(codeSignBlockHeaderByteArray));
+ if (csb.getCodeSignBlockHeader().getBlockSize() != length) {
+ throw new VerifyCodeSignException("Invalid code Sign block size of setCodeSignBlockHeader");
+ }
+ // 2) parse segment headers
+ for (int i = 0; i < csb.getCodeSignBlockHeader().getSegmentNum(); i++) {
+ byte[] segmentHeaderByteArray = new byte[SegmentHeader.SEGMENT_HEADER_LENGTH];
+ fileReadOffset += signedHap.read(segmentHeaderByteArray);
+ csb.addToSegmentList(SegmentHeader.fromByteArray(segmentHeaderByteArray));
+ }
+ // compute merkle tree offset by alignment, based on file start
+ long computedTreeOffset = getAlignmentAddr(CodeSignBlock.PAGE_SIZE_4K, fileReadOffset + offset);
+ // skip zero padding before merkle tree, adds zero padding length to fileReadOffset
+ fileReadOffset += signedHap.skip(computedTreeOffset - offset - fileReadOffset);
+ parseMerkleTree(csb, fileReadOffset, signedHap, computedTreeOffset);
+ }
+ return csb;
+ }
+
+ private static void parseMerkleTree(CodeSignBlock csb, int readOffset, FileInputStream signedHap,
+ long computedTreeOffset) throws VerifyCodeSignException, IOException {
+ // check segment offset and segment size
+ byte[] merkleTreeBytes = new byte[0];
+ int fileReadOffset = readOffset;
+ for (SegmentHeader segmentHeader : csb.getSegmentHeaderList()) {
+ if (fileReadOffset > segmentHeader.getSegmentOffset()) {
+ throw new VerifyCodeSignException("Invaild offset of merkle tree and segment header");
+ }
+ // get merkle tree bytes
+ if (fileReadOffset < segmentHeader.getSegmentOffset()) {
+ merkleTreeBytes = new byte[segmentHeader.getSegmentOffset() - fileReadOffset];
+ fileReadOffset += signedHap.read(merkleTreeBytes);
+ }
+ byte[] sh = new byte[segmentHeader.getSegmentSize()];
+ fileReadOffset += signedHap.read(sh);
+ if (segmentHeader.getType() == SegmentHeader.CSB_FSVERITY_INFO_SEG) {
+ // 3) parse fs-verity info segment
+ csb.setFsVerityInfoSegment(FsVerityInfoSegment.fromByteArray(sh));
+ } else if (segmentHeader.getType() == SegmentHeader.CSB_HAP_META_SEG) {
+ // 4) parse hap info segment
+ csb.setHapInfoSegment(HapInfoSegment.fromByteArray(sh));
+ } else if (segmentHeader.getType() == SegmentHeader.CSB_NATIVE_LIB_INFO_SEG) {
+ // 5) parse so info segment
+ csb.setSoInfoSegment(NativeLibInfoSegment.fromByteArray(sh));
+ }
+ }
+ if (fileReadOffset != csb.getCodeSignBlockHeader().getBlockSize()) {
+ throw new VerifyCodeSignException("Invalid blockSize of getCodeSignBlockHeader");
+ }
+ // parse merkle tree
+ Extension extension = csb.getHapInfoSegment()
+ .getSignInfo()
+ .getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED);
+ if (extension == null) {
+ throw new VerifyCodeSignException("Missing merkleTreeExtension in verifycation");
+ }
+ if (extension instanceof MerkleTreeExtension) {
+ MerkleTreeExtension mte = (MerkleTreeExtension) extension;
+ if (computedTreeOffset != mte.getMerkleTreeOffset()) {
+ throw new VerifyCodeSignException("Invalid merkle tree offset");
+ }
+ if (merkleTreeBytes.length != mte.getMerkleTreeSize()) {
+ throw new VerifyCodeSignException("Invalid merkle tree size");
+ }
+ csb.addOneMerkleTree(CodeSigning.HAP_SIGNATURE_ENTRY_NAME, merkleTreeBytes);
+ }
+ }
+
+ private static long getAlignmentAddr(long alignment, long input) {
+ long residual = input % alignment;
+ if (residual == 0) {
+ return input;
+ } else {
+ return input + (alignment - residual);
+ }
+ }
+
+ /**
+ * Verifies the signature of a given file with its signature in pkcs#7 format
+ *
+ * @param input file being verified in InputStream representation
+ * @param length size of signed data in the file
+ * @param signature byte array of signature in pkcs#7 format
+ * @param merkleTreeOffset merkle tree offset based on file start
+ * @param inMerkleTreeBytes merkle tree raw bytes
+ * @throws FsVerityDigestException if fs-verity digest generation failed
+ * @throws CMSException if signature verify failed
+ * @throws VerifyCodeSignException parsing code sign block error
+ */
+ public static void verifySingleFile(InputStream input, long length, byte[] signature, long merkleTreeOffset,
+ byte[] inMerkleTreeBytes) throws FsVerityDigestException, CMSException, VerifyCodeSignException {
+ Pair pairResult = generateFsVerityDigest(input, length, merkleTreeOffset);
+ byte[] generatedMerkleTreeBytes = pairResult.getSecond();
+ if (generatedMerkleTreeBytes == null) {
+ generatedMerkleTreeBytes = new byte[0];
+ }
+ // For native libs, inMerkleTreeBytes is null, skip check here
+ if ((inMerkleTreeBytes != null) && !Arrays.equals(inMerkleTreeBytes, generatedMerkleTreeBytes)) {
+ throw new VerifyCodeSignException("verify merkle tree bytes failed");
+ }
+ CmsUtils.verifySignDataWithUnsignedDataDigest(pairResult.getFirst(), signature);
+ }
+
+ private static Pair generateFsVerityDigest(InputStream inputStream, long size,
+ long merkleTreeOffset) throws FsVerityDigestException {
+ FsVerityGenerator fsVerityGenerator = new FsVerityGenerator();
+ fsVerityGenerator.generateFsVerityDigest(inputStream, size, merkleTreeOffset);
+ return Pair.create(fsVerityGenerator.getFsVerityDigest(), fsVerityGenerator.getTreeBytes());
+ }
+
+ /**
+ * Get entry name of all native files in hap
+ *
+ * @param hap the given hap
+ * @return list of entry name
+ */
+ private static List getNativeEntriesFromHap(JarFile hap) {
+ List result = new ArrayList<>();
+ for (Enumeration e = hap.entries(); e.hasMoreElements();) {
+ JarEntry entry = e.nextElement();
+ if (!entry.isDirectory()) {
+ if (!isNativeFile(entry.getName())) {
+ continue;
+ }
+ result.add(entry.getName());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Check whether the entry is a native file
+ *
+ * @param entryName the name of entry
+ * @return true if it is a native file, and false otherwise
+ */
+ private static boolean isNativeFile(String entryName) {
+ for (String suffix : EXTRACTED_NATIVE_LIB_SUFFIXS) {
+ if (entryName.endsWith(suffix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..df4b11a7d18f1de22400abd3c26d36f17946ec53
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.hapsigntool.codesigning.utils;
+
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+
+import java.security.Security;
+import java.security.cert.CertificateException;
+import java.util.Collection;
+
+/**
+ * CMS utils class
+ *
+ * @since 2023/06/05
+ */
+public class CmsUtils {
+ static {
+ if (Security.getProvider("BC") == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ }
+
+ /**
+ * Private constructor
+ */
+ private CmsUtils() {
+ }
+
+ private static void isCollectionValid(Collection collection)
+ throws OperatorCreationException {
+ if (collection == null) {
+ throw new OperatorCreationException("No matched cert: " + collection);
+ }
+ if (collection.size() != 1) {
+ throw new OperatorCreationException(
+ "More than one matched certs, matched certs size: " + collection.size());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static boolean verifyCmsSignedData(CMSSignedData cmsSignedData) throws CMSException {
+ return cmsSignedData.verifySignatures(signId -> {
+ Collection collection = cmsSignedData.getCertificates().getMatches(signId);
+ isCollectionValid(collection);
+ X509CertificateHolder cert = collection.iterator().next();
+ try {
+ return new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert);
+ } catch (CertificateException e) {
+ throw new OperatorCreationException("Verify BC signatures failed: " + e.getMessage(), e);
+ }
+ });
+ }
+
+ /**
+ * Verify signed data using an unsigned data digest
+ *
+ * @param unsignedDataDigest unsigned data digest
+ * @param signedData signed data
+ * @return true if verify success
+ * @throws CMSException if error
+ */
+ public static boolean verifySignDataWithUnsignedDataDigest(byte[] unsignedDataDigest, byte[] signedData)
+ throws CMSException {
+ CMSSignedData cmsSignedData = new CMSSignedData(new CMSProcessableByteArray(unsignedDataDigest), signedData);
+ return verifyCmsSignedData(cmsSignedData);
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..aafbaf577149d08d65b1f4052d54ed1e5836bd0b
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.hapsigntool.codesigning.utils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Digest util class
+ *
+ * @since 2023/06/05
+ */
+public class DigestUtils {
+ /**
+ * digest the inputContent with specific algorithm
+ *
+ * @param inputContentArray input Content Array
+ * @param algorithm hash algorithm
+ * @return the result of digest, is a byte array
+ * @throws NoSuchAlgorithmException if error
+ */
+ public static byte[] computeDigest(byte[] inputContentArray, String algorithm) throws NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ md.update(inputContentArray);
+ return md.digest();
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..d3c9ab4f57926de83038c014a43404c786791a0c
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.hapsigntool.codesigning.utils;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * utility for check hap configs
+ *
+ * @since 2023/06/05
+ */
+public class HapUtils {
+ private static final String COMPRESS_NATIVE_LIBS_OPTION = "compressNativeLibs";
+
+ private static final List HAP_CONFIG_FILES = new ArrayList<>();
+
+ private static final String HAP_FA_CONFIG_JSON_FILE = "config.json";
+
+ private static final String HAP_STAGE_MODULE_JSON_FILE = "module.json";
+
+ static {
+ HAP_CONFIG_FILES.add(HAP_FA_CONFIG_JSON_FILE);
+ HAP_CONFIG_FILES.add(HAP_STAGE_MODULE_JSON_FILE);
+ }
+
+ private HapUtils() {
+ }
+
+ /**
+ * Check configuration in hap to find out whether the native libs are compressed
+ *
+ * @param hapFile the given hap
+ * @return boolean value of parsing result
+ * @throws IOException io error
+ */
+ public static boolean checkCompressNativeLibs(File hapFile) throws IOException {
+ try (JarFile inputJar = new JarFile(hapFile, false)) {
+ for (String configFile : HAP_CONFIG_FILES) {
+ JarEntry entry = inputJar.getJarEntry(configFile);
+ if (entry == null) {
+ continue;
+ }
+ try (InputStream data = inputJar.getInputStream(entry)) {
+ String jsonString = new String(InputStreamUtils.toByteArray(data, (int) entry.getSize()),
+ StandardCharsets.UTF_8);
+ return checkCompressNativeLibs(jsonString);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check whether the native libs are compressed by parsing config json
+ *
+ * @param jsonString the config json string
+ * @return boolean value of parsing result
+ */
+ public static boolean checkCompressNativeLibs(String jsonString) {
+ JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject();
+ Queue queue = new LinkedList<>();
+ queue.offer(jsonObject);
+ while (queue.size() > 0) {
+ JsonObject curJsonObject = queue.poll();
+ JsonElement jsonElement = curJsonObject.get(COMPRESS_NATIVE_LIBS_OPTION);
+ if (jsonElement != null) {
+ return jsonElement.getAsBoolean();
+ }
+ for (Map.Entry entry : curJsonObject.entrySet()) {
+ if (entry.getValue().isJsonObject()) {
+ queue.offer(entry.getValue().getAsJsonObject());
+ }
+ }
+ }
+ // default to compress native libs
+ return true;
+ }
+}
diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..816ea38c1138737404e0bdadab2e196c5b8d3cfd
--- /dev/null
+++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ohos.hapsigntool.codesigning.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * InputStream util class
+ *
+ * @since 2023/08/10
+ */
+public class InputStreamUtils {
+ private static final int BUFFER_SIZE = 4096;
+
+ /**
+ * get byte array by inputStream and size
+ *
+ * @param inputStream inputStream data
+ * @param inputStreamSize inputStream size
+ * @return byte array value
+ * @throws IOException io error
+ */
+ public static byte[] toByteArray(InputStream inputStream, int inputStreamSize) throws IOException {
+ if (inputStreamSize == 0) {
+ return new byte[0];
+ }
+ if (inputStreamSize < 0) {
+ throw new IllegalArgumentException("inputStreamSize: " + inputStreamSize + "is less than zero: ");
+ }
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy(inputStream, inputStreamSize, output);
+ return output.toByteArray();
+ }
+
+ private static int copy(InputStream inputStream, int inputStreamSize, OutputStream output) throws IOException {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int readSize = 0;
+ int count = 0;
+ while (readSize < inputStreamSize && (readSize = inputStream.read(buffer)) != -1) {
+ output.write(buffer, 0, readSize);
+ count += readSize;
+ }
+ if (count != inputStreamSize) {
+ throw new IOException("read size err. readSizeCount: " + count + ", inputStreamSize: " + inputStreamSize);
+ }
+ return count;
+ }
+}