diff --git a/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/CmdUnitTest.java b/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/CmdUnitTest.java index b5f568ce327d85841a6d203a38df1428a1c3839b..26854416acd56e45e600af3719b87d2e6081db47 100644 --- a/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/CmdUnitTest.java +++ b/hapsigntool/hap_sign_tool/src/test/java/com/ohos/hapsigntoolcmd/CmdUnitTest.java @@ -958,7 +958,7 @@ public class CmdUnitTest { CMD_SIGN_ALG, CMD_SHA_256_WITH_ECDSA, CMD_KEY_STORE_FILE, CMD_KEY_APP_STORE_PATH, CMD_KEY_STORE_RIGHTS, CMD_RIGHTS_123456, - CMD_IN_FILE, unsignedHap, + CMD_IN_FILE, signedHap, CMD_OUT_FILE, signedHap }); assertTrue(result); 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 index 2d23c548812465c6419fea07d40299268c9121cb..6adad959e5cb30372dea9786dbb3e2087a3b6303 100644 --- 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 @@ -38,7 +38,7 @@ public class Extension { private final int type; - private final int size; + private int size; public Extension(int type, int size) { this.type = type; @@ -49,6 +49,10 @@ public class Extension { return EXTENSION_HEADER_SIZE; } + public void setSize(int size) { + this.size = size; + } + public boolean isType(int type) { return this.type == type; } 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 index 2697ea7af826915595ec20b941f6dec06f82f964..42865040fa13681f3653fe548a649af4deef31cd 100644 --- 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 @@ -16,6 +16,10 @@ package com.ohos.hapsigntool.codesigning.datastructure; import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.utils.NumberUtils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -33,6 +37,8 @@ import java.util.Locale; * @since 2023/09/08 */ public class HapInfoSegment { + private static final Logger LOGGER = LogManager.getLogger(HapInfoSegment.class); + private static final int MAGIC_NUM_BYTES = 4; /** @@ -116,14 +122,11 @@ public class 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) { + if (!NumberUtils.isMultiple4K(inHapSignInfo.getDataSize())) { 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"); } 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 index 303d92edf13effedc56dec7605e4b2c3bd5aa908..96bc17eef15c91635ae4cb3caa2e783b476ac8c3 100644 --- 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 @@ -16,6 +16,7 @@ package com.ohos.hapsigntool.codesigning.datastructure; import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.utils.NumberUtils; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -98,8 +99,7 @@ public class MerkleTreeExtension extends Extension { */ @Override public byte[] toByteArray() { - ByteBuffer bf = ByteBuffer.allocate(Extension.EXTENSION_HEADER_SIZE + MERKLE_TREE_EXTENSION_DATA_SIZE) - .order(ByteOrder.LITTLE_ENDIAN); + ByteBuffer bf = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN); bf.put(super.toByteArray()); bf.putLong(this.merkleTreeSize); bf.putLong(this.merkleTreeOffset); @@ -119,11 +119,11 @@ public class MerkleTreeExtension extends Extension { bf.put(bytes); bf.rewind(); long inMerkleTreeSize = bf.getLong(); - if (inMerkleTreeSize % CodeSignBlock.PAGE_SIZE_4K != 0) { + if (!NumberUtils.isMultiple4K(inMerkleTreeSize)) { throw new VerifyCodeSignException("merkleTreeSize is not a multiple of 4096"); } long inMerkleTreeOffset = bf.getLong(); - if (inMerkleTreeOffset % CodeSignBlock.PAGE_SIZE_4K != 0) { + if (!NumberUtils.isMultiple4K(inMerkleTreeOffset)) { throw new VerifyCodeSignException("merkleTreeOffset is not a aligned to 4096"); } byte[] inRootHash = new byte[ROOT_HASH_SIZE]; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/PageInfoExtension.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/PageInfoExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..377de03c86275b2c0160ec26d318c974e5d575f1 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/PageInfoExtension.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2024-2024 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.utils.NumberUtils; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Pages info extension is a type of Extension to store bitmap file's information, i.e. size and offset, ect. + *

+ * structure + *

+ * 1) u32 type 0x2 + *

+ * 2) u64 mapOffset: offset of the bitmap by the start of the file. + *

+ * 3) u64 mapSize: the bit size of bitmap. + *

+ * 4) u8 unitSize: unit size corresponding to each page, default 4 . + *

+ * 5) u8[3] reserved: + *

+ * 6) u32 signSize: signature size + *

+ * 7) u8[] signature: signature of the data + * + * @since 2024/07/01 + */ +public class PageInfoExtension extends Extension { + /** + * Type of PageInfoExtension + */ + public static final int PAGE_INFO_INLINED = 0x2; + + /** + * Byte size of PageInfoExtension + */ + public static final int PAGE_INFO_EXTENSION_DATA_SIZE_WITHOUT_SIGN = 24; + + /** + * default unit size + */ + public static final int DEFAULT_UNIT_SIZE = 4; + + private static final int RESERVED_SIZE = 3; + + private static final int SIGNATURE_ALIGNMENT = 4; + + private long mapOffset; + + private long mapSize; + + private byte unitSize; + + private byte[] reserved = new byte[RESERVED_SIZE]; + + private int signSize; + + private byte[] signature = new byte[0]; + + private byte[] zeroPadding = new byte[0]; + + /** + * Constructor for PageInfoExtension + * + * @param mapOffset bitmap offset + * @param mapSize bit size + */ + public PageInfoExtension(long mapOffset, long mapSize) { + super(PAGE_INFO_INLINED, PAGE_INFO_EXTENSION_DATA_SIZE_WITHOUT_SIGN); + this.mapOffset = mapOffset; + this.mapSize = mapSize; + unitSize = DEFAULT_UNIT_SIZE; + } + + public void setSignature(byte[] signature) { + if (signature != null) { + this.signSize = signature.length; + this.signature = signature; + this.zeroPadding = new byte[(SIGNATURE_ALIGNMENT - (signSize % SIGNATURE_ALIGNMENT)) % SIGNATURE_ALIGNMENT]; + } + super.setSize(size() - Extension.EXTENSION_HEADER_SIZE); + } + + public long getMapOffset() { + return mapOffset; + } + + public long getMapSize() { + return mapSize; + } + + public byte getUnitSize() { + return unitSize; + } + + /** + * Byte size of PageInfoExtension + * + * @return Byte size of PageInfoExtension + */ + @Override + public int size() { + return Extension.EXTENSION_HEADER_SIZE + PAGE_INFO_EXTENSION_DATA_SIZE_WITHOUT_SIGN + signSize + + zeroPadding.length; + } + + /** + * Converts PageInfoExtension to a newly created byte array + * + * @return Byte array representation of PageInfoExtension + */ + @Override + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN); + bf.put(super.toByteArray()); + bf.putLong(this.mapOffset); + bf.putLong(this.mapSize); + bf.put(this.unitSize); + bf.put(this.reserved); + bf.putInt(this.signSize); + bf.put(this.signature); + bf.put(this.zeroPadding); + return bf.array(); + } + + /** + * Init the PageInfoExtension by a byte array + * + * @param bytes Byte array representation of a PageInfoExtension object + * @return a newly created PageInfoExtension object + * @throws VerifyCodeSignException parse result invalid + */ + public static PageInfoExtension fromByteArray(byte[] bytes) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + bf.rewind(); + long inMapOffset = bf.getLong(); + if (!NumberUtils.isMultiple4K(inMapOffset)) { + throw new VerifyCodeSignException("mapOffset is not a multiple of 4096"); + } + long inMapSize = bf.getLong(); + byte inUnitSize = bf.get(); + if (inMapSize % inUnitSize != 0) { + throw new VerifyCodeSignException("mapSize is not a multiple of unitSize"); + } + bf.get(new byte[RESERVED_SIZE]); + int inSignSize = bf.getInt(); + byte[] inSignature = new byte[inSignSize]; + bf.get(inSignature); + PageInfoExtension extension = new PageInfoExtension(inMapOffset, inMapSize); + extension.unitSize = inUnitSize; + extension.setSignature(inSignature); + return extension; + } + + @Override + public String toString() { + return String.format(Locale.ROOT, + "PageInfoExtension: size[%d], mapOffset[%d], mapSize[%d], unitSize[%d], signSize[%d]", size(), + this.mapOffset, this.mapSize, this.unitSize, this.signSize); + } +} 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 index e2d538c4f3dc2cd6d05518caf9240c7be54b9d50..748b92a2e518316677affb34ebea5670a1d9c5e8 100644 --- 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 @@ -17,6 +17,9 @@ package com.ohos.hapsigntool.codesigning.datastructure; import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; @@ -58,7 +61,9 @@ public class SignInfo { /** * maximum of extension number */ - public static final int MAX_EXTENSION_NUM = 1; + public static final int MAX_EXTENSION_NUM = 2; + + private static final Logger LOGGER = LogManager.getLogger(SignInfo.class); /** * sign info structure without signature in bytes, refer to toByteArray() method @@ -112,6 +117,7 @@ public class SignInfo { this.sigSize = sig == null ? 0 : sig.length; // align for extension after signature this.zeroPadding = new byte[(SIGNATURE_ALIGNMENT - (this.sigSize % SIGNATURE_ALIGNMENT)) % SIGNATURE_ALIGNMENT]; + this.extensionOffset = SIGN_INFO_SIZE_WITHOUT_SIGNATURE + sigSize + this.zeroPadding.length; } /** @@ -138,7 +144,6 @@ public class SignInfo { * @param extension Extension object */ public void addExtension(Extension extension) { - this.extensionOffset = this.size(); this.extensionList.add(extension); this.extensionNum = this.extensionList.size(); } @@ -242,7 +247,8 @@ public class SignInfo { bf.get(inSalt); int inExtensionNum = bf.getInt(); if (inExtensionNum < 0 || inExtensionNum > MAX_EXTENSION_NUM) { - throw new VerifyCodeSignException("Invalid extensionNum of SignInfo"); + LOGGER.info("The signature information may be generated by an new tool, extensionNum {} of SignInfo", + inExtensionNum); } int inExtensionOffset = bf.getInt(); if (inExtensionOffset < 0 || inExtensionOffset % 4 != 0) { @@ -254,7 +260,7 @@ public class SignInfo { % SIGNATURE_ALIGNMENT]; bf.get(inZeroPadding); // parse merkle tree extension - List inExtensionList = parseMerkleTreeExtension(bf, inExtensionNum); + List inExtensionList = parseExtensionList(bf, inExtensionNum); return new SignInfoBuilder().setSaltSize(inSaltSize) .setSigSize(inSigSize) .setFlags(inFlags) @@ -268,22 +274,32 @@ public class SignInfo { .build(); } - private static List parseMerkleTreeExtension(ByteBuffer bf, int inExtensionNum) + private static List parseExtensionList(ByteBuffer bf, int inExtensionNum) throws VerifyCodeSignException { List inExtensionList = new ArrayList<>(); - if (inExtensionNum == 1) { - // parse merkle tree extension + for (int i = 0; i < inExtensionNum; i++) { 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"); + if (extensionType == MerkleTreeExtension.MERKLE_TREE_INLINED) { + // parse merkle tree extension + int extensionSize = bf.getInt(); + if (extensionSize != (MerkleTreeExtension.MERKLE_TREE_EXTENSION_DATA_SIZE)) { + throw new VerifyCodeSignException("Invalid MerkleTree extensionSize of SignInfo"); + } + byte[] merkleTreeExtension = new byte[extensionSize]; + bf.get(merkleTreeExtension); + inExtensionList.add(MerkleTreeExtension.fromByteArray(merkleTreeExtension)); + } else if (extensionType == PageInfoExtension.PAGE_INFO_INLINED) { + // parse page info extension + int extensionSize = bf.getInt(); + if (extensionSize < (PageInfoExtension.PAGE_INFO_EXTENSION_DATA_SIZE_WITHOUT_SIGN)) { + throw new VerifyCodeSignException("Invalid PageInfo extensionSize of SignInfo"); + } + byte[] pageInfoExtension = new byte[extensionSize]; + bf.get(pageInfoExtension); + inExtensionList.add(PageInfoExtension.fromByteArray(pageInfoExtension)); + } else { + LOGGER.info("Invalid extensionType {} of SignInfo", extensionType); } - byte[] merkleTreeExtension = new byte[MerkleTreeExtension.MERKLE_TREE_EXTENSION_DATA_SIZE]; - bf.get(merkleTreeExtension); - inExtensionList.add(MerkleTreeExtension.fromByteArray(merkleTreeExtension)); } return inExtensionList; } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfDefine.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfDefine.java new file mode 100644 index 0000000000000000000000000000000000000000..288d5daeec73c3d735d6f4c6bc17c8baab741c20 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfDefine.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024-2024 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.elf; + +/** + * ELF struct define + * + * @since 2024/07/01 + */ +public interface ElfDefine { + /** + * 32-bit elf file + */ + byte ELF_32_CLASS = 1; + + /** + * 64-bit elf file + */ + byte ELF_64_CLASS = 2; + + /** + * little endian + */ + byte ELF_DATA_2_LSB = 1; + + /** + * big endian + */ + byte ELF_DATA_2_MSB = 2; + + /** + * 32-bit elf file's program header length + */ + int ELF_PHEADER_32_LEN = 32; + + /** + * 64-bit elf file's program header length + */ + int ELF_PHEADER_64_LEN = 56; + + /** + * elf header e_ident length + */ + int EI_NIDENT_LEN = 16; + + /** + * 32-bit elf header length + */ + int ELF_HEADER_32_LEN = EI_NIDENT_LEN + 36; + + /** + * 64-bit elf header length + */ + int ELF_HEADER_64_LEN = EI_NIDENT_LEN + 48; +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfFile.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfFile.java new file mode 100644 index 0000000000000000000000000000000000000000..78f5c39663757778a0cd66f8a70fe789a4751e60 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfFile.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024-2024 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.elf; + +import com.ohos.hapsigntool.codesigning.exception.ElfFormatException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * ELF file + * + * @since 2024/07/01 + */ +public class ElfFile { + private ElfHeader elfHeader; + + private final List programHeaderList = new ArrayList<>(); + + /** + * Constructor for ElfFile + * + * @param is InputStream + * @throws IOException io error + * @throws ElfFormatException elf file format error + */ + public ElfFile(InputStream is) throws IOException, ElfFormatException { + elfHeader = new ElfHeader(is); + if (!isElfFile()) { + return; + } + byte eiClass = elfHeader.getEiClass(); + byte eiData = elfHeader.getEiData(); + int ePhnum = elfHeader.getEPhnum(); + long ePhOff = elfHeader.getEPhOff(); + if (eiClass == ElfDefine.ELF_32_CLASS) { + is.skip(ePhOff - ElfDefine.ELF_HEADER_32_LEN); + } else if (eiClass == ElfDefine.ELF_64_CLASS) { + is.skip(ePhOff - ElfDefine.ELF_HEADER_64_LEN); + } + for (int i = 0; i < ePhnum; i++) { + ElfProgramHeader pHeader = new ElfProgramHeader(is, eiClass, eiData); + programHeaderList.add(pHeader); + } + } + + /** + * filter executable program segment headers + * + * @return executable program segment headers + */ + public List filterExecPHeaders() { + return programHeaderList.stream().filter(phdr -> (phdr.getPFlags() & 1) == 1).collect(Collectors.toList()); + } + + /** + * return true if magic number is correct + * + * @return true if magic number is correct + */ + public final boolean isElfFile() { + return elfHeader.isElfFile(); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..3d246d46b81288eba6a06196cac02a0bb08f8bc9 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfHeader.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2024-2024 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.elf; + +import com.ohos.hapsigntool.codesigning.exception.ElfFormatException; +import com.ohos.hapsigntool.zip.UnsignedDecimalUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * ELF header info + * + * @since 2024/07/01 + */ +public class ElfHeader { + /** + * Magic number and other info + */ + private byte[] ident = new byte[ElfDefine.EI_NIDENT_LEN]; + + private boolean isElfFile; + + /** + * 32-bit or 64-bit file + */ + private byte eiClass; + + /** + * LITTLE_ENDIAN or BIG_ENDIAN + */ + private byte eiData; + + /** + * elf version + */ + private byte eiVersion; + + /** + * Object file type + */ + private short eType; + + /** + * Architecture + */ + private int eMachine; + + /** + * Object file version + */ + private int eVersion; + + /** + * Entry point virtual address + */ + private long eEntry; + + /** + * Program header table file offset + */ + private long ePhOff; + + /** + * Section header table file offset + */ + private long eShOff; + + /** + * Processor-specific flags + */ + private int eFlags; + + /** + * ELF header size in bytes + */ + private short eEhSize; + + /** + * Program header table entry size + */ + private short ePhEntSize; + + /** + * Program header table entry count + */ + private int ePhNum; + + /** + * Section header table entry size + */ + private short eShEntSize; + + /** + * Section header table entry count + */ + private short eShNum; + + /** + * Section header string table index + */ + private short eShStrndx; + + /** + * Constructor for ElfHeader + * + * @param is InputStream + * @throws IOException io error + * @throws ElfFormatException elf file format error + */ + public ElfHeader(InputStream is) throws IOException, ElfFormatException { + int read = is.read(ident); + isElfFile = isElfFile(ident); + if (read != ident.length || !isElfFile) { + return; + } + eiClass = ident[4]; + eiData = ident[5]; + eiVersion = ident[6]; + int len; + if (eiClass == ElfDefine.ELF_32_CLASS) { + len = ElfDefine.ELF_HEADER_32_LEN - ElfDefine.EI_NIDENT_LEN; + } else if (eiClass == ElfDefine.ELF_64_CLASS) { + len = ElfDefine.ELF_HEADER_64_LEN - ElfDefine.EI_NIDENT_LEN; + } else { + throw new ElfFormatException("ELF eiClass is incorrect"); + } + ByteOrder bo; + if (eiData == ElfDefine.ELF_DATA_2_LSB) { + bo = ByteOrder.LITTLE_ENDIAN; + } else if (eiData == ElfDefine.ELF_DATA_2_MSB) { + bo = ByteOrder.BIG_ENDIAN; + } else { + throw new ElfFormatException("ELF eiData is incorrect"); + } + byte[] bytes = new byte[len]; + read = is.read(bytes); + if (read != len) { + throw new ElfFormatException("ELF file header is incorrect"); + } + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(bo); + initHeader(byteBuffer); + } + + private void initHeader(ByteBuffer byteBuffer) throws ElfFormatException { + eType = byteBuffer.getShort(); + eMachine = byteBuffer.getShort(); + eVersion = byteBuffer.getInt(); + + if (eiClass == ElfDefine.ELF_32_CLASS) { + eEntry = UnsignedDecimalUtil.getUnsignedInt(byteBuffer); + ePhOff = UnsignedDecimalUtil.getUnsignedInt(byteBuffer); + eShOff = UnsignedDecimalUtil.getUnsignedInt(byteBuffer); + } else { + eEntry = byteBuffer.getLong(); + ePhOff = byteBuffer.getLong(); + eShOff = byteBuffer.getLong(); + } + eFlags = byteBuffer.getInt(); + eEhSize = byteBuffer.getShort(); + ePhEntSize = byteBuffer.getShort(); + ePhNum = UnsignedDecimalUtil.getUnsignedShort(byteBuffer); + eShEntSize = byteBuffer.getShort(); + eShNum = byteBuffer.getShort(); + eShStrndx = byteBuffer.getShort(); + } + + public byte getEiClass() { + return eiClass; + } + + public byte getEiData() { + return eiData; + } + + public long getEPhOff() { + return ePhOff; + } + + public int getEPhnum() { + return ePhNum; + } + + /** + * elf file start with [0x7F 0x45 0x4C 0x46] + * + * @param bytes byte array + * @return true if start with [0x7F 0x45 0x4C 0x46] + */ + public static boolean isElfFile(byte[] bytes) { + if (bytes == null || bytes.length < 4) { + return false; + } + return bytes[0] == 0x7F && bytes[1] == 0x45 && bytes[2] == 0x4C && bytes[3] == 0x46; + } + + /** + * return true if magic number is correct + * + * @return true if magic number is correct + */ + public boolean isElfFile() { + return isElfFile; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfProgramHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfProgramHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..91d2a3350e087ead8804deef0b21a22664281246 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/elf/ElfProgramHeader.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2024-2024 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.elf; + +import com.ohos.hapsigntool.codesigning.exception.ElfFormatException; +import com.ohos.hapsigntool.zip.UnsignedDecimalUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * ELF program header info + * + * @since 2024/07/01 + */ +public class ElfProgramHeader { + /** + * Segment type + */ + private int pType; + + /** + * Segment flags + */ + private int pFlags; + + /** + * Segment file offset + */ + private long pOffset; + + /** + * Segment virtual address + */ + private long pVaddr; + + /** + * Segment physical address + */ + private long pPaddr; + + /** + * Segment size in file + */ + private long pFilesz; + + /** + * Segment size in memory + */ + private long pMemsz; + + /** + * Segment alignment + */ + private long pAlign; + + /** + * Constructor for ElfPHeader + * + * @param is InputStream + * @param eiClass eiClass + * @param eiData eiData + * @throws IOException io error + * @throws ElfFormatException elf file format error + */ + public ElfProgramHeader(InputStream is, byte eiClass, byte eiData) throws IOException, ElfFormatException { + ByteOrder bo = ByteOrder.LITTLE_ENDIAN; + if (eiData == ElfDefine.ELF_DATA_2_LSB) { + bo = ByteOrder.LITTLE_ENDIAN; + } else if (eiData == ElfDefine.ELF_DATA_2_MSB) { + bo = ByteOrder.BIG_ENDIAN; + } else { + throw new ElfFormatException("ELF ei_data is incorrect"); + } + if (eiClass == ElfDefine.ELF_32_CLASS) { + byte[] bytes = new byte[ElfDefine.ELF_PHEADER_32_LEN]; + int read = is.read(bytes); + if (read != ElfDefine.ELF_PHEADER_32_LEN) { + throw new ElfFormatException("ELF program header is incorrect"); + } + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(bo); + pType = byteBuffer.getInt(); + pOffset = UnsignedDecimalUtil.getUnsignedInt(byteBuffer); + pVaddr = UnsignedDecimalUtil.getUnsignedInt(byteBuffer); + pPaddr = UnsignedDecimalUtil.getUnsignedInt(byteBuffer); + pFilesz = byteBuffer.getInt(); + pMemsz = byteBuffer.getInt(); + pFlags = byteBuffer.getInt(); + pAlign = byteBuffer.getInt(); + } else { + byte[] bytes = new byte[ElfDefine.ELF_PHEADER_64_LEN]; + int read = is.read(bytes); + if (read != ElfDefine.ELF_PHEADER_64_LEN) { + throw new ElfFormatException("ELF program header is incorrect"); + } + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(bo); + pType = byteBuffer.getInt(); + pFlags = byteBuffer.getInt(); + pOffset = byteBuffer.getLong(); + pVaddr = byteBuffer.getLong(); + pPaddr = byteBuffer.getLong(); + pFilesz = byteBuffer.getLong(); + pMemsz = byteBuffer.getLong(); + pAlign = byteBuffer.getLong(); + } + } + + public int getPFlags() { + return pFlags; + } + + public long getPOffset() { + return pOffset; + } + + public long getPFilesz() { + return pFilesz; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/ElfFormatException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/ElfFormatException.java new file mode 100644 index 0000000000000000000000000000000000000000..1089f073a2aea8458ca2a21463947d908344ed77 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/ElfFormatException.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024-2024 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; + +/** + * Elf format exception + * + * @since 2024/07/01 + */ +public class ElfFormatException extends Exception { + public ElfFormatException(String message) { + super(message); + } + + public ElfFormatException(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 index 5531b7f837b0b55054bc7a1db9291d2e94f38916..da6f8ec11e7e9eb6c1c37f2aa4c6744e4164d830 100644 --- 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 @@ -17,6 +17,7 @@ package com.ohos.hapsigntool.codesigning.fsverity; import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.utils.NumberUtils; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -65,6 +66,11 @@ public class FsVerityDescriptor { */ public static final byte CODE_SIGN_VERSION = 0x1; + /** + * code sign version + */ + public static final byte CODE_SIGN_VERSION_V2 = 0x2; + /** * FsVerity descriptor size */ @@ -83,12 +89,7 @@ public class FsVerityDescriptor { /** * reserved size */ - public static final int RESERVED_SIZE_AFTER_FLAGS = 4; - - /** - * reserved size - */ - public static final int RESERVED_SIZE_AFTER_TREE_OFFSET = 127; + public static final int RESERVED_SIZE_AFTER_TREE_OFFSET = 119; private byte version; @@ -108,8 +109,12 @@ public class FsVerityDescriptor { private int flags; + private int bitMapSize; + private long merkleTreeOffset; + private long bitMapOffset; + private byte csVersion; private FsVerityDescriptor(Builder builder) { @@ -152,7 +157,7 @@ public class FsVerityDescriptor { bf.rewind(); FsVerityDescriptor.Builder builder = new FsVerityDescriptor.Builder(); byte inFsVersion = bf.get(); - if (FsVerityDescriptor.VERSION != inFsVersion) { + if (inFsVersion != FsVerityDescriptor.VERSION) { throw new VerifyCodeSignException("Invalid fs-verify descriptor version of ElfSignBlock"); } byte inFsHashAlgorithm = bf.get(); @@ -169,7 +174,7 @@ public class FsVerityDescriptor { int inFlags = bf.getInt(); bf.getInt(); long inTreeOffset = bf.getLong(); - if (inTreeOffset % PAGE_SIZE_4K != 0) { + if (!NumberUtils.isMultiple4K(inTreeOffset)) { throw new VerifyCodeSignException("Invalid merkle tree offset of ElfSignBlock"); } bf.get(new byte[FsVerityDescriptor.RESERVED_SIZE_AFTER_TREE_OFFSET]); @@ -198,8 +203,9 @@ public class FsVerityDescriptor { writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE); writeBytesWithSize(buffer, salt, SALT_SIZE); buffer.putInt(flags); - writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_FLAGS); + buffer.putInt(0); buffer.putLong(merkleTreeOffset); + buffer.putLong(0); writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_TREE_OFFSET); buffer.put(csVersion); return buffer.array(); @@ -211,7 +217,7 @@ public class FsVerityDescriptor { * @return bytes of descriptor * @throws FsVerityDigestException if error */ - public byte[] getByteForGenerateDigest() throws FsVerityDigestException { + public byte[] getDiscByte() throws FsVerityDigestException { ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN); buffer.put(CODE_SIGN_VERSION); buffer.put(hashAlgorithm); @@ -225,8 +231,39 @@ public class FsVerityDescriptor { writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE); writeBytesWithSize(buffer, salt, SALT_SIZE); buffer.putInt(flags); - writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_FLAGS); + buffer.putInt(0); + buffer.putLong(merkleTreeOffset); + return buffer.array(); + } + + /** + * Get bytes for generate digest, cs_version 2 + * + * @param mapOffset bit map data offset at file + * @param mapSize bit map size + * @param unitSize bit map unit size corresponding to each page + * @return bytes of descriptor + * @throws FsVerityDigestException if error + */ + public byte[] getDiscByteCsv2(long mapOffset, long mapSize, byte unitSize) 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(0); + buffer.putLong(fileSize); + writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE); + writeBytesWithSize(buffer, salt, SALT_SIZE); + buffer.putInt((unitSize << 1 | flags)); + buffer.putInt((int) mapSize); buffer.putLong(merkleTreeOffset); + buffer.putLong(mapOffset); + writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_TREE_OFFSET); + buffer.put(CODE_SIGN_VERSION_V2); return buffer.array(); } @@ -271,8 +308,12 @@ public class FsVerityDescriptor { private int flags; + private int bitMapSize; + private long merkleTreeOffset; + private long bitMapOffset; + private byte csVersion; public Builder setVersion(byte version) { 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 index 57501d09d6ab5e056e991a4fcd669340b362fa94..c7a674bbb6e316b55b6c22d9c06777f3d0ebff34 100644 --- 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 @@ -15,6 +15,7 @@ package com.ohos.hapsigntool.codesigning.fsverity; +import com.ohos.hapsigntool.codesigning.datastructure.PageInfoExtension; import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; import com.ohos.hapsigntool.codesigning.utils.DigestUtils; @@ -42,10 +43,23 @@ public class FsVerityGenerator { private byte[] fsVerityDigest = null; + private byte[] fsVerityDigestV2 = null; + private byte[] treeBytes = null; private byte[] rootHash = null; + private PageInfoExtension pageInfoExtension; + + /** + * Constructor for FsVerityGenerator + * + * @param pg PageInfoExtension + */ + public void setPageInfoExtension(PageInfoExtension pg) { + this.pageInfoExtension = pg; + } + /** * generate merkle tree of given input * @@ -94,14 +108,25 @@ public class FsVerityGenerator { .setRawRootHash(merkleTree.rootHash) .setFlags(flags) .setMerkleTreeOffset(fsvTreeOffset); - byte[] fsVerityDescriptor = builder.build().getByteForGenerateDigest(); - byte[] digest; try { - digest = DigestUtils.computeDigest(fsVerityDescriptor, FS_VERITY_HASH_ALGORITHM.getHashAlgorithm()); + byte[] fsVerityDescriptor = builder.build().getDiscByte(); + byte[] digest = DigestUtils.computeDigest(fsVerityDescriptor, FS_VERITY_HASH_ALGORITHM.getHashAlgorithm()); + fsVerityDigest = FsVerityDigest.getFsVerityDigest(FS_VERITY_HASH_ALGORITHM.getId(), digest); } catch (NoSuchAlgorithmException e) { throw new FsVerityDigestException("Invalid algorithm" + e.getMessage(), e); } - fsVerityDigest = FsVerityDigest.getFsVerityDigest(FS_VERITY_HASH_ALGORITHM.getId(), digest); + if (pageInfoExtension != null && flags != 0) { + try { + byte[] fsVerityDescriptorV2 = builder.build() + .getDiscByteCsv2(pageInfoExtension.getMapOffset(), pageInfoExtension.getMapSize(), + pageInfoExtension.getUnitSize()); + byte[] digest = DigestUtils.computeDigest(fsVerityDescriptorV2, + FS_VERITY_HASH_ALGORITHM.getHashAlgorithm()); + fsVerityDigestV2 = FsVerityDigest.getFsVerityDigest(FS_VERITY_HASH_ALGORITHM.getId(), digest); + } catch (NoSuchAlgorithmException e) { + throw new FsVerityDigestException("Invalid algorithm" + e.getMessage(), e); + } + } treeBytes = merkleTree.tree; rootHash = merkleTree.rootHash; } @@ -115,6 +140,15 @@ public class FsVerityGenerator { return fsVerityDigest; } + /** + * Get FsVerity digest + * + * @return bytes of FsVerity digest + */ + public byte[] getFsVerityDigestV2() { + return fsVerityDigestV2; + } + /** * Get merkle tree in bytes * 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 index c450dd116d62002ec2196eb895e048518e1bf546..e9b2b509d97b4be56a4e8ec5bbe96da03637ee2e 100644 --- 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 @@ -20,13 +20,16 @@ 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.PageInfoExtension; import com.ohos.hapsigntool.codesigning.datastructure.SignInfo; +import com.ohos.hapsigntool.codesigning.elf.ElfHeader; 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.codesigning.utils.NumberUtils; import com.ohos.hapsigntool.entity.Pair; import com.ohos.hapsigntool.error.HapFormatException; import com.ohos.hapsigntool.error.ProfileException; @@ -34,6 +37,7 @@ import com.ohos.hapsigntool.hap.config.SignerConfig; import com.ohos.hapsigntool.signer.LocalSigner; import com.ohos.hapsigntool.utils.FileUtils; import com.ohos.hapsigntool.utils.StringUtils; +import com.ohos.hapsigntool.zip.EntryType; import com.ohos.hapsigntool.zip.Zip; import com.ohos.hapsigntool.zip.ZipEntry; import com.ohos.hapsigntool.zip.ZipEntryHeader; @@ -79,14 +83,12 @@ public class CodeSigning { 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 SignerConfig signConfig; private CodeSignBlock codeSignBlock; + private PageInfoExtension pageInfoExtension; + /** * provide code sign functions to sign a hap * @@ -181,7 +183,6 @@ public class CodeSigning { LOGGER.debug("Sign hap."); String ownerID = HapUtils.getAppIdentifier(profileContent); - try (FileInputStream inputStream = new FileInputStream(input)) { Pair hapSignInfoAndMerkleTreeBytesPair = signFile(inputStream, dataSize, true, fsvTreeOffset, ownerID); @@ -207,12 +208,26 @@ public class CodeSigning { return generated; } + private void createPageInfoExtension(ZipEntry entry) { + long bitmapOff = entry.getCentralDirectory().getOffset() + ZipEntryHeader.HEADER_LENGTH + + entry.getZipEntryData().getZipEntryHeader().getFileNameLength() + entry.getZipEntryData() + .getZipEntryHeader() + .getExtraLength(); + long bitmapSize = bitmapOff / CodeSignBlock.PAGE_SIZE_4K * PageInfoExtension.DEFAULT_UNIT_SIZE; + pageInfoExtension = new PageInfoExtension(bitmapOff, bitmapSize); + } + private long computeDataSize(Zip zip) throws HapFormatException { long dataSize = 0L; for (ZipEntry entry : zip.getZipEntries()) { ZipEntryHeader zipEntryHeader = entry.getZipEntryData().getZipEntryHeader(); - if (FileUtils.isRunnableFile(zipEntryHeader.getFileName()) - && zipEntryHeader.getMethod() == Zip.FILE_UNCOMPRESS_METHOD_FLAG) { + EntryType type = entry.getZipEntryData().getType(); + short method = zipEntryHeader.getMethod(); + if ((EntryType.runnableFile.equals(type) && method == Zip.FILE_UNCOMPRESS_METHOD_FLAG)) { + continue; + } + if (EntryType.bitMap.equals(type)) { + createPageInfoExtension(entry); continue; } // if the first file is not uncompressed abc or so, set dataSize to zero @@ -224,7 +239,7 @@ public class CodeSigning { + zipEntryHeader.getFileNameLength() + zipEntryHeader.getExtraLength(); break; } - if ((dataSize % CodeSignBlock.PAGE_SIZE_4K) != 0) { + if (!NumberUtils.isMultiple4K(dataSize)) { throw new HapFormatException( String.format(Locale.ROOT, "Invalid dataSize(%d), not a multiple of 4096", dataSize)); } @@ -328,7 +343,7 @@ public class CodeSigning { while ((libEntry = hnpInputStream.getNextEntry()) != null) { byte[] bytes = new byte[4]; hnpInputStream.read(bytes, 0, 4); - if (!isElfFile(bytes)) { + if (!ElfHeader.isElfFile(bytes)) { hnpInputStream.closeEntry(); continue; } @@ -375,7 +390,7 @@ public class CodeSigning { if (StringUtils.isEmpty(entryName)) { return false; } - if (entryName.endsWith(NATIVE_LIB_AN_SUFFIX)) { + if (entryName.endsWith(FileUtils.NATIVE_LIB_AN_SUFFIX)) { return true; } if (entryName.startsWith(FileUtils.LIBS_PATH_PREFIX)) { @@ -384,13 +399,6 @@ public class CodeSigning { return false; } - private boolean isElfFile(byte[] bytes) { - if (bytes == null || bytes.length != 4) { - return false; - } - return bytes[0] == 0x7F && bytes[1] == 0x45 && bytes[2] == 0x4C && bytes[3] == 0x46; - } - /** * Sign specific entries in a hap * @@ -438,6 +446,7 @@ public class CodeSigning { public Pair signFile(InputStream inputStream, long fileSize, boolean storeTree, long fsvTreeOffset, String ownerID) throws FsVerityDigestException, CodeSignException { FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.setPageInfoExtension(pageInfoExtension); fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset); byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest(); byte[] signature = generateSignature(fsVerityDigest, ownerID); @@ -453,6 +462,13 @@ public class CodeSigning { Extension merkleTreeExtension = new MerkleTreeExtension(merkleTreeSize, fsvTreeOffset, fsVerityGenerator.getRootHash()); signInfo.addExtension(merkleTreeExtension); + if (pageInfoExtension != null) { + byte[] fsVerityDigestV2 = fsVerityGenerator.getFsVerityDigestV2(); + byte[] signatureV2 = generateSignature(fsVerityDigestV2, ownerID); + pageInfoExtension.setSignature(signatureV2); + signInfo.addExtension(pageInfoExtension); + LOGGER.debug(pageInfoExtension.toString()); + } } return Pair.create(signInfo, fsVerityGenerator.getTreeBytes()); } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/PageInfoGenerator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/PageInfoGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..d376bad12d18117de6d660d54af01c17b670e5b1 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/PageInfoGenerator.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2024-2024 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.PageInfoExtension; +import com.ohos.hapsigntool.codesigning.elf.ElfFile; +import com.ohos.hapsigntool.codesigning.elf.ElfProgramHeader; +import com.ohos.hapsigntool.codesigning.exception.ElfFormatException; +import com.ohos.hapsigntool.codesigning.utils.NumberUtils; +import com.ohos.hapsigntool.error.HapFormatException; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.zip.EntryType; +import com.ohos.hapsigntool.zip.Zip; +import com.ohos.hapsigntool.zip.ZipEntry; +import com.ohos.hapsigntool.zip.ZipEntryHeader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * pages info bitmap generator + * + * @since 2024/07/01 + */ +public class PageInfoGenerator { + private static final byte ABC_M_CODE = 2; + + private static final byte ELF_M_CODE = 1; + + private static final Logger LOGGER = LogManager.getLogger(PageInfoGenerator.class); + + private long maxEntryDataOffset = 0L; + + private final List excSegmentList = new ArrayList<>(); + + /** + * Constructor for PageInfoGenerator + * + * @param zip zip + * @throws IOException io error + * @throws HapFormatException hap file format error + */ + public PageInfoGenerator(Zip zip) throws IOException, HapFormatException, ElfFormatException { + Map runnableFileNames = new LinkedHashMap<>(); + List zipEntries = zip.getZipEntries(); + for (ZipEntry entry : zipEntries) { + ZipEntryHeader zipEntryHeader = entry.getZipEntryData().getZipEntryHeader(); + long entryDataOffset = entry.getCentralDirectory().getOffset() + ZipEntryHeader.HEADER_LENGTH + + zipEntryHeader.getFileNameLength() + zipEntryHeader.getExtraLength(); + if (!NumberUtils.isMultiple4K(entryDataOffset)) { + throw new HapFormatException( + String.format(Locale.ROOT, "Invalid entryDataOffset(%d), not a multiple of 4096", entryDataOffset)); + } + if (EntryType.runnableFile.equals(entry.getZipEntryData().getType()) + && Zip.FILE_UNCOMPRESS_METHOD_FLAG == entry.getZipEntryData().getZipEntryHeader().getMethod()) { + runnableFileNames.put(zipEntryHeader.getFileName(), entryDataOffset); + continue; + } + maxEntryDataOffset = entryDataOffset; + break; + } + File input = new File(zip.getFile()); + try (JarFile hap = new JarFile(input, false)) { + for (Map.Entry en : runnableFileNames.entrySet()) { + this.libExecSegment(hap, en.getKey(), en.getValue()); + } + } + } + + private void libExecSegment(JarFile hap, String libFileName, long entryDataOffset) + throws IOException, ElfFormatException { + JarEntry libEntry = hap.getJarEntry(libFileName); + if (libFileName.endsWith(FileUtils.ABC_FILE_SUFFIX)) { + long size = libEntry.getSize(); + excSegmentList.add(new ExcSegment(ABC_M_CODE, libFileName, entryDataOffset, entryDataOffset + size)); + } else { + try (InputStream stream = hap.getInputStream(libEntry)) { + ElfFile elfFile = new ElfFile(stream); + if (!elfFile.isElfFile()) { + LOGGER.info("{} not ELF file", libFileName); + return; + } + List elfPHeaders = elfFile.filterExecPHeaders(); + for (ElfProgramHeader programHeader : elfPHeaders) { + long pOffset = programHeader.getPOffset(); + long pFilesz = programHeader.getPFilesz(); + long off = entryDataOffset + pOffset; + long endoff = off + pFilesz; + excSegmentList.add(new ExcSegment(ELF_M_CODE, libFileName, off, endoff)); + } + } + } + } + + /** + * generate bitMap + * + * @return byte array of bitmap + * @throws HapFormatException hap format error + */ + public byte[] generateBitMap() throws HapFormatException { + if (!NumberUtils.isMultiple4K(maxEntryDataOffset)) { + throw new HapFormatException( + String.format(Locale.ROOT, "Invalid maxEndOff(%d), not a multiple of 4096", maxEntryDataOffset)); + } + int len = (int) (maxEntryDataOffset / CodeSignBlock.PAGE_SIZE_4K * PageInfoExtension.DEFAULT_UNIT_SIZE); + BitSet bitmap = new BitSet(len); + for (ExcSegment es : excSegmentList) { + int begin = (int) (es.getStartOffset() >> 12) * PageInfoExtension.DEFAULT_UNIT_SIZE; + int end = (NumberUtils.isMultiple4K(es.getEndOffset())) + ? (int) ((es.getEndOffset() >> 12)) * PageInfoExtension.DEFAULT_UNIT_SIZE + : (int) ((es.getEndOffset() >> 12) + 1) * PageInfoExtension.DEFAULT_UNIT_SIZE; + for (int i = begin; i < end; i = i + PageInfoExtension.DEFAULT_UNIT_SIZE) { + if ((ELF_M_CODE == es.getType())) { + bitmap.set(i); + } else { + bitmap.set(i + 1); + } + } + } + long[] longArray = bitmap.toLongArray(); + ByteBuffer buffer = ByteBuffer.allocate(longArray.length * 8).order(ByteOrder.LITTLE_ENDIAN); + for (long l : longArray) { + buffer.putLong(l); + } + return buffer.array(); + } + + static class ExcSegment { + /** + * abc or elf + */ + private byte type; + + private String fileName; + + private long startOffset; + + private long endOffset; + + ExcSegment(byte type, String fileName, long startOffset, long endOffset) { + this.type = type; + this.fileName = fileName; + this.startOffset = startOffset; + this.endOffset = endOffset; + } + + public byte getType() { + return type; + } + + public String getFileName() { + return fileName; + } + + public long getStartOffset() { + return startOffset; + } + + public long getEndOffset() { + return endOffset; + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/NumberUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/NumberUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..ab62c214276786ca3093ec5fa3cceb568f1623ae --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/NumberUtils.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024-2024 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; + +/** + * InputStream util class + * + * @since 2024/07/01 + */ +public class NumberUtils { + /** + * Check whether number is multiple of 4096 + * + * @param num check number + * @return true if l is multiple of 4096 + */ + public static boolean isMultiple4K(long num) { + return (num & 0xfffL) == 0; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java index edf3bf6e1eb1abd6c509aa9d5266dd14fa8a0870..7413b759dce6928159e63f31239e2fd666127ca5 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java @@ -19,6 +19,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; +import com.ohos.hapsigntool.codesigning.exception.ElfFormatException; +import com.ohos.hapsigntool.codesigning.sign.PageInfoGenerator; import com.ohos.hapsigntool.entity.Options; import com.ohos.hapsigntool.codesigning.exception.CodeSignException; import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; @@ -328,15 +330,11 @@ public abstract class SignProvider { File input = new File(signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE)); output = new File(signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE)); String suffix = getFileSuffix(input); - if (input.getCanonicalPath().equals(output.getCanonicalPath())) { - tmpOutput = File.createTempFile("signedHap", "." + suffix); - isPathOverlap = true; - } else { - tmpOutput = output; - } + isPathOverlap = input.getCanonicalPath().equals(output.getCanonicalPath()); + tmpOutput = isPathOverlap ? File.createTempFile("signedHap", "." + suffix) : output; // copy file and Alignment int alignment = Integer.parseInt(signParams.get(ParamConstants.PARAM_BASIC_ALIGNMENT)); - Zip zip = copyFileAndAlignment(input, tmpOutput, alignment); + Zip zip = copyFileAndAlignment(input, tmpOutput, alignment, suffix); // generate sign block and output signedHap try (RandomAccessFile outputHap = new RandomAccessFile(tmpOutput, "rw")) { ZipDataInput outputHapIn = new RandomAccessFileZipDataInput(outputHap); @@ -364,7 +362,8 @@ public abstract class SignProvider { isRet = true; } } catch (FsVerityDigestException | InvalidKeyException | HapFormatException | MissingParamsException -|InvalidParamsException |ProfileException |NumberFormatException |CustomException |IOException |CodeSignException e) { + | InvalidParamsException | ProfileException | NumberFormatException | CustomException | IOException + | CodeSignException | ElfFormatException e) { printErrorLogWithoutStack(e); } catch (SignatureException e) { printErrorLog(e); @@ -497,14 +496,23 @@ public abstract class SignProvider { * @param input file input * @param tmpOutput file tmpOutput * @param alignment alignment + * @param suffix suffix * @return zip zip * @throws IOException io error * @throws HapFormatException hap format error */ - private Zip copyFileAndAlignment(File input, File tmpOutput, int alignment) - throws IOException, HapFormatException { + private Zip copyFileAndAlignment(File input, File tmpOutput, int alignment, String suffix) + throws IOException, HapFormatException, ElfFormatException { Zip zip = new Zip(input); zip.alignment(alignment); + if (StringUtils.containsIgnoreCase(CodeSigning.SUPPORT_FILE_FORM, suffix)) { + PageInfoGenerator pageInfoGenerator = new PageInfoGenerator(zip); + byte[] bitMap = pageInfoGenerator.generateBitMap(); + if (bitMap != null && bitMap.length > 0) { + zip.addBitMap(bitMap); + zip.alignment(alignment); + } + } zip.removeSignBlock(); long start = System.currentTimeMillis(); zip.toFile(tmpOutput.getCanonicalPath()); diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java index 72301e87a4bfb319ed94fb37b552b06bf4141870..fe65899ab981ac00da3c5120ce01c8d906ec10a0 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java @@ -78,6 +78,21 @@ public final class FileUtils { */ public static final String LIBS_PATH_PREFIX = "libs/"; + /** + * abc file suffix + */ + public static final String ABC_FILE_SUFFIX = ".abc"; + + /** + * an file suffix + */ + public static final String NATIVE_LIB_AN_SUFFIX = ".an"; + + /** + * bitmap file name + */ + public static final String BIT_MAP_FILENAME = ".pages.info"; + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private FileUtils() { @@ -494,7 +509,7 @@ public final class FileUtils { if (StringUtils.isEmpty(name)) { return false; } - if (name.endsWith(".an") || name.endsWith(".abc")) { + if (name.endsWith(NATIVE_LIB_AN_SUFFIX) || name.endsWith(ABC_FILE_SUFFIX)) { return true; } if (name.startsWith(LIBS_PATH_PREFIX)) { diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/CentralDirectory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/CentralDirectory.java index 04adc9b4260579ab487baa6b3a972068313d03bd..37e8d1d02c8258fbbd222b7e51c735b45aec0c3f 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/CentralDirectory.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/CentralDirectory.java @@ -155,6 +155,13 @@ public class CentralDirectory { private int length; + /** + * updateLength + */ + public void updateLength() { + length = CD_LENGTH + fileNameLength + extraLength + commentLength; + } + /** * get Central Directory * @@ -199,7 +206,7 @@ public class CentralDirectory { bf.get(readComment); cd.setComment(readComment); } - cd.setLength(CD_LENGTH + cd.getFileNameLength() + cd.getExtraLength() + cd.getCommentLength()); + cd.updateLength(); return cd; } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EndOfCentralDirectory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EndOfCentralDirectory.java index 92d211b305612c48bfd453e3478ab2ab3d7be970..6936dadfd59cdf7a9f51125baa7b89ec9f97c56d 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EndOfCentralDirectory.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EndOfCentralDirectory.java @@ -123,8 +123,8 @@ public class EndOfCentralDirectory { eocd.setDiskNum(UnsignedDecimalUtil.getUnsignedShort(bf)); eocd.setcDStartDiskNum(UnsignedDecimalUtil.getUnsignedShort(bf)); eocd.setThisDiskCDNum(UnsignedDecimalUtil.getUnsignedShort(bf)); - eocd.setcDTotal(UnsignedDecimalUtil.getUnsignedShort(bf)); - eocd.setcDSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + eocd.setCDTotal(UnsignedDecimalUtil.getUnsignedShort(bf)); + eocd.setCDSize(UnsignedDecimalUtil.getUnsignedInt(bf)); eocd.setOffset(UnsignedDecimalUtil.getUnsignedInt(bf)); eocd.setCommentLength(UnsignedDecimalUtil.getUnsignedShort(bf)); if (bf.remaining() != eocd.getCommentLength()) { @@ -195,19 +195,19 @@ public class EndOfCentralDirectory { this.thisDiskCDNum = thisDiskCDNum; } - public int getcDTotal() { + public int getCDTotal() { return cDTotal; } - public void setcDTotal(int cDTotal) { + public void setCDTotal(int cDTotal) { this.cDTotal = cDTotal; } - public long getcDSize() { + public long getCDSize() { return cDSize; } - public void setcDSize(long cDSize) { + public void setCDSize(long cDSize) { this.cDSize = cDSize; } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EntryType.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EntryType.java new file mode 100644 index 0000000000000000000000000000000000000000..bd190dc3295018af100d0030028847eb37152160 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/EntryType.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.zip; + +/** + * Entry Type + * + * @since 2024/06/25 + */ +public enum EntryType { + runnableFile, + bitMap, + resourceFile; +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/Zip.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/Zip.java index 7ab9bdc2ed2b9a31894b6837620bd6b1d93411f9..97d1ed4275c896c35955980384a056479d5d2b1b 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/Zip.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/Zip.java @@ -43,7 +43,7 @@ public class Zip { /** * file is uncompress file flag */ - public static final int FILE_UNCOMPRESS_METHOD_FLAG = 0; + public static final short FILE_UNCOMPRESS_METHOD_FLAG = 0; /** * max comment length @@ -129,9 +129,9 @@ public class Zip { } private void getZipCentralDirectory(File file) throws IOException { - zipEntries = new ArrayList<>(endOfCentralDirectory.getcDTotal()); + zipEntries = new ArrayList<>(endOfCentralDirectory.getCDTotal()); // read full central directory bytes - byte[] cdBytes = FileUtils.readFileByOffsetAndLength(file, cDOffset, endOfCentralDirectory.getcDSize()); + byte[] cdBytes = FileUtils.readFileByOffsetAndLength(file, cDOffset, endOfCentralDirectory.getCDSize()); if (cdBytes.length < CentralDirectory.CD_LENGTH) { throw new ZipException("find zip cd failed"); } @@ -189,8 +189,15 @@ public class Zip { for (ZipEntry entry : zipEntries) { ZipEntryData zipEntryData = entry.getZipEntryData(); FileUtils.writeByteToOutFile(zipEntryData.getZipEntryHeader().toBytes(), fos); - boolean isSuccess = FileUtils.appendWriteFileByOffsetToFile(file, fos, - zipEntryData.getFileOffset(), zipEntryData.getFileSize()); + boolean isSuccess; + if (entry.getZipEntryData().getData() != null) { + ByteBuffer bf = ByteBuffer.wrap(entry.getZipEntryData().getData()); + bf.order(ByteOrder.LITTLE_ENDIAN); + isSuccess = FileUtils.writeByteToOutFile(bf.array(), fos); + } else { + isSuccess = FileUtils.appendWriteFileByOffsetToFile(file, fos, + zipEntryData.getFileOffset(), zipEntryData.getFileSize()); + } if (!isSuccess) { throw new ZipException("write zip data failed"); } @@ -228,8 +235,9 @@ public class Zip { break; } int alignBytes; - if (method == FILE_UNCOMPRESS_METHOD_FLAG && FileUtils.isRunnableFile( - zipEntryData.getZipEntryHeader().getFileName())) { + EntryType type = entry.getZipEntryData().getType(); + if ((type == EntryType.runnableFile && method == FILE_UNCOMPRESS_METHOD_FLAG) || + type == EntryType.bitMap) { // .abc and .so file align 4096 byte. alignBytes = 4096; } else if (isFirstUnRunnableFile) { @@ -250,6 +258,30 @@ public class Zip { } } + /** + * add bit map entry + * + * @param data bitmap data + * @throws ZipException ZipException + */ + public void addBitMap(byte[] data) throws ZipException { + for (ZipEntry e : zipEntries) { + if (e.getZipEntryData().getType() == EntryType.bitMap) { + e.getZipEntryData().setData(data); + e.getZipEntryData().getZipEntryHeader().setUnCompressedSize(data.length); + e.getZipEntryData().getZipEntryHeader().setCompressedSize(data.length); + return; + } + } + ZipEntry entry = new ZipEntry.Builder().setMethod(FILE_UNCOMPRESS_METHOD_FLAG) + .setUncompressedSize(data.length) + .setCompressedSize(data.length) + .setFileName(FileUtils.BIT_MAP_FILENAME) + .setData(data) + .build(); + zipEntries.add(entry); + } + /** * remove sign block */ @@ -262,22 +294,19 @@ public class Zip { * sort uncompress entry in the front. */ private void sort() { - // sort uncompress file (so, abc, an) - other uncompress file - compress file + // sort uncompress file (so, abc, an) - bitmap - other uncompress file - compress file zipEntries.sort((entry1, entry2) -> { short entry1Method = entry1.getZipEntryData().getZipEntryHeader().getMethod(); short entry2Method = entry2.getZipEntryData().getZipEntryHeader().getMethod(); String entry1FileName = entry1.getZipEntryData().getZipEntryHeader().getFileName(); String entry2FileName = entry2.getZipEntryData().getZipEntryHeader().getFileName(); if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG && entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) { - boolean isRunnableFile1 = FileUtils.isRunnableFile(entry1FileName); - boolean isRunnableFile2 = FileUtils.isRunnableFile(entry2FileName); - if (isRunnableFile1 && isRunnableFile2) { - return entry1FileName.compareTo(entry2FileName); - } else if (isRunnableFile1) { - return -1; - } else if (isRunnableFile2) { - return 1; + EntryType entry1Type = entry1.getZipEntryData().getType(); + EntryType entry2Type = entry2.getZipEntryData().getType(); + if (entry1Type != entry2Type) { + return entry1Type.compareTo(entry2Type); } + return entry1FileName.compareTo(entry2FileName); } else if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG) { return -1; } else if (entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) { @@ -292,6 +321,7 @@ public class Zip { long offset = 0L; long cdLength = 0L; for (ZipEntry entry : zipEntries) { + entry.updateLength(); entry.getCentralDirectory().setOffset(offset); offset += entry.getZipEntryData().getLength(); cdLength += entry.getCentralDirectory().getLength(); @@ -301,9 +331,11 @@ public class Zip { } cDOffset = offset; endOfCentralDirectory.setOffset(offset); - endOfCentralDirectory.setcDSize(cdLength); + endOfCentralDirectory.setCDSize(cdLength); offset += cdLength; eOCDOffset = offset; + endOfCentralDirectory.setCDTotal(zipEntries.size()); + endOfCentralDirectory.setThisDiskCDNum(zipEntries.size()); } public List getZipEntries() { diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntry.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntry.java index ff66339ad690b1ddacba48b9ec301cb80c16dc5e..74f7ff3def4f92e79723d69f7bfcdd1f917bc110 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntry.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntry.java @@ -18,6 +18,8 @@ package com.ohos.hapsigntool.zip; import com.ohos.hapsigntool.error.ZipException; import java.util.Arrays; +import java.util.Calendar; +import java.util.zip.CRC32; /** * ZipEntry and CentralDirectory data @@ -27,7 +29,15 @@ import java.util.Arrays; public class ZipEntry { private ZipEntryData zipEntryData; - private CentralDirectory fileEntryIncentralDirectory; + private CentralDirectory fileEntryInCentralDirectory; + + /** + * updateLength + */ + public void updateLength() { + zipEntryData.updateLength(); + fileEntryInCentralDirectory.updateLength(); + } /** * alignment one entry @@ -40,7 +50,7 @@ public class ZipEntry { // if cd extra len bigger than entry extra len, make cd and entry extra length equals int padding = calZeroPaddingLengthForEntryExtra(); int remainder = (int) ((zipEntryData.getZipEntryHeader().getLength() - + fileEntryIncentralDirectory.getOffset()) % alignNum); + + fileEntryInCentralDirectory.getOffset()) % alignNum); if (remainder == 0) { return padding; @@ -58,7 +68,7 @@ public class ZipEntry { private int calZeroPaddingLengthForEntryExtra() throws ZipException { int entryExtraLen = zipEntryData.getZipEntryHeader().getExtraLength(); - int cdExtraLen = fileEntryIncentralDirectory.getExtraLength(); + int cdExtraLen = fileEntryInCentralDirectory.getExtraLength(); if (cdExtraLen > entryExtraLen) { setEntryHeaderNewExtraLength(cdExtraLen); return cdExtraLen - entryExtraLen; @@ -71,12 +81,12 @@ public class ZipEntry { } private void setCenterDirectoryNewExtraLength(int newLength) throws ZipException { - byte[] newCDExtra = getAlignmentNewExtra(newLength, fileEntryIncentralDirectory.getExtraData()); - fileEntryIncentralDirectory.setExtraData(newCDExtra); - fileEntryIncentralDirectory.setExtraLength(newLength); - fileEntryIncentralDirectory.setLength(CentralDirectory.CD_LENGTH - + fileEntryIncentralDirectory.getFileNameLength() - + fileEntryIncentralDirectory.getExtraLength() + fileEntryIncentralDirectory.getCommentLength()); + byte[] newCDExtra = getAlignmentNewExtra(newLength, fileEntryInCentralDirectory.getExtraData()); + fileEntryInCentralDirectory.setExtraData(newCDExtra); + fileEntryInCentralDirectory.setExtraLength(newLength); + fileEntryInCentralDirectory.setLength(CentralDirectory.CD_LENGTH + + fileEntryInCentralDirectory.getFileNameLength() + + fileEntryInCentralDirectory.getExtraLength() + fileEntryInCentralDirectory.getCommentLength()); } private void setEntryHeaderNewExtraLength(int newLength) throws ZipException { @@ -109,10 +119,163 @@ public class ZipEntry { } public CentralDirectory getCentralDirectory() { - return fileEntryIncentralDirectory; + return fileEntryInCentralDirectory; } public void setCentralDirectory(CentralDirectory centralDirectory) { - this.fileEntryIncentralDirectory = centralDirectory; + this.fileEntryInCentralDirectory = centralDirectory; + } + + /** + * zip entry builder + */ + public static class Builder { + private short version = 10; + + private short flag = 2048; + + private short method = 0; + + private long compressedSize; + + private long unCompressedSize; + + private String fileName; + + private byte[] extraData; + + private byte[] comment; + + private byte[] data; + + public Builder setVersion(short version) { + this.version = version; + return this; + } + + public Builder setFlag(short flag) { + this.flag = flag; + return this; + } + + public Builder setMethod(short method) { + this.method = method; + return this; + } + + public Builder setCompressedSize(long compressedSize) { + this.compressedSize = compressedSize; + return this; + } + + public Builder setUncompressedSize(long unCompressedSize) { + this.unCompressedSize = unCompressedSize; + return this; + } + + public Builder setFileName(String fileName) { + this.fileName = fileName; + return this; + } + + public Builder setExtraData(byte[] extraData) { + this.extraData = extraData; + return this; + } + + public Builder setComment(byte[] comment) { + this.comment = comment; + return this; + } + + public Builder setData(byte[] data) { + this.data = data; + return this; + } + + /** + * build zip entry + * + * @return Zip Entry + * @throws ZipException ZipException + */ + public ZipEntry build() throws ZipException { + Calendar calendar = Calendar.getInstance(); + // java time stamp to dos timestamp, dos time start 1980 + int time = (calendar.get(Calendar.YEAR) - 1980) << 25 | (calendar.get(Calendar.MONTH) + 1) << 21 + | calendar.get(Calendar.DAY_OF_MONTH) << 16 | calendar.get(Calendar.HOUR_OF_DAY) << 11 + | calendar.get(Calendar.MINUTE) << 5 | calendar.get(Calendar.SECOND) >> 1; + CentralDirectory cd = addCenterDirectory(time); + ZipEntryHeader zipEntryHeader = addZipEntryHeader(time); + if (data == null) { + throw new ZipException("can not find entry data"); + } + final CRC32 c = new CRC32(); + c.update(data); + final int crc32 = new Long(c.getValue()).intValue(); + cd.setCrc32(crc32); + zipEntryHeader.setCrc32(crc32); + + ZipEntryData entryData = new ZipEntryData(); + entryData.setData(data); + entryData.setZipEntryHeader(zipEntryHeader); + ZipEntry entry = new ZipEntry(); + entry.setZipEntryData(entryData); + entryData.setType(EntryType.bitMap); + entry.setCentralDirectory(cd); + return entry; + } + + private CentralDirectory addCenterDirectory(int time) { + CentralDirectory cd = new CentralDirectory(); + cd.setVersion(version); + cd.setVersionExtra(version); + cd.setFlag(flag); + cd.setMethod(method); + cd.setLastTime((short) time); + cd.setLastDate((short) (time >> 16)); + cd.setCompressedSize(compressedSize); + cd.setUnCompressedSize(unCompressedSize); + cd.setFileName(fileName); + cd.setFileNameLength(fileName.length()); + if (extraData != null) { + cd.setExtraData(extraData); + cd.setExtraLength(extraData.length); + } else { + cd.setExtraLength(0); + } + if (comment != null) { + cd.setComment(comment); + cd.setCommentLength(comment.length); + } else { + cd.setCommentLength(0); + } + cd.setDiskNumStart(0); + cd.setExternalFile(0); + + cd.updateLength(); + return cd; + } + + private ZipEntryHeader addZipEntryHeader(int time) { + ZipEntryHeader zipEntryHeader = new ZipEntryHeader(); + zipEntryHeader.setVersion(version); + zipEntryHeader.setFlag(flag); + zipEntryHeader.setMethod(method); + zipEntryHeader.setLastTime((short) time); + zipEntryHeader.setLastDate((short) (time >> 16)); + zipEntryHeader.setCompressedSize(compressedSize); + zipEntryHeader.setUnCompressedSize(unCompressedSize); + zipEntryHeader.setFileName(fileName); + zipEntryHeader.setFileNameLength(fileName.length()); + if (extraData != null) { + zipEntryHeader.setExtraData(extraData); + zipEntryHeader.setExtraLength(extraData.length); + } else { + zipEntryHeader.setExtraLength(0); + } + zipEntryHeader.updateLength(); + return zipEntryHeader; + } } } \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryData.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryData.java index 35b19e7ad5833b31343f479a92d321a808507651..f32cd5efef955262031a71088898896d80f3e93e 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryData.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryData.java @@ -47,8 +47,20 @@ public class ZipEntryData { private long length; - public ZipEntryHeader getZipEntryHeader() { - return zipEntryHeader; + private EntryType type; + + private byte[] data; + + /** + * updateLength + */ + public void updateLength() { + zipEntryHeader.updateLength(); + if (data != null) { + length = zipEntryHeader.getLength() + data.length + (dataDescriptor == null ? 0 : 16); + } else { + length = zipEntryHeader.getLength() + fileSize + (dataDescriptor == null ? 0 : 16); + } } /** @@ -88,6 +100,14 @@ public class ZipEntryData { entry.setFileSize(fileSize); input.skip(fileSize); + if (FileUtils.isRunnableFile(entryHeader.getFileName())) { + entry.setType(EntryType.runnableFile); + } else if (FileUtils.BIT_MAP_FILENAME.equals(entryHeader.getFileName())) { + entry.setType(EntryType.bitMap); + } else { + entry.setType(EntryType.resourceFile); + } + long entryLength = entryHeader.getLength() + fileSize; short flag = entryHeader.getFlag(); // set desc null flag @@ -109,6 +129,10 @@ public class ZipEntryData { this.zipEntryHeader = zipEntryHeader; } + public ZipEntryHeader getZipEntryHeader() { + return zipEntryHeader; + } + public DataDescriptor getDataDescriptor() { return dataDescriptor; } @@ -140,4 +164,20 @@ public class ZipEntryData { public void setLength(long length) { this.length = length; } + + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } + + public EntryType getType() { + return type; + } + + public void setType(EntryType type) { + this.type = type; + } } \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryHeader.java index 7c880a6bea5a14cb38fa5da1a9e7a777a0ef890f..d020c8fa237577be67bf44f40af1b98852003f5b 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryHeader.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/ZipEntryHeader.java @@ -113,6 +113,13 @@ public class ZipEntryHeader { private int length; + /** + * updateLength + */ + public void updateLength() { + length = HEADER_LENGTH + fileNameLength + extraLength; + } + /** * get Zip Entry Header * @@ -137,7 +144,7 @@ public class ZipEntryHeader { entryHeader.setUnCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); entryHeader.setFileNameLength(UnsignedDecimalUtil.getUnsignedShort(bf)); entryHeader.setExtraLength(UnsignedDecimalUtil.getUnsignedShort(bf)); - entryHeader.setLength(HEADER_LENGTH + entryHeader.getFileNameLength() + entryHeader.getExtraLength()); + entryHeader.updateLength(); return entryHeader; }